Python-属性控制与描述符
1.__dict__与__slots__
字典位于类与实例的“心脏”。__dict__属性跟踪所有实例属性。举例来说,你有一个实例inst,它有一个属性foo,那使用inst.foo 来访问它与使用inst.__dict__['foo']来访问是一致的。class AClass(): A = 0 def __init__(self): self.x = 'x' self.y = 'y' self.z = 'z' inst = AClass() print AClass.__dict__ print inst.__dict__ inst.w = 'w' print inst.__dict__
输出:
{'A': 0, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0xb736ad4c>}
{'y': 'y', 'x': 'x', 'z': 'z'}
{'y': 'y', 'x': 'x', 'z': 'z', 'w': 'w'}
字典会占据大量内存,如果你有一个属性数量很少的类,但有很多实例,那么正好是这种情况。为内存上的考虑,可以使用__slots__属性来替代__dict__。基本上,__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性的集合来表示。它可以是一个列表,元组或可迭代对象。也可以是标识实例能拥有的唯一的属性的简单字符串。带__slots__属性的类定义不会存在__dict__,且任何试图创建一个其名不在__slots__中的名字的实例属性都将导致AttributeError 异常:
输出:
{'A': 0, '__module__': '__main__', '__slots__': ('x', 'y', 'z'), 'y': <member 'y' of 'SlottedClass' objects>, 'x': <member 'x' of 'SlottedClass' objects>, 'z': <member 'z' of 'SlottedClass' objects>, '__doc__': None, '__init__': <function __init__ at 0xb752417c>}
输出:
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
print inst.__dict__
AttributeError: 'SlottedClass' object has no attribute '__dict__'
输出:
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
inst.w = 'w'
AttributeError: 'SlottedClass' object has no attribute 'w'
这种特性的主要目的是节约内存。其副作用是某种类型的"安全",它能防止用户随心所欲的动态增加实例属性。
2.__getattr__()与__getattribute__()
Python 类有一个名为__getattr__()的特殊方法,它仅当属性不在实例的__dict__或它的类的__dict__,或者祖先类的__dict__中找到时,才被调用。__getattribute__()使用起来类似__getattr__(),不同之处在于,当属性被访问时,它就一直都可以被调用,而不局限于不能找到的情况。
如果类同时定义了__getattribute__()及__getattr__()方法,除非明确从__getattribute__()里调用,或__getattribute__()引发了AttributeError 异常,否则后者不会被调用。
3.描述符__get__(),__set__()和__delete__()
描述符是Python 新式类中的关键点之一。它为对象属性提供强大的API。你可以认为描述符是表示对象属性的一个代理。当需要属性时,可通过描述符(如果有)或者采用常规方式(句点属性标识法)来访问它。严格来说, 描述符实际上可以是任何(新式) 类, 这种类至少实现了三个特殊方法__get__(),__set__()及__delete__()中的一个,这三个特殊方法充当描述符协议的作用。__get__(),__set__()及__delete__()的原型,如下:
def __get__(self, obj, typ=None) ==> value
def __set__(self, obj, val) ==> None
def __delete__(self, obj) ==> None
整个描述符系统的心脏是__getattribute__(),因为对每个属性的实例都会调用到这个特殊的方法。举例来说,给定类X 和实例x, x.foo 由__getattribute__()转化成:
type(x).__dict__['foo'].__get__(x, type(x))
如果类调用了__get__()方法,那么None 将作为对象被传入(对于实例, 传入的是self):
X.__dict__['foo'].__get__(None, X)
最后,如果super()被调用了,比如,给定Y 为X 的子类,然后用super(Y,obj).foo 在obj.__class__.__mro__中紧接类Y 沿着继承树来查找类X,然后调用:
X.__dict__['foo'].__get__(obj, X)
__getattribute__()方法查询属性的优先级:
类属性
数据描述符 #由__get__(),__set__()及__delete__()定义
实例属性 #由示例的__dict__跟踪
非数据描述符 #函数定义
默认为__getattr__()
示例一展示__get__与__set__的调用过程:
输出:
call __set__, obj as <__main__.MyClass object at 0xb733a84c>, val as 'x'
call __get__, obj as <__main__.MyClass object at 0xb733a84c>, type as <class '__main__.MyClass'>
示例二展示数据描述符与示例属性的优先级关系,当设置实例的__dict__后,使用点操作符访问时,仍然无法拿到__dict__中赋的值,因为首先会访问数据描述符:
输出:
call __set__, obj as <__main__.MyClass object at 0xb752f92c>, val as 'bar'
call __get__, obj as <__main__.MyClass object at 0xb752f92c>, type as <class '__main__.MyClass'>
print inst.foo as None
call __get__, obj as <__main__.MyClass object at 0xb752f92c>, type as <class '__main__.MyClass'>
print inst.foo as None but has inst.__dict__['foo'] as 'no-bar'
示例三展示实例属性与非数据描述符的优先级关系,当设置实例的__dict__后,就覆盖了原有的非数据描述符定义:
输出:
{}
call by function self.foo()
{'foo': <function callFoo at 0xb7511d4c>}
call by function callFoo()
4.property()内建函数
property()内建函数有四个参数,它们是 :
property(fget=None, fset=None, fdel=None, doc=None)
property()的一般用法是,将它写在一个类定义中,property()接受一些传进来的函数作为参数。你不必写一个描述符类,并在其中定义你要调用的这些方法。只要把你写的函数全部传递给property()就可以了:
输出:
3.14159265359
Constant "pi"
下面例子展示类如何建立一个只读属性,它只在初始化时被赋值一次,,用逐位异或操作符将它隐藏起来:
>>> inst = ProtectAndHideX('foo')
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 13, in <module>
inst = ProtectAndHideX('foo')
File "/home/zhangjun/workspace/try/src/try.py", line 5, in __init__
assert isinstance(x, int), '"x" must be an integer!'
AssertionError: "x" must be an integer!
>>> inst = ProtectAndHideX(10)
>>> print 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
inst.x = 20
AttributeError: can't set attribute
下面是另一个关于setter 的例子:
>>> inst = HideX('foo')
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 16, in <module>
inst = HideX('foo')
File "/home/zhangjun/workspace/try/src/try.py", line 5, in __init__
self.x = x
File "/home/zhangjun/workspace/try/src/try.py", line 11, in set_x
assert isinstance(x, int), '"x" must be an integer!'
AssertionError: "x" must be an integer!
>>> inst = HideX(10)
>>> print 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
>>> print 'inst.x =', inst.x
inst.x = 20
这里其实可以用inst.set_x(30)来set属性,如何控制代码只能通过点操作符进行属性赋值:
>>> inst = HideX(10)
>>> 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
>>> 'inst.x =', inst.x
inst.x = 20
>>> inst.set_x(30)
Traceback (most recent call last):
File "/home/zhangjun/workspace/try/src/try.py", line 22, in <module>
inst.set_x(30)
AttributeError: 'HideX' object has no attribute 'set_x'
这里两点明显不同:(1) 类的名字空间更加简洁,只有 ['__doc__','__init__', '__module__', 'x'], (2), 用户不能再通过inst.set_x(40) 给属性赋值,必须使用init.x = 30。