继续学习类、方法和继承。
================================================================================
class语句
以下是class语句的一般形式:
class <name>(superclass,...): data = value def method(self,...): self.member = value在class语句内,任何赋值语句都会产生类属性,而且还有特殊名称方法重载运算符。例如,名为__init__的函数会在实例对象构造时调用(如果定义过的话)。
例子
类是命名空间,也就是定义变量名(属性)的工具。
1.就像函数一样,class语句是本地作用域,由内嵌的赋值语句建立的变量名,就存在于这个本地作用域内。
2.就像模块内的变量名,在class语句内赋值的变量名会变成类对象中的属性。
因为class是复合语句,所以任何种类的语句都可位于其主体内:print、=、if、def等。当class语句自身执行时,class语句内的所有语句都会执行。在class语句内赋值的变量名,会创建类属性,而内嵌的def则会创建类方法。
例如,把简单的非函数的对象赋值给类属性,就会产生数据属性,由所有实例共享。
>>> class ShareData: spam = 42 >>> x = ShareData() >>> y = ShareData() >>> x.spam,y.spam (42, 42)在这里,因为变量名spam是在class语句的顶层进行赋值的,因此会附加在这个类中,从而为所有的实例共享。我们可通过类名称修改它,或者是通过实例或类引用它。
>>> ShareData.spam = 99 >>> x.spam,y.spam,ShareData.spam (99, 99, 99)这种类属性可以用于管理贯穿所有实例的信息。例如,所产生的实例的数目的计数器。
>>> x.spam = 88 >>> x.spam,y.spam,ShareData.spam (88, 99, 99)对实例的属性进行赋值运算会在该实例内创建或修改变量名,而不是在共享的类中。
看下面这个例子,可以更容易理解这种行为,把相同的变量名储存在两个位置:
>>> class MixedNames: data = 'spam' def __init__(self,value): self.data = value def display(self): print(self.data,MixedNames.data)当创建这个类的实例的时候,变量名data会在构造函数方法内对self.data进行赋值运算,从而把data附加到这些实例上。
>>> x = MixedNames(1) >>> y = MixedNames(2) >>> x.display(),y.display() 1 spam 2 spam (None, None)【这里的(None,None)是调用display函数的返回值】
方法
方法即函数。方法在class中是由def语句创建的函数对象。从抽象的角度来看,方法替实例对象提供了要继承的行为。从程序的角度看,方法与简单函数的工作方式完全一致,只是有一个重要的差别:方法的第一个参数总是接收方法调用的隐性主体,也就是实例对象。
Python会自动把实例方法的调用对应到类方法函数。如下所示,方法调用需要通过实例,就像这样:
instance.method(args...)这会自动翻译成以下形式的类方法函数调用:
class.method(instance,args...)class通过Python继承搜索流程找出方法名称所在之处。事实上,两种调用形式在Python中都有效。
例子
定义下面这个类:
>>> class NextClass: def printer(self,text): self.message = text print(self.message)我们通过实例调用printer方法如下:
>>> x = NextClass() >>> x.printer('instance call') instance call >>> x.message 'instance call'当通过实例进行点号运算调用它时,printer会先通过继承将其定位,然后它的self参数会自动赋值为实例对象(x)。text参数会获得在调用时传入的字符串('instance call')。注意:因为Python会自动传递第一个参数给self,实际上只需要传递一个参数。在printer中,变量名self是用于读取或设置每个实例的数据的,因为self引用的是当前正在处理的实例。
>>> NextClass.printer(x,'class call')#Direct Class Call class call >>> x.message 'class call'通过实例和类的调用具有相同的效果,只要在类形式中传递了相同的实例对象。实际上,在默认情况下,如果尝试不带任何实例调用的方法时,就会得到出错信息。
>>> NextClass.printer('bad call') Traceback (most recent call last): File "<pyshell#35>", line 1, in <module> NextClass.printer('bad call') TypeError: printer() missing 1 required positional argument: 'text'================================================================================
调用超类构造函数
在构造时,Python会找出并且只调用一个__init__。如果保证子类的构造函数也会执行超类构造时的逻辑,一般都必须通过类明确地调用超类的__init__方法。
class Super: def __init__(self,x): ...default code... class Sub(Super): def __init__(self,x,y): Super.__init__(self,x) ...custom code... I = Sub(1,2)这种写法便于维护代码,之前也介绍过。这种方法扩展了超类的方法,而不是完全取代了它。
类接口技术
扩展只是一种与超类接口的方法。下面所示的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.methon') def delegate(self): self.action() 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.delegate()执行结果如下:
Inheritor... in Super.methon Replacer... in Replacer.method Extender... starting Extender.method in Super.methon ending Extender.method Provider... in Provider.action================================================================================