在 Python 中,數據的屬性和處理數據的方法統稱屬性(attribute)。其實,方法只是可調
用的屬性。除了這兩者以外,咱們還能夠建立特性(property),在不改變類接口的前提
下,使用存取方法(即讀值方法和設值方法)修改數據屬性html
除了特性,Python 還提供了豐富的 API,用於控制屬性的訪問權限,以及實現動態屬性。
使用點號訪問屬性時(如 obj.attr),Python 解釋器會調用特殊的方法(如
__getattr__ 和 __setattr__)計算屬性python
使用 __new__ 方法以靈活的方式建立對象
咱們一般把 __init__ 稱爲構造方法,這是從其餘語言借鑑過來的術語。其實,用於構建
實例的是特殊方法 __new__:這是個類方法(使用特殊方式處理,所以沒必要使用
@classmethod 裝飾器),必須返回一個實例。返回的實例會做爲第一個參數(即
self)傳給 __init__ 方法。由於調用 __init__ 方法時要傳入實例,並且禁止返回任何
值,因此 __init__ 方法實際上是「初始化方法」。真正的構造方法是 __new__。咱們幾乎不
須要本身編寫 __new__ 方法,由於從 object 類繼承的實現已經足夠了。函數
建立可讀寫特性工具
class LineItem: def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price def subtotal(self): return self.weight * self.price @property def weight(self): return self.__weight @weight.setter def weight(self, value): if value > 0: self.__weight = value else: raise ValueError('value must be > 0')
去除重複的方法是抽象。抽象特性的定義有兩種
方式:使用特性工廠函數,或者使用描述符類。後者更靈活spa
雖然內置的 property 常常用做裝飾器,但它實際上是一個類。在 Python 中,函數和類通
常能夠互換,由於兩者都是可調用的對象,並且沒有實例化對象的 new 運算符,因此調
用構造方法與調用工廠函數沒有區別。此外,只要能返回新的可調用對象,代替被裝飾的
函數,兩者均可以用做裝飾器。code
不適用property裝飾器的例子,經典的調用orm
class LineItem: def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price def subtotal(self): return self.weight * self.price def get_weight(self): return self.__weight def set_weight(self, value): if value > 0: self.__weight = value else: raise ValueError('value must be > 0') weight = property(get_weight, set_weight)
某些狀況下,這種經典形式比裝飾器句法好;稍後討論的特性工廠函數就是一例。可是,
在方法衆多的類定義體中使用裝飾器的話,一眼就能看出哪些是讀值方法,哪些是設值方
法,而不用按照慣例,在方法名的前面加上 get 和 set。htm
本節的主要觀點是,obj.attr 這樣的表達式不會從 obj 開始尋找 attr,而是從
obj.__class__ 開始,並且,僅當類中沒有名爲 attr 的特性時,Python 纔會在 obj 實
例中尋找。這條規則不只適用於特性,還適用於一整類描述符——覆蓋型描述符對象
先尋找類屬性,在尋找實例屬性blog
若是使用經典調用句法,爲 property 對象設置文檔字符串的方法是傳入 doc 參數:
weight = property(get_weight, set_weight, doc='weight in kilograms')
使用裝飾器建立 property 對象時,讀值方法(有 @property 裝飾器的方法)的文檔字
符串做爲一個總體,變成特性的文檔
建立特性工廠函數
def quantity(storage_name): def qty_getter(instance): return instance.__dict__[storage_name] def qty_setter(instance, value): if value > 0: instance.__dict__[storage_name] = value else: raise ValueError('value must be > 0') return property(qty_getter, qty_setter) class LineItem: weight = quantity('weight') price = quantity('price') def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price def subtotal(self): return self.weight * self.price
在真實的系統中,分散在多個類中的多個字段可能要作一樣的驗證,此時最好把
quantity 工廠函數放在實用工具模塊中,以便重複使用。最終可能要重構那個簡單的工
廠函數,改爲更易擴展的描述符類,而後使用專門的子類執行不一樣的驗證。
處理屬性刪除操做
class BlackKnight: def __init__(self): self.members = ['an arm', 'another arm', 'a leg', 'another leg'] self.phrases = ["It's but a scratch.", "It's just a flesh wound.", "I'm invincible!", "All right, we'll call it a draw"] @property def member(self): print('next member is:') return self.members[0] @member.deleter def member(self): text = 'BLACK KNIGHT (loses {})\n -- {}' print(text.format(self.members.pop(0), self.phrases.pop(0)))
影響屬性處理方式的特殊屬性
後面幾節中的不少函數和特殊方法,其行爲受下述 3 個特殊屬性的影響。
__class__
對象所屬類的引用(即 obj.__class__ 與 type(obj) 的做用相同)。Python 的某些
特殊方法,例如 __getattr__,只在對象的類中尋找,而不在實例中尋找。
__dict__
一個映射,存儲對象或類的可寫屬性。有 __dict__ 屬性的對象,任什麼時候候都能隨意
設置新屬性。若是類有 __slots__ 屬性,它的實例可能沒有 __dict__ 屬性。參見下面
對 __slots__ 屬性的說明。
__slots__
類能夠定義這個這屬性,限制實例能有哪些屬性。__slots__ 屬性的值是一個字符
串組成的元組,指明容許有的屬性。 若是 __slots__ 中沒有 '__dict__',那麼該類
的實例沒有 __dict__ 屬性,實例只容許有指定名稱的屬性。
dir([object])
列出對象的大多數屬性。官方文檔
(https://docs.python.org/3/library/functions.html#dir)說,dir 函數的目的是交互式使用,
所以沒有提供完整的屬性列表,只列出一組「重要的」屬性名。dir 函數能審查有或沒有
__dict__ 屬性的對象。dir 函數不會列出 __dict__ 屬性自己,但會列出其中的
鍵。dir 函數也不會列出類的幾個特殊屬性,例如 __mro__、__bases__ 和 __name__。
若是沒有指定可選的 object 參數,dir 函數會列出當前做用域中的名稱。
getattr(object, name[, default])
從 object 對象中獲取 name 字符串對應的屬性。獲取的屬性可能來自對象所屬的類
或超類。若是沒有指定的屬性,getattr 函數拋出 AttributeError 異常,或者返回
default 參數的值(若是設定了這個參數的話)。
hasattr(object, name)
若是 object 對象中存在指定的屬性,或者能以某種方式(例如繼承)經過 object
對象獲取指定的屬性,返回 True。文檔
(https://docs.python.org/3/library/functions.html#hasattr)說道:「這個函數的實現方法是調
用 getattr(object, name) 函數,看看是否拋出 AttributeError 異常。」
setattr(object, name, value)
把 object 對象指定屬性的值設爲 value,前提是 object 對象能接受那個值。這個
函數可能會建立一個新屬性,或者覆蓋現有的屬性。
vars([object])
返回 object 對象的 __dict__ 屬性;若是實例所屬的類定義了 __slots__ 屬性,
實例沒有 __dict__ 屬性,那麼 vars 函數不能處理那個實例(相反,dir 函數能處理這
樣的實例)。若是沒有指定參數,那麼 vars() 函數的做用與 locals() 函數同樣:返回
表示本地做用域的字典。
使用點號或內置的 getattr、hasattr 和 setattr 函數存取屬性都會觸發下述列表中相
應的特殊方法。可是,直接經過實例的 __dict__ 屬性讀寫屬性不會觸發這些特殊方法
——若是須要,一般會使用這種方式跳過特殊方法。
不論是使用點號存取屬性,仍是使用 19.6.2 節列出的某個內置函數,都會觸發下述特殊方
法中的一個。例如,obj.attr 和 getattr(obj, 'attr', 42) 都會觸發
Class.__getattribute__(obj, 'attr') 方法。
__delattr__(self, name)
只要使用 del 語句刪除屬性,就會調用這個方法。例如,del obj.attr 語句觸發
Class.__delattr__(obj, 'attr') 方法。
__dir__(self)
把對象傳給 dir 函數時調用,列出屬性。例如,dir(obj) 觸發
Class.__dir__(obj) 方法。
__getattr__(self, name)
僅當獲取指定的屬性失敗,搜索過 obj、Class 和超類以後調用。表達式
obj.no_such_attr、getattr(obj, 'no_such_attr') 和 hasattr(obj,
'no_such_attr') 可能會觸發 Class.__getattr__(obj, 'no_such_attr') 方法,但
是,僅當在 obj、Class 和超類中找不到指定的屬性時纔會觸發。
__getattribute__(self, name)
嘗試獲取指定的屬性時總會調用這個方法,不過,尋找的屬性是特殊屬性或特殊方法
時除外。點號與 getattr 和 hasattr 內置函數會觸發這個方法。調用
__getattribute__ 方法且拋出 AttributeError 異常時,纔會調用 __getattr__ 方
法。爲了在獲取 obj 實例的屬性時不致使無限遞歸,__getattribute__ 方法的實現要
使用 super().__getattribute__(obj, name)。
__setattr__(self, name, value)
嘗試設置指定的屬性時總會調用這個方法。點號和 setattr 內置函數會觸發這個方法。例如,obj.attr = 42 和 setattr(obj, 'attr', 42) 都會觸發Class.__setattr__(obj, ‘attr’, 42) 方法。