魔法方法、特性和迭代器
9.2 构造函数
10 11 12 13 14
在本书中,我并没有在所有示例中都显式地设置元类或继承object。然而,如果你的程序 无需与旧版Python兼容,建议将所有类都定义为新式类,并使用将在9.2.3节介绍的函数super 等功能。
请注意,在Python 3中没有旧式类,因此无需显式地继承object或将__metaclass__设置为 type。所有的类都将隐式地继承object。如果没有指定超类,将直接继承它,否则将间接地继 承它。
9.2 构造函数
我们要介绍的第一个魔法方法是构造函数。你可能从未听说过构造函数(constructor),它其 实就是本书前面一些示例中使用的初始化方法,只是命名为__init__。然而,构造函数不同于普 通方法的地方在于,将在对象创建后自动调用它们。因此,无需采用本书前面一直使用的做法:
>>> f = FooBar()
>>> f.init()
构造函数让你只需像下面这样做:
>>> f = FooBar()
在Python中,创建构造函数很容易,只需将方法init的名称从普通的init改为魔法版__init__
即可。
class FooBar:
def __init__(self):
self.somevar = 42
>>> f = FooBar()
>>> f.somevar 42
到目前为止一切顺利。但你可能会问,如果给构造函数添加几个参数,结果将如何呢?请看 下面的代码:
class FooBar:
def __init__(self, value=42):
self.somevar = value
你认为该如何使用这个构造函数呢?由于参数是可选的,你可以当什么事都没发生,还像原 来那样做。但如果要指定这个参数(或者说如果这个参数不是可选的)呢?你肯定猜到了,不过 这里还是演示一下。
>>> f = FooBar('This is a constructor argument')
>>> f.somevar
'This is a constructor argument'
在所有的Python魔法方法中,__init__绝对是你用得最多的。
注意 Python提供了魔法方法__del__,也称作析构函数(destructor)。这个方法在对象被销毁
(作为垃圾被收集)前被调用,但鉴于你无法知道准确的调用时间,建议尽可能不要使 用__del__。
9.2.1 重写普通方法和特殊的构造函数
第7章介绍了继承。每个类都有一个或多个超类,并从它们那里继承行为。对类B的实例调用 方法(或访问其属性)时,如果找不到该方法(或属性),将在其超类A中查找。请看下面两个类:
class A:
def hello(self):
print("Hello, I'm A.") class B(A):
pass
类A定义了一个名为hello的方法,并被类B继承。下面的示例演示了这些类是如何工作的:
>>> a = A()
>>> b = B()
>>> a.hello() Hello, I'm A.
>>> b.hello() Hello, I'm A.
由于类B自己没有定义方法hello,因此对其调用方法hello时,打印的是消息"Hello, I'm A."。 要在子类中添加功能,一种基本方式是添加方法。然而,你可能想重写超类的某些方法,以 定制继承而来的行为。例如,B可以重写方法hello,如下述修改后的类B定义所示:
class B(A):
def hello(self):
print("Hello, I'm B.")
这样修改定义后,b.hello()的结果将不同。
>>> b = B()
>>> b.hello() Hello, I'm B.
重写是继承机制的一个重要方面,对构造函数来说尤其重要。构造函数用于初始化新建对象 的状态,而对大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。虽然所有 方法的重写机制都相同,但与重写普通方法相比,重写构造函数时更有可能遇到一个特别的问题:
重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。
请看下面的Bird类:
class Bird:
def __init__(self):
self.hungry = True def eat(self):
1
if self.hungry:
print('Aaaah ...')
>>> b.eat() Aaaah ...
>>> b.eat() No, thanks!
从这个示例可知,鸟进食后就不再饥饿。下面来看子类SongBird,它新增了鸣叫功能。
class SongBird(Bird):
def __init__(self):
self.sound = 'Squawk!' def sing(self):
print(self.sound)
SongBird类使用起来与Bird类一样容易:
>>> sb = SongBird()
>>> sb.sing() Squawk!
SongBird是Bird的子类,继承了方法eat,但如果你尝试调用它,将发现一个问题。
>>> sb.eat()
Traceback (most recent call last):
File "<stdin>", line 1, in ? File "birds.py", line 6, in eat if self.hungry:
AttributeError: SongBird instance has no attribute 'hungry'
异常清楚地指出了问题出在什么地方:SongBird没有属性hungry。为何会这样呢?因为在
SongBird中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry的代码。要消除这
种错误,SongBird的构造函数必须调用其超类(Bird)的构造函数,以确保基本的初始化得以执
class SongBird(Bird):
def __init__(self):
Bird.__init__(self) self.sound = 'Squawk!' def sing(self):
print(self.sound)
在SongBird类中,只添加了一行,其中包含代码Bird.__init__(self)。先来证明这确实管用,
再解释这到底意味着什么。
>>> sb = SongBird()
>>> sb.sing() Squawk!
>>> sb.eat() Aaaah ...
>>> sb.eat() No, thanks!
这样做为何管用呢?对实例调用方法时,方法的参数self将自动关联到实例(称为关联的方 法),这样的示例你见过多个。然而,如果你通过类调用方法(如Bird.__init__),就没有实例 与其相关联。在这种情况下,你可随便设置参数self。这样的方法称为未关联的。这就对本节的 标题做出了解释。
通过将这个未关联方法的self参数设置为当前实例,将使用超类的构造函数来初始化 SongBird对象。这意味着将设置其属性hungry。
9.2.3 使用函数 super
如果你使用的不是旧版Python,就应使用函数super。这个函数只适用于新式类,而你无论 如何都应使用新式类。调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方 法时,调用的将是超类(而不是当前类)的方法。因此,在SongBird的构造函数中,可不使用Bird,
而是使用super(SongBird, self)。另外,可像通常那样(也就是像调用关联的方法那样)调用方
法__init__。在Python 3中调用函数super时,可不提供任何参数(通常也应该这样做),而它将
像变魔术一样完成任务。
下面是前述示例的修订版本:
class Bird:
def __init__(self):
self.hungry = True def eat(self):
if self.hungry:
print('Aaaah ...') self.hungry = False else:
print('No, thanks!') class SongBird(Bird):
def __init__(self):
super().__init__()