來源:http://www.xdarui.com/archives/261.html
html
原文http://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methodspython
代碼審查對發現人們很難理解的問題來講絕對是一種很讚的方式。我(Julien Danjou)最近在作OpenStack patches校對的時候發現人們不可以正確的理解與使用Python提供的不一樣的函數修飾。因此我這裏提供一個連接以便下次review的時候帖出。ide
一個方法就是一個函數,它依付於一個類之上。你能夠像這樣去定義和使用:函數
>>> 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>
Pizza
這個類中的
get_size
方法沒綁定,這是什麼意思?咱們嘗試去調用下就知道了:
>>> 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中能夠是任何東西)讓我這樣試試:
>>> Pizza.get_size(Pizza(42)) 42
因此Python幫咱們作了這些事情,給全部Pizza
的實例綁定全部它的方法。這意味着get_size
是Pizza
實例的已綁定方法:方法的第一個參數是實例它自己。ui
>>> Pizza(42).get_size <bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>> >>> Pizza(42).get_size() 42
get_size()
方法傳入任何參數,由於它已經綁定了,
Pizza
實例自身會自動設置成方法的第一個參數。這能夠證實:
>>> m = Pizza(42).get_size >>> m() 42
Pizza
對象保存引用,因其方法與對象綁定了,因此其方法自己就夠了。可是若是咱們想知道這個方法倒底綁定到了哪一個對象上該怎麼作?這有一個小技巧:
>>> 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中,直接使用類中的方法再也不視爲未綁定方法,而是一個簡單的函數,而只是在須要的時候才綁定到對象上。因此基本原則沒變只是簡化了模型。this
>>> 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>
靜態方法是方法中的一個特例。有時,你寫了一段屬於某個類的代碼,可是它並未使用這個類。好比:編碼
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
能夠幫咱們作這些狀況:
Pizza
實例都增長一個綁定的方法。綁定方法也是一個函數,因此建立它們也有額外的開銷。經過靜態方法能夠避免這些開銷:
>>> Pizza().cook is Pizza().cook False >>> Pizza().mix_ingredients is Pizza.mix_ingredients True >>> Pizza().mix_ingredients is Pizza().mix_ingredients True
說到這,倒底什麼是類方法?類方法是指不是綁定在對象上而是類上的方法!。3d
>>> 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
的類都沒法使用工廠方法的自身用處。
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
類名不會被直接使用,並且繼承的子類和重載父類都會很好的工做。
class Pizza(object): def __init__(self, radius, height): self.radius = radius self.height = height @staticmethod def compute_circumference(radius): return math.pi * (radius ** 2) @classmethod def compute_volume(cls, height, radius): return height * cls.compute_circumference(radius) def get_volume(self): return self.compute_volume(self.height, self.radius)
抽象方法是基類中定義的方法,但卻沒有任何實現。在Java中,能夠把方法申明成一個接口。而在Python中實現一個抽象方法的最簡便方法是:code
class Pizza(object): def get_radius(self): raise NotImplementedError
Pizza
繼承下來的子類都必需實現
get_radius
方法,不然就會產生一個錯誤。這種方式實如今抽象方法也有缺點,若是你寫了一個類繼承自
Pizza
但卻忘了實現
get_radius
時,只有當你用到了那個方法時纔會拋錯。
>>> 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
import abc class BasePizza(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def get_radius(self): """Method that should do something."""
abc
以及其特定的方法,一旦你嘗試去實例化
BasePizza
類或任意從其繼承下來的類的時候都會獲得一個錯誤。
>>> BasePizza() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius
當建立類及繼承時,你就得使用混搭這些方式的方法了。這裏一些提示。htm
請記住,申明類時應先申明成抽象類,而且不要把類的原型寫死。這意味着,抽象方法必需得實現,但我能夠在實現抽象方法時能夠任意指定參數列表。
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
得按要求實現對應的接口,這很是有用。這意味着,咱們能夠實現成一個類或一個靜態方法,好比:
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
。
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
做爲類的方法,你就錯了。這只是意味着你在
BasePizza
類中,實現
get_ingredients
只是一個類方法。
抽象類中實現抽象方法?是的!Python中抽象方法與Java相反。你能夠在抽象方法中加入代碼並經過super()
調用:
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()
BasePizza
類中繼承下來的pizza都得實現
get_ingredients
方法,可是咱們可使用
super()
經過默認機制獲取配料表。