Python中使用static、class、abstract方法

方法在Python中是如何工做的python

方法就是一個函數,它做爲一個類屬性而存在,你能夠用以下方式來聲明、訪問一個函數:函數

Pythonthis

 

1編碼

2spa

3.net

43d

5對象

6繼承

7接口

8

>>> class Pizza(object):

...     def __init__(self, size):

...         self.size = size

...     def get_size(self):

...         return self.size

...

>>> Pizza.get_size

<unbound method Pizza.get_size>

Python在告訴你,屬性_get_size是類Pizza的一個未綁定方法。這是什麼意思呢?很快咱們就會知道答案:

Python

 

1

2

3

4

>>> Pizza.get_size()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead)

咱們不能這麼調用,由於它尚未綁定到Pizza類的任何實例上,它須要一個實例做爲第一個參數傳遞進去(Python2必須是該類的實例,Python3中能夠是任何東西),嘗試一下:

Python

 

1

2

>>> Pizza.get_size(Pizza(42))

42

太棒了,如今用一個實例做爲它的的第一個參數來調用,整個世界都清靜了,若是我說這種調用方式還不是最方便的,你也會這麼認爲的;沒錯,如今每次調用這個方法的時候咱們都不得不引用這個類,若是不知道哪一個類是咱們的對象,長期看來這種方式是行不通的。

那麼Python爲咱們作了什麼呢,它綁定了全部來自類_Pizza的方法以及該類的任何一個實例的方法。也就意味着如今屬性get_sizePizza的一個實例對象的綁定方法,這個方法的第一個參數就是該實例自己。

Python

 

1

2

3

4

>>> Pizza(42).get_size

<bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>>

>>> Pizza(42).get_size()

42

和咱們預期的同樣,如今再也不須要提供任何參數給_get_size,由於它已是綁定的,它的self參數會自動地設置給Pizza實例,下面代碼是最好的證實:

Python

 

1

2

3

>>> m = Pizza(42).get_size

>>> m()

42

更有甚者,你都不必使用持有Pizza對象的引用了,由於該方法已經綁定到了這個對象,因此這個方法對它本身來講是已經足夠了。

也許,若是你想知道這個綁定的方法是綁定在哪一個對象上,下面這種手段就能得知:

Python

 

1

2

3

4

5

6

7

>>> m = Pizza(42).get_size

>>> m.__self__

<__main__.Pizza object at 0x7f3138827910>

>>> # You could guess, look at this:

...

>>> m == m.__self__.get_size

True

顯然,該對象仍然有一個引用存在,只要你願意你仍是能夠把它找回來。

在Python3中,依附在類上的函數再也不看成是未綁定的方法,而是把它看成一個簡單地函數,若是有必要它會綁定到一個對象身上去,原則依然和Python2保持一致,可是模塊更簡潔:

Python

 

1

2

3

4

5

6

7

8

>>> class Pizza(object):

...     def __init__(self, size):

...         self.size = size

...     def get_size(self):

...         return self.size

...

>>> Pizza.get_size

<function Pizza.get_size at 0x7f307f984dd0>

 

靜態方法

靜態方法是一類特殊的方法,有時你可能須要寫一個屬於這個類的方法,可是這些代碼徹底不會使用到實例對象自己,例如:

Python

 

1

2

3

4

5

6

7

class Pizza(object):

    @staticmethod

    def mix_ingredients(x, y):

        return x + y

 

    def cook(self):

        return self.mix_ingredients(self.cheese, self.vegetables)

這個例子中,若是把_mix_ingredients做爲非靜態方法一樣能夠運行,可是它要提供self參數,而這個參數在方法中根本不會被使用到。這裏的@staticmethod裝飾器能夠給咱們帶來一些好處:

  • Python再也不須要爲Pizza對象實例初始化一個綁定方法,綁定方法一樣是對象,可是建立他們須要成本,而靜態方法就能夠避免這些。

 

Python

 

1

2

3

4

5

6

>>> Pizza().cook is Pizza().cook

False

>>> Pizza().mix_ingredients is Pizza.mix_ingredients

True

>>> Pizza().mix_ingredients is Pizza().mix_ingredients

True

 

  • 可讀性更好的代碼,看到@staticmethod咱們就知道這個方法並不須要依賴對象自己的狀態。
  • 能夠在子類中被覆蓋,若是是把mix_ingredients做爲模塊的頂層函數,那麼繼承自Pizza的子類就無法改變pizza的mix_ingredients了若是不覆蓋cook的話。

類方法

話雖如此,什麼是類方法呢?類方法不是綁定到對象上,而是綁定在類上的方法。

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

>>> class Pizza(object):

...     radius = 42

...     @classmethod

...     def get_radius(cls):

...         return cls.radius

...

>>>

>>> Pizza.get_radius

<bound method type.get_radius of <class '__main__.Pizza'>>

>>> Pizza().get_radius

<bound method type.get_radius of <class '__main__.Pizza'>>

>>> Pizza.get_radius is Pizza().get_radius

True

>>> Pizza.get_radius()

42

不管你用哪一種方式訪問這個方法,它老是綁定到了這個類身上,它的第一個參數是這個類自己(記住:類也是對象)。

何時使用這種方法呢?類方法一般在如下兩種場景是很是有用的:

  • 工廠方法:它用於建立類的實例,例如一些預處理。若是使用@staticmethod代替,那咱們不得不硬編碼Pizza類名在函數中,這使得任何繼承Pizza的類都不能使用咱們這個工廠方法給它本身用。

 

