7.1 名字空间
类型是类型,实例是实例。如同 def,关键字 class 的作用是创建类型对象,类型对象很特殊,在整个进程中是单例的,是不被回收的。
>>> class User(object): pass
...
>>> u = User()
>>> type(u)
<class '__main__.User'>
>>> u.__class__
<class '__main__.User'>
类型和实例拥有自己的名字空间
>>> User.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None, '__module__': '__main__'})
>>> u.__dict__
{}
访问对象成员时,就从他们的名字空间查找,而非globals, locals
成员查找顺序:instance.__dict__ -> class.__dict__ -> baseclass.__dict__
7.2 字段
字段(Field)和属性(Property) 是不同的。
- 实例字段存储在 instance.__dict__, 代表单个对象实体的状态。
- 静态字段存储在class.__dict__,为所有同类型实例共享
- 必须通过类型和实例对象才能访问字段。
- 以双下划线开头的class和instance成员视为私有,会被重命名。
>>> class User(object):
... table = 't_user'
... def __init__(self, name, age):
... self.name = name
... self.age = age
>>> u1 = User('user1',20)
>>> u1.__dict__
{'name': 'user1', 'age': 20}
>>> u2 = User('user2', 30)
>>> u2.__dict__
{'name': 'user2', 'age': 30}
>>> User.__dict__
'__dict__': <attribute '__dict__' of 'User' objects>, 'table': 't_user'
访问静态字段,出来class.name,也可以使用instance.name, 按照查找规则先查找instance.__dict__, 如果没有就继续查找 class.__dict__
私有字段以双下划线开头,无论是静态还是实例成员,都被重命名:_<class>__<name>
>>> class User(object):
... __table = 't_user'
... def __init__(self, name, age):
... self.name = name
... self.__age = age
... def __str__(self):
... return '{0}: {1},{2}'.format(self.__table, self.name, self.__age)
...
>>> u = User('tom', 20)
>>> u.__dict__
{'name': 'tom', '_User__age': 20}
>>> User.__dict__
mappingproxy({'_User__table': 't_user',})
有些时候即想使用私有字段,又想使用外部访问权限
- 用重命名的格式访问_class__name
- 只用一个下划线,仅提醒,不重命名
7.3 属性
属性(Property) 是 由getter,setter,deleter 几个方法构成的逻辑,属性可能直接返回字段值,也可能是动态逻辑运算的结果。
属性以装饰器或描述符实现
class User(object):
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
self.__name = value
@name.deleter
def name(self):
del self.__name
u = User()
u.name = 'TOM'
print(u.__dict__)
print(User.__dict__)
# {'_User__name': 'TOM'}
# { 'name': <property object at 0x7f2b85f7c368>,}
从class.__dict__可以看出,几个属性方法最终变成了property object。所以几个同名方法也没有报错
直接使用property() 实现属性。
class User(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self, value):
self.__name = value
def del_name(self):
del self.__name
name = property(get_name, set_name, del_name)
u = User('Tom')
u.__dict__['name'] = 'user' # 显式在 instance.__dict__创建同名实例字段
print(u.name) # Tom
print(u.__dict__)
print(User.__dict__)
# {'name': 'user', '_User__name': 'Tom'}
# { 'set_name': <function User.set_name at 0x7f32d1efc730>,'get_name': <function User.get_name at 0x7f32d1efc6a8>, 'name': <property object at 0x7f32d365d368>, 'del_name': <function User.del_name at 0x7f32d1efc7b8>}
区别不大,但是User.__dict__保留了 set get del 方法
属性总是比实例的同名字段优先
在instance.__dict__ 显式的设置了同名实例字段,访问 u.name 时依然是属性。
7.4 方法
实例方法和函数最大的区别就是self这个隐式参数
>>> class User(object):
... def print_id(self):
... print(hex(id(self)))
...
>>> u = User()
>>> u.print_id
<bound method User.print_id of <__main__.User object at 0x7f6124960dd8>>
>>> User.print_id
<function User.print_id at 0x7f6124967c80>
>>> u.print_id()
0x7f6124960dd8
>>> User.print_id(u)
0x7f6124960dd8
当实例调用时, 他是一个bound method,动态绑定到对象实例。当类型调用时,是一个function,必须显式的传递self参数
在方法里访问对象成员时,必须使用对象实例引用。否则被当成普通名字,按照LEGB查找。
因为所有的方法都存储在class.__dict__中,不可能出现同名key,所以方法不能重载
7.5 继承
基类所有的实例字段都存储在instance.__dict__
class User(object):
table = 't_user'
def __init__(self, name, age):
self._name = name
self._age = age
def test(self):
print(self._name, self._age)
class Manager(User):
table = 't_manage'
def __init__(self, name, age, title):
User.__init__(self, name, age)
self._title = title
def kill(self):
print('213...')
>>> m = Manager('TOM',34, 'XCD')
>>> m.__dict__
{'_name': 'TOM', '_age': 34, '_title': 'XCD'} # 实例包含了所有基类的字段。
>>> Manager.__dict__ # 派生类名字空间里没有任何基类成员
{ 'kill': <function Manager.kill at 0x7f681aec3598>, 'table': 't_manage'}
>>> User.__dict__
{ 'test': <function User.test at 0x7f681aec3488>, 'table': 't_user'}
基类引用常常在__base__, 直接派生类存储在__subclasses__
>>> Manager.__base__
<class 'testdemo.User'>
>>> User.__subclasses__()
[<class 'testdemo.Manager'>]
多重继承
class A(object):
def __init__(self, a):
self._a = a
class B(object):
def __init__(self, b):
self._b = b
class C(A, B):
def __init__(self, a, b):
A.__init__(self, a)
B.__init__(self, b)
>>> C.__bases__
(<class 'testdemo.A'>, <class 'testdemo.B'>)
>>> c = C(1,2)
>>> c.__dict__
{'_a': 1, '_b': 2} # 实例名字空间包含了所有基类实例字段
多继承成员搜索mro(method resolution order):从下到上(深度优先,从派生类到基类),从左到右
mro 和我们前面提及的成员查找规则是有区别的,__mro__ 列表中并没有 instance。所以在表述时,需要注意区别
>>> C.mro()
[<class 'testdemo.C'>, <class 'testdemo.A'>, <class 'testdemo.B'>, <class 'object'>]
>>> C.__mro__
(<class 'testdemo.C'>, <class 'testdemo.A'>, <class 'testdemo.B'>, <class 'object'>)
super
super 的类型参数决定了在 mro 列表中的搜索起始位置,总是返回该参数后续类型的成员。单继
承时总是搜索参数的基类型
>>> class A(object):
... def test(self): print "a"
>>> class B(A):
... def test(self): print "b"
>>> class C(B):
... def __init__(self):
... super(C, self).test() # 从 mro 中 C 的后续类型,也就是 B 开始查找。
... super(B, self).test() # 从 B 的后续类型 A 开始查找。
>>> C.__mro__
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>]
>>> C()
b
a
<__main__.C object at 0x101498f90>
__bases__
类型对象有两个相似的成员:
• __base__: 只读,总是返回 __bases__[0]。
• __bases__: 基类列表,可直接修改来更换基类,影响 mro 顺序。
>>> class A(object): pass
...
>>> class B(object): pass
...
>>> class C(B): pass
...
>>> C.__bases__ # 直接基类元组
(<class '__main__.B'>,)
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)
>>> C.__bases__ = (A,) # 通过__bases__修改基类
>>> C.__base__ # __base__变化A
<class '__main__.A'>
>>> C.__mro__ # mro变化
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
对多继承同样有效,比如调整基类顺序
>>> class C(A,B): pass
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
>>> C.__bases__ = (B,A) # 调整基类顺序
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
通过更换基类实现代码注入,影响既有类型的行为。事实上,我们还可以更改实例的类型。
>>> a = A()
>>> a.__class__
<class '__main__.A'>
>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>
抽象类
from abc import ABCMeta, abstractmethod, abstractproperty
class User(object):
__metaclass__ = ABCMeta # 通过元类来控制抽象类行行为
def __init__(self, uid):
self._uid = uid
@abstractmethod
def print_id(self):
pass
name = abstractproperty() # 抽象属性
class Manager(User):
def __init__(self, uid):
User.__init__(self, uid)
def print_id(self):
print(self._uid, self._name)
name = property(lambda s: s._name, lambda s,v: setattr(s, '_name', v))
m = Manager(1)
m.name = 'TOM'
m.print_id()
7.7 操作符重载
__setitem__
又称索引器,像序列或字典类型那样操作对象。
class A(object):
def __init__(self, **kwargs):
self._data = kwargs
def __getitem__(self, key):
return self._data.get(key)
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
self._data.pop(key, None)
def __contains__(self, key):
return key in self._data.keys()
a = A(x=1, y=2)
print(a['x'])
a['z'] = 3
print('z' in a)
del a['z']
print(a._data)
# 1
# True
# {'x': 1, 'y': 2}
__call__
像函数那样调用对象
class A(object):
def __call__(self, *args, **kwargs):
print(hex(id(self)), args, kwargs)
a = A()
a(1, 2, s='hi')
__dir__
配合__slots__隐藏内部成员
class A(object):
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
def __dir__(self): # 必须返回list
return ['x']
a = A(1, 2)
print(dir(a)) # ['x'] y不见了
__getattr__
这几个方法的触发时机
- __getattr__: 访问不存在的成员
- __setattr__: 对任何成员的赋值操作
- __delattr__: 删除成员操作
- __getattribute__: 访问任何存在或不存在的成员,包括 _dict__
不要在这几个方法里直接访问对象成员,也不要用 hasattr/getattr/setattr/delattr 函数,因为它们会被再次拦截,形成无限循环。正确的做法是直接操作 __dict__
而 __getattribute__ 连 __dict__ 都会拦截,只能用基类的 __getattribute__ 返回结果
class A(object):
def __init__(self, x):
self.x = x
def __getattr__(self, name):
print('getattr: ', name)
return self.__dict__.get(name)
def __setattr__(self, key, value):
print('set: ', key, value)
self.__dict__[key] = value
def __delattr__(self, name):
print('del: ', name)
self.__dict__.pop(name, None)
def __getattribute__(self, name):
print('attr: ', name)
return object.__getattribute__(self, name)
>>> a = A(10) # init里面的self.x = x被__setattr__捕获
set: x 10
attr: __dict__
>>> a.x # 访问已存在的字段,仅被__getattribute__捕获
attr: x
10
>>> a.y=20 # 创建新的字段,被__setattr__ 捕获
set: y 20
attr: __dict__
>>> a.z # 访问不存在的字段被__getattr__捕获
attr: z
getattr: z
attr: __dict__
>>> del a.y
del: y
attr: __dict__
总结:
名字空间:访问对象成员查找顺序:instance.__dict__ -> class.__dict__ --> base.__dict__
字段: 实例字段 (实例名字空间) 和 静态字段 (类名字空间)
属性: 由setter, getter, deletter构成的逻辑,属性的优先级高于实例字段
方法: 方法存放在class.__dict__, 在调用时动态绑定在实例对象上,方法不能重载是因为存放在class.__dict__ 键不能重复
继承: 多继承成员搜索mro:从下到上(深度优先,从派生类到基类),从左到右 ,__base__: 只读,总是返回 __bases__[0]。__bases__: 基类列表,可直接修改来更换基类,影响 mro 顺序。
操作符重载:__setitem__, __getattr__ , __call__, __dir__