class语句
就像def一样,class语句是对象的创建者并且是一个隐含的赋值运算:当他执行时会产生类对象,并把其引用值储存到名称中。此外像def一样,class语句也是可执行代码。直到python抵达并运行定义的class语句前,你的类是不存在的。
一般形式
class是复合语句,其缩进语句的主体一般都出现在头部行下边。在头部行中,父类列在类名称之后的口号内,由逗号相隔。列出一个以上的父类会触发多继承。下面是class语句的一般形式:
class name(superclass,....):
attr = value
def method(self,....)
self.attr = value
示例
类既像模块也像函数:
- 就想函数一样,由class语句中内嵌的赋值语句创建的名称,位于class的局部作用域中;
- 就像模块内的名称,在class语句中赋值的名称会成为类对象中的属性;
class ShareData():
spam = 42
x = ShareData()
y = ShareData()
print(x.spam, y.spam) # output:(42, 42)
在这里,名称spam是在class语句的顶层被赋值,因此会被附加在这个类中,从而为所有的实例共享。我们可以通过类名修改它:
ShareData.spam = 99
print(x.spam, y.spam) # output:(99, 99)
所以像这种类属性可以用于管理横跨所有实例的信息,同时通过实例的修改的是实例中的变量:
x.spam = 88
print(x.spam, y.spam) # output:(88, 99)
命名空间的先后顺序是:实例、子类、父类…
方法
从程序设计的角度看,方法和函数的工作方式是一样的,只是有一个重要的差异,:方法的第一位参数总是接受方法调用的隐含主体,也就是当前的实例对象;
instance.method(args....)
上述方法调用会被自动翻译为:
instance.method(instance, args....)
下面的代码为了说明方法定义时的self时什么:
>>> class Aclass:
... def printer(self, text):
... self.text = text
... print(self)
... print(self.text)
...
>>> x = Aclass()
>>> x.printer('abc')
<__main__.Aclass object at 0x10b6bc290>
abc
>>> print(x)
<__main__.Aclass object at 0x10b6bc290>
调用父类构造函数
class super():
def __init__(self, x):
...default code...
class sub(super):
def __init__(self, x, y)
super.__init__(self, x)
...custom code...
继承
属性数的构造
通常来说:
- 实例属性是由对方法内的self属性进行赋值运算而产生的;
- 类属性是通过class语句内的语句(赋值语句)而创建的;
- 父类的连续是通过class语句首行的括号内列出的类而产生的;
定制被继承的方式
class Super:
def method(self):
print('in super.method')
class Sub(Super):
def method(self):
print('starting Sub.method')
Super.method(self)
print('ending Sub.method')
直接调用父类方法是这里的关键,Sub类用其定制化版本替代了Super中的方法函数。但在替代时,Sub又调用了Super所提供的版本,从而完成了默认的行为。换句话说,Sub。method只是扩展了Super.method的行为,而不是完全替代了它;
类接口技术
class Super():
def method(self):
print('in Super.method')
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('syarting Extender.method')
Super.method(self)
print('ending Extender.method')
class Provider(Spuer):
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()
output:
Inheritor...
in Super.method
Replacer...
in Replacer.method
Extender...
syarting Extender.method
in Super.method
ending Extender.method
Provider
in Provider.action
抽象父类
上面的代码中,有一点是需要额外理解的。当我们通过Provider实例调用delegate方法时,会发生两次独立的继承搜索:
- 在x.delegate调用一开始,python会搜索Provider实例和类树中更上一层的类对象,直到Super中找到delegate的方法。实例x会照常传给该方法的self参数;
- 在 Super. delegate方法中,self.action会对self以及它上层的对象发起另一次新的独立继承搜索。因为self引用了一个 Provider实例,所以 action方法会在Provider子类中找到;
这种‘填补空缺’的代码结构正是典型的OOP的软件框架;
类的编写者有时使用assert语句,使这种子类要求更为明显,或者引发内置的NotImplementedError异常,这个将在后续学习;
命名空间:结论
- 无点号运算的名称对应于作用域;
- 掉点号的属性名使用的是对象的命名空间;
- 有些作用域用于初始化对象的命名空间;
简单名称:如果被赋值就不是全局的
无点号的简单名称遵循LEGB词法作用域规则:
- 赋值语句(X = value):默认情况下名称成为局部的
- 引用(X):按照LEGB规则;