文章目录
- Chap28 类代码编写细节
- Chap29 运算符重载
Chap28 类代码编写细节
class语句
class语句是Python主要的OOP工具,但与C++不同的是,Python的class并不是声明式的。迹象def一样,class语句是对象的创建者并且是一个隐含的赋值运算——执行时,它会产生类对象,并把其引用值存储在前面所使用的变量名。此外,像def一样,class语句也是真正的可执行代码。知道Python抵达并运算定义的class语句前,这个类都不存在(一般都是在其所在的模块被导入时,在这之前都不会存在)。
一般形式
class是复合语句,其缩进语句的主体一般都出现在头一行的下边。在头一行中,超类列在类名称之后的括号内,由逗号相隔。列出一个以上的超类会引起多重继承。以下为class语句的一般形式:
class<name>(superclass,...): # assign to name
data=value # shared class data
def method(self,...):
self.member=value
在class语句内,任何赋值语句都会产生类属性,而且还有特殊名称重载运算符。例如,名为__init__的函数会在实例对象构造时调用(如果定义过的话)。
例子
就像之前见过的那样,类几乎就是命名空间,也就是定义变量名(属性)的工具,把数据和逻辑导出给客户端。就像模块文件,位于class语句主体中的语句会建立其属性。当Pyhon执行class语句时(不是调用类),会从头至尾执行其主体内的所有语句。在这个过程中,进行的赋值运算会在这个类作用域中创建变量名,从而称为对应的类对象内的属性。因此,类就像函数和模块:
- 就像函数一样,class语句时本地作用域,由内嵌的赋值语句建立的变量名,就存在这个本地作用域内。
- 就像模块内的变量名,在class语句内赋值的变量名会变成类对象中的属性。
类的主要的不同之处在于其命名空间也是Python继承的基础。在类或者实例对象中找不到的所引用的属性,就会在其他类中获取。
因为class是复合语句,所以任何种类的语句都可位于其主体内:pinrt、=、if、def等。当class语句自身运行时(不是稍后调用类来创建实例的时候),class语句内的所有语句都会执行。在class语句内赋值的变量名,会创建类属性,而内嵌的def则会创建类方法,但是,其他赋值语句也可制作属性。
class SharedData:
spam=42 # Generates a class data attribute
x=SharedData() # Make two instances
y=SharedData()
x.spam,y.spam # They inherit and share 'spam'
(42, 42)
如上,因为变量名是在class语句的顶层进行赋值的,因此会附加到类中,从而为所有的实例共享。可以通过类名称来修改它,也可以通过实例或列引用来修改。
SharedData.spam=99
x.spam,y.spam,SharedData.spam
(99, 99, 99)
这种类属性可以用于管理贯穿所有实例的信息。例如,所产生的实例的数目的计数器(31章会扩展这一概念)。现在,如果通过实例而不是类来给变量名spam赋值,看看会发生什么:
x.spam=88
x.spam,y.spam,SharedData.spam
(88, 99, 99)
对实例的属性进行赋值运算会在该实例内创建或修改变量名,而不是在共享的类中。通常情况下,继承搜索只会在属性引用时发生,而不是在赋值运算时发生:对对象属性进行赋值总是会修改该对象,除此之外没有其他影响。例如,y.spam会通过继承而在类中查找,但是,对x.spam进行赋值运算则会把该变量名附加在x本身上。
下面的这个例子,可以更容易地理解这种行为,把相同的变量名储存在两个位置。假设执行下列类:
class MixedNames: # Define class
data='spam' # Assign class attr
def __init__(self,value): # Assign method name
self.data=value # Assign instance attr
def display(self):
print(self.data,MixedNames.data) # Instance attr,class arrt
这个类有两个def,把类属性和方法函数绑定在一起。此外,也包含一个=赋值语句。因为赋值语句是在类中赋值变量名data,该变量名会在这个类的作用域内存在,变成类对象的属性。就像所有类属性,这个data会被继承,从而被所有没有自己的data属性的类的实例所共享。
当创建这个类的实例的时候,变量名data会在构造函数方法内对self.data进行赋值运算,从而把data附加在这些实例上。
当创建这个类的实例的时候,变量名data会在构造函数方法内对self.data进行赋值运算,从而把data附加在这些实例上。
x=MixedNames(1)
y=MixedNames(2)
x.display();y.display()
1 spam
2 spam
结果就是,data存在于两个地方:在实例对象内(由__init__中的slef.data赋值运算所创建)以及在实例继承变量名的类中(由类中的data赋值运算所创建)。类的display方法打印了这两个版本,先以点号运算得到self实例的属性,然后才是类。
利用这些技术把属性储存在不同对象内,可以决定其可见范围。附加在类上时,变量名是共享的;附加在实例上时,变量名是属于每个实例的数据而不是共享的行为或数据。虽然继承搜索会查找变量名,但总是可以通过直接读取所需要的对象,而获得树中任何地方的属性。
方法
如果了解了函数,就了解了类中的方法。方法位于class语句的主体内,是由def语句建立的函数对象。从抽象的视角来看,方法替实例对象提供了要继承的行为。从程序设计的角度来看,方法的工作方式与简单函数完全一致,只是有个重要差异:方法的第一个参数总是接受方法调用的隐性主体,也就是实例对象。
换句话说,Python自动把实例方法的调用对应到类方法函数,如下所示。方法调用需要通过实例,就像这样:
instance.method(args...)
这会自动翻译成以下形式的类方法函数调用:
class.method(instance,args...)
class通过Python继承搜索流程找出方法名称所在之处。事实上,两种调用形式在Python中都有效。
除了方法属性名称是正常的继承外,第一个参数就是方法调用背后唯一的神奇之处。在类方法中,按惯例第一个参数通常称为self(严格地说,只有其位置重要,而不是其名称)。这个参数给方法提供了一个钩子,从而返回调用的主体,也就是实例对象:因为类可以产生许多实例对象,所以需要这个参数来惯例每个实例彼此各不相同的数据。
C++(JAVA也一样)程序员会发现,Pthon的self参数与C++的this指针很像。不过,Python中,self一定要在程序代码中明确地写出:方法一定要通过self来取出或修改由当前方法调用或正在处理的实例的属性。这种让self明确和的本质是有意设计的:这个变量名存在,会让你明确脚本中使用的实例属性名称,而不是本地作用域或全局作用域中的变量名。
例子
假设定义了下面的类:
class NextClass: # Define class
def printer(self,text):
self.message=text
print(self.message)
变量名printer引用了一个函数对象。因为这是在class语句的作用域中赋值的,就会变成类对象的属性,被由这个类创建的每个实例所继承。通常,因为像printer这类方法都是设计成处理实例的,所以得通过实例予以调用。
x=NextClass()
x.printer('instance call')
instance call
x.message
'instance call'
当通过对实例进行点号运算调用它时,printer会先通过继承将其定为,然后它的self参数会自动赋值为实例对象(x)。text参数会获得在调用时传入的字符串(‘instance call’)。注意:i那位Python会自动传递第一个参数给self,实际上只需传递一个参数。在printer中,变量名self是用于读取或设置每个实例的数据的,因为self引用的是当前正在处理的实例。
方法能通过实例或类本身两种方法其中的任意一种进行调用。例如,可以通过类名称调用printer,之傲明确地传递了一个实例给self参数。
NextClass.printer(x,'class call') # direct class call
class call
x.message # and instance changed again
'class call'
通过实例和类的调用具有相同的效果,只要在类实例中传递了相同的实例对象。实际上,在默认的情况下,如果尝试不带任何实例调用的方法时,就会得出错信息。
NextClass.printer('bad call')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-628a808881c2> in <module>()
----> 1 NextClass.printer('bad call')
TypeError: printer() missing 1 required positional argument: 'text'
调用超类构造方法
方法一般是通过实例调用的。不过,通过类调用方法也扮演了一些特殊的角色。常见的场景涉及了构造方法。就像所有属性__init__方法是由继承进行查找的。也就是说,在构造时,Python会找出并且只调用一个__init__。如果要保证子类的构造方法也会执行超类构造时的逻辑,一般必须通过类明确地调用超类的__init__方法。
class Super:
def __init__(self,x):
...default code...
class Sub(Super):
def __init__(self,x,y):
Super.__init__(self,x)
...default code...
I=Sub(1,2)
这是代码有可能直接调用运算符重载方法的环境之一。如果真的想运行超类的构造方法,自然只能用这种方法进行调用:没有这样的调用,子类会完全取代超类的构造方法。这门技术在实际中更显示的介绍,可以参加本章最后的例子。
其他方法调用的可能性
这种通过类调用方法的模式,是扩展继承方法行为(而不是完全取代)的一般基础。在第31章中,将会遇到Python2.2时新增的选项:静态方法,可以让程序员编写不预期第一参数为实例对象的方法。这类方法可像简单的无实例的函数那样运作,其变量名属于其所在类的作用域,并且可以用来管理类数据。一个相关的概念,类方法,当调用的时候接受一个类而不是一个实例,并且它可以用来管理基于每个类的数据。不过,这是高级的选用扩展功能。通常来说,一定要为方法传入实例,无论通过实例还是类调用都行。
继承
像class语句这样的命名空间工具的重点就是支持变量名继承。本节扩展了Python中关于属性继承的一些机制和角色。
在Python中,当对象进行点号运算时,就发生了继承,而且设计了搜索属性定义树(一个或多个命名空间)。每次使用object.attr形式的表达式时(object是实例或类对象),Python会从头至尾搜索命名空间,先从对象开始,寻找所能找到的第一个attr。这包括在方法中对self属性的引用。因为树种较低的定义会覆盖较高的定义,继承构成了专有化的基础。
属性树的构造
通常来说:
- 实例属性是由对方法内self属性进行赋值预算而生成的。
- 类属性是通过class语句内的语句(赋值语句)而生成的。
- 超类的连接是通过class语句首行的括号内列出类而生成的。
继承方法的专有化
刚才谈到了继承树的搜索模式,变成了将系统专有化的最好方式。因为继承会现在子类寻找变量名,然后才查找超类,子类就可以对超类的属性重新定义来取代默认的行为。实际上,可以把整个系统做成类的层次,再新增外部的子类来对其进行扩展,而不是再原处修改已经存在的逻辑。
重新定义继承变量名的该奶奶引出了各种专有化技术。例如,子类可以完全取代继承的属性,提供超类可以找到的属性,并且通过已覆盖的方法回调超类来扩展超类的方法。再之前已经看到过实际中取代的做法。下面是如何进行扩展的例子:
class Super:
def method(self):
print('in Super.method')
class Sub(Super):
def method(self): # Override method
print('starting Sub.method') # Add actions here
Super.method(self) # Run default action
print('ending Sub.method')
x=Sub()
x.method()
starting Sub.method
in Super.method
ending Sub.method
直接调用超类方法是这里的重点。Sub类以其专有化的版本取代了Super的方法函数。但是,取代时,Sub又回调了Super所导出的版本,从而实现了其默认的行为。换句话说,Sub.method只是扩展了Super.method的行为,而不是完全取代它。这种扩展编码模式常常用于构造函数。例如,参考本章之前的“方法”一节。
类接口技术
扩展知识一种与超类接口的方式。下面展示的specialize.py文件定义了多个类,示范了一些常用的技巧。
- Super:定义一个method函数以及在子类中期待一个动作的delegate。
- Inheritor:没有提供任何新的变量名,因此会获得Super中定义的一切内容。
- Replacer:用自己的版本覆盖Super的method。
- Extender:覆盖并回调默认的method,从而定制Super的method。
- Provider:实现Super的delegate方法预期的action方法。
class Super:
def method(self):
print('in Super.method')
def delegate(self):
self.action() # Expected to be defined
class Inheritor(Super):
pass
class Replacer(Super):
def method(self):
print('in Replacer.method')
class Extender(Super):
def method(self):
print('starting Extender.method')
Super.method(self)
print('ending Extender.method')
class Provider(Super):
def action(self):
print('in Provider.action')
if __name__=='__main__':
for klass in (Inheritor,Replacer,Extender):
print('\n'+klass.__name__+'...')
klass().method()
print('\nProvider...')
x=Provider()
x.action()
Inheritor...
in Super.method
Provider...
in Provider.action
Replacer...
in Replacer.method
Provider...
in Provider.action
Extender...
starting Extender.method
in Super.method
ending Extender.method
Provider...
in Provider.action
抽象超类
注意上一个例子中的Provider类是如何工作的。当通过Provider实例调用delegate方法时,有两个独立的继承搜索会发生:
- 在最初x.delegate的调用中,Python会搜索Provider实例和它上层的对象,知道在Super中找到delegate的方法。实例x会像往常一样传递给这个方法的self参数。
- 在Super.delegate方法中,selef.acion()会对self以及它上层的对象启动新的独立继承搜索。因为self指的是Provider实例,在Provider子类中就会找到action方法。
这种“填空”的代码结构一般就是OOP的软件框架。至少,从delegate方法的角度来看,这个例子中的超类有时也称作是抽象超类——也就是类的部分行为默认是由其子类所提供的。如果预期的方法没有在子类中定义,当继承搜索失败时,Python会引发未定义变量名的异常。
类的编写者偶尔会使用assert语句,使这种子类需求更为明显,或者引发内置的异常NotImplementError(将在下一部分深入学习可能触发异常的语句时深入讲解)。作为提前介绍,下面是assert方法的实际应用示例:
class Super:
def delegate(self):
self.action()
def action(self):
assert False,'action must be defined!' # if this version is called
X=Super()
X.delegate()
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-20-c3402a1669b5> in <module>()
6
7 X=Super()
----> 8 X.delegate()
<ipython-input-20-c3402a1669b5> in delegate(self)
1 class Super:
2 def delegate(self):
----> 3 self.action()
4 def action(self):
5 assert False,'action must be defined!' # if this version is called
<ipython-input-20-c3402a1669b5> in action(self)
3 self.action()
4 def action(self):
----> 5 assert False,'action must be defined!' # if this version is called
6
7 X=Super()
AssertionError: action must be defined!
将在32和33章介绍assert。简而言之,如果其表达式运算结构为假,就会引发带有出错信息的异常。在这里,表达式总是为假(0).因此,如果没有方法重新定义,继承就会找到这个版本,触发出错信息。此外,有些类只在该类的不完整方法中直接产生NotImplemented异常。
class Super:
def delegate(self):
self.action()
def action(self):
raise NotImplementedError('action must be defined!')
X=Super()
X.delegate()
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-23-59f783ee492e> in <module>()
6
7 X=Super()
----> 8 X.delegate()
<ipython-input-23-59f783ee492e> in delegate(self)
1 class Super:
2 def delegate(self):
----> 3 self.action()
4 def action(self):
5 raise NotImplementedError('action must be defined!')
<ipython-input-23-59f783ee492e> in action(self)
3 self.action()
4 def action(self):
----> 5 raise NotImplementedError('action must be defined!')
6
7 X=Super()
NotImplementedError: action must be defined!
对于子类,除非提供了期待的方法来代替超类中默认的方法,否则将会得到异常。
class SubWrong(Super):pass
Y=SubWrong()
Y.delegate()
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-25-845759ebdd3e> in <module>()
2
3 Y=SubWrong()
----> 4 Y.delegate()
<ipython-input-23-59f783ee492e> in delegate(self)
1 class Super:
2 def delegate(self):
----> 3 self.action()
4 def action(self):
5 raise NotImplementedError('action must be defined!')
<ipython-input-23-59f783ee492e> in action(self)
3 self.action()
4 def action(self):
----> 5 raise NotImplementedError('action must be defined!')
6
7 X=Super()
NotImplementedError: action must be defined!
class SubRight(Super):
def action(self):
print('spam')
Y=SubRight()
Y.delegate()
spam
如果要参考这一节中概念的更实际的例子,可以参考第31章结尾的习题8以及第六部分中的解答(在附录B)
Python2.6和Python3.0中的抽象超类
在Python2.6和Python3.0中,前一小节的抽象超类(即“抽象基类”),需要由子类填充的方法,它们也可以以特殊的类语法来实现。我们编写代码的这种方法根据版本不同而有所变化。在Python3.0中,可以在class头部使用一个关键字参数,以及特殊的@装饰器语法,这二者都将在稍后更详细地进行学习。
from abc import ABCMeta,abstractmethod
class Super(metaclass=ABCMeta):
@abstractmethod
def method(self,...):
pass
但是在Python2.6中,则使用一个类属性:
class Super:
__metaclass__=ABCMeta
@abstractmethod
def met