原文地址:http://hi.baidu.com/_yuan0518/blog/item/803d8fd24e30fc20970a160c.html
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 異常:
class SlottedClass(object): A = 0 __slots__ = ('x', 'y', 'z') def __init__(self): self.x = 'x' self.y = 'y' self.z = 'z' inst = SlottedClass() print SlottedClass.__dict__
輸出:
{'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>}
print inst.__dict__
輸出:
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__'
inst.w = 'w'
輸出:
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__的調用過程:
class PropertyClass(object): def __get__(self, obj, typ=None): print 'call __get__, obj as %r, type as %r' % (obj, typ) def __set__(self, obj, val): print 'call __set__, obj as %r, val as %r' % (obj, val) class MyClass(object): foo = PropertyClass() inst = MyClass() inst.foo = 'x' inst.foo
輸出:
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__中賦的值,因為首先會訪問資料描述符:
class PropertyClass(object): def __get__(self, obj, typ=None): print 'call __get__, obj as %r, type as %r' % (obj, typ) def __set__(self, obj, val): print 'call __set__, obj as %r, val as %r' % (obj, val) class MyClass(object): foo = PropertyClass() inst = MyClass() inst.foo = 'bar' print 'print inst.foo as %r' % inst.foo inst.__dict__['foo'] = 'no-bar' print "print inst.foo as %r but has inst.__dict__['foo'] as %r" % (inst.foo, inst.__dict__['foo'])
輸出:
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__後,就覆蓋了原有的非資料描述符定義:
def callFoo(): print 'call by function callFoo()' class MyClass(object): def foo(self): print 'call by function self.foo()' inst = MyClass() print inst.__dict__ inst.foo() inst.foo = callFoo print inst.__dict__ inst.foo()
輸出:
{}
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()就可以了:
from math import pi def get_pi(dummy): return pi class PI(object): pi = property(get_pi, doc='Constant "pi"') inst = PI() print inst.pi print PI.pi.__doc__
輸出:
3.14159265359
Constant "pi"
下面例子展示類如何建立一個唯讀屬性,它只在初始化時被賦值一次,,用逐位異或操作符將它隱藏起來:
class ProtectAndHideX(object): def __init__(self, x): assert isinstance(x, int), '"x" must be an integer!' self.__x = ~x def get_x(self): return ~self.__x x = property(get_x)
>>> 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 的例子:
class HideX(object): def __init__(self, x): self.x = x def get_x(self): return ~self.__x def set_x(self, x): assert isinstance(x, int), '"x" must be an integer!' self.__x = ~x x = property(get_x, set_x)
>>> 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屬性,如何控制碼只能通過點操作符進行屬性賦值:
class HideX(object): def __init__(self, x): self.x = x def x(): def fget(self): return ~self.__x def fset(self, x): assert isinstance(x, int), '"x" must be an integer!' self.__x = ~x return locals() x = property(**x())
>>> 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。