来源: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()
经过默认机制获取配料表。