5-4-类代码编写细节
1. class 语句
Python 的 class 并不是声明式的,class 语句是对象的创建者并且是一个隐含的赋值运算。
1.1 一般形式
class 是复合语句。在头一行中,超类列在类名称之后的括号内,由逗号相隔。列出一个以上的超类会引起多重继承:
class name(superclass, ...): # 赋值给变量名
data = value
def method(self, ...):
self.member = value
1.2 例子
当 Python 执行 class 语句时(不是调用类),会从头至尾执行其主体内的所有语句。在这个过程中,进行的赋值运算会在这个类作用域中创建变量名,从而成为对应的类对象内的属性。类就像模块和函数:
- 就像函数一样,class 语句是本地作用域,由内嵌的赋值语句建立的变量名,就存在于这个本地作用域内。
- 就像模块内的变量名,在 class 语句内赋值的变量名会变成类对象中的属性。
在 class 语句内赋值的变量名,会创建类属性,而内嵌的 def 则会创建类方法,但是,其他赋值语句也可制作属性。
例如,把简单的非函数的对象赋值给类属性,就会产生数据属性,由所有实例共享:
class SharedData:
spam = 42
x = SharedData()
y = SharedData()
x.spam, y.spam
(42, 42)
SharedData.spam = 99
x.spam, y.spam, SharedData.spam
(99, 99, 99)
x.spam = 88
x.spam, y.spam, SharedData.spam
(88, 99, 99)
对实例的属性进行赋值运算会在该实例内创建或修改变量名,而不是在共享的类中。
class MixedNames:
data = 'spam' # 赋值类属性
def __init__(self, value):
self.data = value # 赋值实例属性
def display(self):
print(self.data, MixedNames.data)
x = MixedNames(1)
y = MixedNames(2)
x.display(); y.display() # self.data 不同,MixedNames.data 相同
1 spam
2 spam
2. 方法
方法位于 class 语句的主体内,是由 def 语句建立的函数对象。方法的第一个参数总是接收方法调用的隐形主体,也就是实例对象。
在类方法中,按惯例第一个参数通常都称为 self。这个参数给方法提供了一个 hook,从而返回调用的主体,也就是实例对象。
2.1 例子
class NextClass:
def printer(self, text):
self.message = text
print(self.message)
x = NextClass()
x.printer('instance call')
x.message
instance call
'instance call'
2.2 调用超类构造函数
在构造时,Python 会找出并且只调用一个 __init__
。如果要保证子类的构造函数也会执行超类构造时的逻辑,一般都必须通过类明确地调用超类的 __init__
方法:
class Super:
def __init__(self, x):
pass
class Sub(Super):
def __init__(self, x, y):
Super.__init__(self, x) # 运行超类的 __init__
pass # 运行自己的 __init__
I = Sub(1, 2)
3. 继承
当对对象进行点号运算时,就会发生继承,而且涉及了搜索属性定义树(一个或多个命名空间)。
3.1 属性树的构造
- 实例属性是由对方法内 self 属性进行赋值运算而生成的。
- 类属性是通过 class 语句内的语句(赋值语句)而生成的。
- 超类的连接是通过 class 语句首行的括号内列出类而生成的。
3.2 继承方法的专有化
继承会现在子类寻找变量名,然后才查找超类,子类就可以对超类的属性重新定义来取代默认的行为。
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')
x = Super()
x.method()
in Super.method
x = Sub()
x. method()
starting Sub.method
in Super.method
ending Sub.method
3.3 抽象超类
类的部分行为默认是由其子类所提供的。如果预期的方法没有在子类中定义,当继承搜索失败时,Python 会引发未定义变量名的异常。
4. 命名空间:完整的内容
- 无点号运算的变量名与作用域相对应。
- 点号的属性名使用的是对象的命名空间。
- 有些作用域会对对象的命名空间进行初始化(模块和类)。
4.1 简单变量名:如果赋值就不是全局变量
无点号的简单变量名遵循 LEGB 作用域法则:
- **赋值语句(X = value)。**使变量名成为本地变量:在当前作用域内,创建或改变变量名 X,除非声明它是全局变量。
- **引用(X)。**在当前作用域内搜索变量名 X,之后是在任何以及所有的嵌套的函数中,然后是在当前的全局作用域中搜索,最后在内置作用域中搜索。
4.2 属性名称:对象命名空间
- **赋值语句(object.X = value)。**在进行点号运算的对象的命名空间内创建或修改属性名 X。继承树的搜索只发生在属性引用时,而不是属性的赋值运算时。
- **引用(object.X)。**就基于类的对象而言,会在对象内搜索属性名 X,然后是其上所有可读取的类。对于不是基于类的对象而言,则是从对象中直接读取 X。
4.3 嵌套类
类有时在函数中进行嵌套,并生成,这是闭包的一种变体。
X = 1
def nester():
print(X) # 全局:1
class C:
print(X) # 全局:1
def method1(self):
print(X) # 全局:1
def method2(self):
X = 3
print(X) # 本地:3
I = C()
I.method1()
I.method2()
print(X) # 全局:1
nester()
print('-' * 40)
1
1
1
1
3
----------------------------------------
X = 1
def nester():
X = 2 # 隐藏全局变量
print(X) # 本地:2
class C:
print(X) # 嵌套 def 中:2
def method1(self):
print(X) # 嵌套 def 中:2
def method2(self):
X = 3 # 隐藏嵌套变量
print(X) # 本地:3
I = C()
I.method1()
I.method2()
print(X) # 全局:1
nester()
print('-' * 40)
1
2
2
2
3
----------------------------------------
X = 1
def nester():
X = 2 # 隐藏全局变量
print(X) # 本地:2
class C:
X = 3 # class 本地变量隐藏 nester 的 C.X 或 I.X
print(X) # 本地:3
def method1(self):
print(X) # 嵌套 def 中:2
print(self.X) # 继承的类本地变量:3
def method2(self):
X = 4 # 隐藏嵌套中的变量(nester,不是 class)
print(X) # 本地:4
self.X = 5 # 隐藏类中的变量
print(self.X) # 位于实例中:5
I = C()
I.method1()
I.method2()
print(X)
nester()
print('-' * 40)
1
2
3
2
3
4
5
----------------------------------------
4.4 命名空间字典
属性点号运算其实内部就是字典的索引运算,而属性继承其实就是搜索链接的字典。
class Super:
def hello(self):
self.data1 = 'spam'
class Sub(Super):
def hola(self):
self.data2 = 'eggs'
X = Sub()
X.__dict__ # 实例的命名空间字典
{}
X.__class__ # 实例的类
__main__.Sub
Sub.__bases__ # 类的超类
(__main__.Super,)
Super.__bases__
(object,)
属性最后会位于实例的属性命名空间字典内,而不是类。
Y = Sub()
X.hello()
X.__dict__
{'data1': 'spam'}
X.hola()
X.__dict__
{'data1': 'spam', 'data2': 'eggs'}
Sub.__dict__.keys()
dict_keys(['__module__', 'hola', '__doc__'])
Super.__dict__.keys()
dict_keys(['__module__', 'hello', '__dict__', '__weakref__', '__doc__'])
Y.__dict__ # Y 还是空的命名空间字典
{}
dir(X) # dir(object) 类似于 object.__dict__.keys() 调用,不过会引入一些系统属性
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'data1',
'data2',
'hello',
'hola']
4.5 命名空间链接:树爬升
可以用实例和类的特殊属性 __class__
和 __bases__
来显示类树:
# classtree.py 文件
"""
Climb inheritance trees using namespace links,
displaying higher superclasses with indentation for height
"""
def classtree(cls, indent):
print('.' * indent + cls.__name__) # 打印类名
for supercls in cls.__bases__: # 递归所有超类
classtree(supercls, indent + 3)
def instancetree(inst):
print('Tree of %s' % inst)
classtree(inst.__class__, 3)
def selftest():
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E: pass
class F(D, E): pass
instancetree(B())
instancetree(F())
if __name__ == '__main__': selftest()
Tree of <__main__.selftest.<locals>.B object at 0x0000022E079E8BA8>
...B
......A
.........object
Tree of <__main__.selftest.<locals>.F object at 0x0000022E079E8BA8>
...F
......D
.........B
............A
...............object
.........C
............A
...............object
......E
.........object
5. 类与模块的关系
- 模块
- 是数据/逻辑包。
- 通过编写 Python 文件或 C 扩展来创建。
- 通过导入来使用。
- 类
- 实现新的对象。
- 由 class 语句创建。
- 通过调用来使用。
- 总是位于一个模块中。