Python

 

1

2

3

4

5

6

7

class Pizza(object):

    def __init__(self, ingredients):

        self.ingredients = ingredients

 

    @classmethod

    def from_fridge(cls, fridge):

        return cls(fridge.get_cheese() + fridge.get_vegetables())

 

  • 調用靜態類:若是你把一個靜態方法拆分紅多個靜態方法,除非你使用類方法,不然你仍是得硬編碼類名。使用這種方式聲明方法,Pizza類名明永遠都不會在被直接引用,繼承和方法覆蓋均可以完美的工做。

 

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Pizza(object):

    def __init__(self, radius, height):

        self.radius = radius

        self.height = height

 

    @staticmethod

    def compute_area(radius):

         return math.pi * (radius ** 2)

 

    @classmethod

    def compute_volume(cls, height, radius):

         return height * cls.compute_area(radius)

 

    def get_volume(self):

        return self.compute_volume(self.height, self.radius)

 

抽象方法

抽象方法是定義在基類中的一種方法,它沒有提供任何實現,相似於Java中接口(Interface)裏面的方法。

在Python中實現抽象方法最簡單地方式是:

Python

 

1

2

3

class Pizza(object):

    def get_radius(self):

        raise NotImplementedError

任何繼承自_Pizza的類必須覆蓋實現方法get_radius,不然會拋出異常。

這種抽象方法的實現有它的弊端,若是你寫一個類繼承Pizza,可是忘記實現get_radius,異常只有在你真正使用的時候纔會拋出來。

Python

 

1

2

3

4

5

6

7

>>> Pizza()

<__main__.Pizza object at 0x7fb747353d90>

>>> Pizza().get_radius()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 3, in get_radius

NotImplementedError

還有一種方式可讓錯誤更早的觸發,使用Python提供的abc模塊,對象被初始化以後就能夠拋出異常:

Python

 

1

2

3

4

5

6

7

8

import abc

 

class BasePizza(object):

    __metaclass__  = abc.ABCMeta

 

    @abc.abstractmethod

    def get_radius(self):

         """Method that should do something."""

使用abc後,當你嘗試初始化BasePizza或者任何子類的時候立馬就會獲得一個TypeError,而無需等到真正調用get_radius的時候才發現異常。

Python

 

1

2

3

4

>>> BasePizza()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius

 

混合靜態方法、類方法、抽象方法

當你開始構建類和繼承結構時,混合使用這些裝飾器的時候到了,因此這裏列出了一些技巧。

記住,聲明一個抽象的方法,不會固定方法的原型,這就意味着雖然你必須實現它,可是我能夠用任何參數列表來實現:

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

import abc

 

class BasePizza(object):

    __metaclass__  = abc.ABCMeta

 

    @abc.abstractmethod

    def get_ingredients(self):

         """Returns the ingredient list."""

 

class Calzone(BasePizza):

    def get_ingredients(self, with_egg=False):

        egg = Egg() if with_egg else None

        return self.ingredients + egg

這樣是容許的,由於Calzone知足BasePizza對象所定義的接口需求。一樣咱們也能夠用一個類方法或靜態方法來實現:

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

import abc

 

class BasePizza(object):

    __metaclass__  = abc.ABCMeta

 

    @abc.abstractmethod

    def get_ingredients(self):

         """Returns the ingredient list."""

 

class DietPizza(BasePizza):

    @staticmethod

    def get_ingredients():

        return None

這一樣是正確的,由於它遵循抽象類BasePizza設定的契約。事實上get_ingredients方法並不須要知道返回結果是什麼,結果是實現細節,不是契約條件。

所以,你不能強制抽象方法的實現是一個常規方法、或者是類方法仍是靜態方法,也沒什麼可爭論的。從Python3開始(在Python2中不能如你期待的運行,見issue5867),在abstractmethod方法上面使用@staticmethod@classmethod裝飾器成爲可能。

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

import abc

 

class BasePizza(object):

    __metaclass__  = abc.ABCMeta

 

    ingredient = ['cheese']

 

    @classmethod

    @abc.abstractmethod

    def get_ingredients(cls):

         """Returns the ingredient list."""

         return cls.ingredients

別誤會了,若是你認爲它會強制子類做爲一個類方法來實現get_ingredients那你就錯了,它僅僅表示你實現的get_ingredientsBasePizza中是一個類方法。

能夠在抽象方法中作代碼的實現?沒錯,Python與Java接口中的方法相反,你能夠在抽象方法編寫實現代碼經過super()來調用它。(譯註:在Java8中,接口也提供的默認方法,容許在接口中寫方法的實現)

Python

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

import abc

 

class BasePizza(object):

    __metaclass__  = abc.ABCMeta

 

    default_ingredients = ['cheese']

 

    @classmethod

    @abc.abstractmethod

    def get_ingredients(cls):

         """Returns the ingredient list."""

         return cls.default_ingredients

 

class DietPizza(BasePizza):

    def get_ingredients(self):

        return ['egg'] + super(DietPizza, self).get_ingredients()

這個例子中,你構建的每一個pizza都經過繼承BasePizza的方式,你不得不覆蓋get_ingredients方法,可是可以使用默認機制經過super()來獲取ingredient列表。

相關文章
相關標籤/搜索