7.6 混合使用静态方法、类方法和抽象方法
这些装饰器各有各的用处,但有时可能会需要同时使用。下面介绍一些相关的小技巧。
抽象方法的原型并非一成不变。在实际实现方法的时候,可以根据需要对方法的参数进行扩展。
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
尽管静态方法get_ingredients没有基于对象的状态返回结果,但是它仍然满足在基类BasePizza中定义的抽象接口,所以它仍然是有效的。
从Python 3开始(在Python 2中有问题,详见issue 5867,http://bugs.python.org/issue5867),可能会支持在@abstractmethod之上使用@staticmethod和@classmethod装饰器,如示例7.13所示。
示例 7.13 混合使用@classmethod和@abstractmethod
import abc class BasePizza(object): __metaclass__ = abc.ABCMeta ingredients = ['cheese'] @classmethod @abc.abstractmethod def get_ingredients(cls): """Returns the ingredient list.""" return cls.ingredients
注意,像这样在BasePizza中定义get_ingredients为类方法并不会强迫其子类也将其定义为类方法。将其定义为静态方法也是一样,没有办法强迫子类将抽象方法实现为某种特定类型的方法。
但是等一下,这里我们在抽象方法中居然是有实现代码的。真的可以吗?是的,在Python中完全没问题!不同于Java,Python中可以在抽象方法中放入代码并使用super()调用它,如示例7.14所示。
示例 7.14 通过抽象方法使用super()
import abc class BasePizza(object): __metaclass__ = abc.ABCMeta default_ingredients = ['cheese'] @classmethod @abc.abstractmethod def get_ingredients(cls): """Returns the default ingredient list.""" return cls.default_ingredients class DietPizza(BasePizza): def get_ingredients(self): return [Egg()] + super(DietPizza, self).get_ingredients()
在这个例子中,每一个新的继承自BasePizza基类的Pizza子类都必须重写get_ingredients方法,但它可以通过基类的默认机制访问原料表。
7.7 关于super的真相
从Python的最早期开始,开发人员就能够通过单继承和多继承扩展他们的类。不过,很多开发人员似乎并不理解这些机制是如何工作的,以及与其关联的super()方法。
单继承和多继承各有利弊,组成或者鸭子类型都超出了本书的讨论范围,如果你不了解这些概念,那么建议读一些相关的资料以便形成自己的观点。
多继承仍然被广泛使用,尤其在使用了混合模式的代码里。这也是了解它仍然很重要的原因,因为它是Python核心的一部分。
注意
混入(mixin)类是指继承自两个或两个以上的类,并将它们的特性组合在一起。
到目前为止,你应该知道,在Python中类也是对象,而且对于这种构建类的特定声明方式应该非常熟悉了:class classname(expression of inheritance)。
括号内的部分是一个Python表达式,返回一个当前类要继承的类对象列表。通常,都会直接指定,但也可以像下面这样写:
>>> def parent(): ... return object ... >>> class A(parent()): ... pass ... >>> A.mro() [<class '__main__.A'>, <type 'object'>]
不出所料,可以正常运行:类A继承自父类object。类方法mro()返回方法解析顺序(method resolution order)用于解析属性。当前的MRO系统是在Python 2.3中第一次被实现的,关于其内部工作机制详见Python 2.3 release notes(http://www.python.org/download/releases/2.3/mro)。
你已经知道了调用父类方法的正规方式是通过super()函数,但你很可能不知道super()函数实际上是一个构造器,每次调用它都会实例化一个super对象。它接收一个或两个参数,第一个参数是一个类,第二个参数是一个子类或第一个参数的一个实例。
构造器返回的对象就像是第一个参数的父类的一个代理。它有自己的__getattribute__方法去遍历MRO列表中的类并返回第一个满足条件的属性:
>>> class A(object): ... bar = 42 ... def foo(self): ... pass ... >>> class B(object): ... bar = 0 ... >>> class C(A, B): ... xyz = 'abc' ... >>> C.mro() [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>] >>> super(C, C()).bar 42 >>> super(C, C()).foo <bound method C.foo of <__main__.C object at 0x7f0299255a90>> >>> super(B).__self__ >>> super(B, B()).__self__ <__main__.B object at
当请求C的实例的访问其super对象的一个属性时,它会遍历MRO列表,并从第一个包含这个属性的类中返回这个属性。
前一个例子中使用了绑定的super对象,也就是说,通过两个参数调用super。如果只通过一个参数调用super(),则会返回一个未绑定的super对象:
>>> super(C) <super: <class 'C'>, NULL>
由于对象是未绑定的,所以不能通过它访问类属性:
>>> super(C).foo Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'super' object has no attribute 'foo' >>> super(C).bar Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'super' object has no attribute 'bar' >>> super(C).xyz Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'super' object has no attribute 'xyz'
粗一看,似乎这种super对象没什么用,但是super类通过某种方式实现了描述符协议(也就是__get__),这种方式能够让未绑定的super对象像类属性一样有用:
>>> class D(C): ... sup = super(C) ... >>> D().sup <super: <class 'C'>, <D object>> >>> D().sup.foo <bound method D.foo of <__main__.D object at 0x7f0299255bd0>> >>> D().sup.bar 42
通过用实例和属性名字作为参数来调用未绑定的super对象的__get__方法(super(C).__get__(D(), 'foo'))能够让它找到并解析foo。
注意
尽管你可能没有听说过描述符协议,但是你很可能在不知道的情况已经通过@property装饰器使用过它。它是Python的一种机制,允许对象以属性的方式进行存储以返回其他东西而非其自身。本书不会讨论这个协议的具体细节,想详细了解可参考Python数据模型文档(http://docs.python.org/2/reference/datamodel.html#implementing-descriptors)。
在许多场景中使用super都是很有技巧的,例如处理继承链中不同的方法签名。遗憾的是,除了类似让方法接收*args, **kwargs参数这样的技巧,针对这个问题同样“没有银弹”。
在Python 3中,super()变得更加神奇:可以在一个方法中不传入任何参数调用它。但没有参数传给super()时,它会为它们自动搜索栈框架:
class B(A): def foo(self): super().foo()
super是在子类中访问父类属性的标准方式,应该尽量使用它。它能确保父类方法的协作调用不出意外,例如在多继承时父类方法没有被调用或者被调用了两次。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论