返回介绍

7.6 混合使用静态方法、类方法和抽象方法

发布于 2024-01-23 21:41:46 字数 5663 浏览 0 评论 0 收藏 0

这些装饰器各有各的用处,但有时可能会需要同时使用。下面介绍一些相关的小技巧。

抽象方法的原型并非一成不变。在实际实现方法的时候,可以根据需要对方法的参数进行扩展。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文