Python-屬性控制與描述符

來源:互聯網
上載者:User
原文地址: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。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.