(轉)Python 物件協議
原文:https://www.cnblogs.com/JanSN/p/12540247.html
因為 Python 是一門動態語言,Duck Typing 的概念遍佈其中,所以其中的 Concept 並不以型別的約束為載體,而另外使用稱為協議的概念。
在 Python 中就是我需要呼叫你某個方法,你正好就有這個方法。
比如:在字串格式化中,如果有佔位符 %s,那麼按照字串轉換的協議,Python 會自動地呼叫相應物件的__str__()
方法。
分類
①、型別轉換協議
除了__str__()
外,還有其他的方法,
比如
__repr__()
、__init__()
、__long__()
、__float__()
__nonzero__()
等,統稱型別轉換協議。
除了型別轉換協議之外,還要許多其他協議。
②、比較大小的協議
這個協議依賴於__cmp__()
,與 C 語言庫函式 cmp 類似,
當兩者相等時,返回 0,當self < other
時返回負值,反之返回正值。
因為這種複雜性,所以 Python 又有
__eq__()
等於、__ne__()
不等於、__lt__()
小於、__gt__()
大於
等方法來實現比較大小的判定。
這也就是 Python 對==
、!=
、<
和>
等操作符的進行過載的支撐機制。
③、數值型別相關的協議
這一類的函式比較多。基本上,只要實現了那麼幾個方法,基本上就能夠模擬數值型別了。
不過還需要提到一個 Python 中特有的概念:反運算(??)
類似
__radd__()
的方法,所有的數值運算子和位運算子都是支援的,規則也是一律在前面加上字首 r 即可。
④、容器型別協議
下面方法需要配合類中的某個可用索引訪問的容器屬性使用
容器的協議是非常淺顯的,既然為容器,那麼必然要有協議查詢內含多少物件,
在 Python 中,要支援內建函式len()
,通過__len__()
來完成。
-
__getitem__()
讀、檢視使用示例 -
__setitem__()
寫、(使用示例與上面類似) -
__delitem__()
刪除
也很好理解。 -
__iter__()
實現了迭代器協議, -
__reversed__()
則提供對內建函式reversed()
容器型別中最有特色的是對成員關係的判斷符 in 和 not in 的支援,這個方法叫
__contains__()
,只要支援這個函式就能夠使用 in 和 not in 運算子了。
⑤、可呼叫物件協議
所謂可呼叫物件,即類似函式物件,
__call__()
:讓類例項表現得像函式一樣,這樣就可以讓每一個函式呼叫都有所不同。
class Functor(object):
def __init__(self, context):
self._context = context
def __call__(self):
print("do something with {}".format(self._context))
lai_functor = Functor("lai")
yong_functor = Functor("yong")
lai_functor() # 呼叫物件,執行 __call__() 協議內定義動作
yong_functor()
⑥、可雜湊物件協議
__hash__()
:物件需要生成 hashCode 時呼叫協議內的定義
通過此方法來支援hash()
這個內建函式的,這在建立自己的型別時非常有用,
因為只有支援可雜湊協議的型別才能作為 dict 的鍵型別(不過只要繼承自 object 的新式類就預設支援了)
⑦、屬性操作協議和描述符協議
當操作類的屬性時呼叫下列方法
-
__getattr__()
:
如果屬性查詢在例項以及對應的類中(通過__dict__
)失敗, 那麼會呼叫到類的__getattr__
函式。檢視使用示例 -
__setattr__()
對已存在的屬性進行賦值。檢視使用示例 -
__delattr__()
刪除屬性方法應該很少用到,這裡不多做了解
⑧、還有上下文管理器協議,也就是對 with 語句的支援**
這個協議通過
__enter__()
__exit__()
兩個方法來實現對資源的清理,確保資源無論在什麼情況下都會正常清理。
協議不像 C++、Java 等語言中的介面,它更像是宣告,沒有語言上的約束力。
使用示例
__getattr__()
__getattr__
函式的作用:
如果屬性查詢(attribute lookup)在例項以及對應的類中(通過__dict__
)失敗, 那麼會呼叫到類的__getattr__
函式;
如果沒有定義這個函式,那麼丟擲AttributeError
異常。
由此可見,__getattr__一定是作用於屬性查詢的最後一步
- 例 1
class A(object):
def __init__(self, a, b):
self.a1 = a
self.b1 = b
print('init')
def mydefault(self, *args):
print('args:' + str(args[0]))
def __getattr__(self, attr_name):
print("not exist func:", attr_name)
return self.mydefault
a1 = A(10, 20)
a1.fn1(33)
a1.fn2('hello') # 自己執行一遍吧
輸出:
init
not exist func: fn1
args:33
not exist func: fn2
args:hello
- 例 2:經典示例。使用
__getattr__()
虛擬 字典物件
class ObjectDict(dict):
def __init__(self, *args, **kwargs):
super(ObjectDict, self).__init__(*args, **kwargs)
def __getattr__(self, attr_name):
value = self[attr_name] #
if isinstance(value, dict):
value = ObjectDict(value)
return value
if __name__ == '__main__':
od = ObjectDict(asf={'a': 1}, d=True)
print(od.asf) # {'a': 1}
print(od.asf.a) # 1
print(od.d) # True
__setattr__()
這個需要注意,會攔截所有屬性的的賦值語句
如果定義了這個方法,self.attr = value
就會變成self.__setattr__("attr", value)
需要非常注意的是:
當在__setattr__
方法內對屬性進行賦值時,不可使用self.attr = value
因為他會再次呼叫self.__setattr__("attr", value)
則會形成無窮遞迴迴圈,最後導致堆疊溢位異常。
正確做法:
應該通過對屬性字典做索引運算來賦值任何例項屬性,也就是使用
self.__dict__['name'] = value
如果類自定義了__setattr__
方法,當通過例項獲取屬性嘗試賦值時,就會呼叫__setattr__
。
常規的對例項屬性賦值,被賦值的屬性和值會存入例項屬性字典__dict__中。
例項屬性字典__dict__
class ClassA(object):
def __init__(self, classname):
self.classname = classname
insA = ClassA('ClassA')
print(insA.__dict__) # {'classname': 'ClassA'}
insA.tag = 'insA'
print(insA.__dict__) # {'tag': 'insA', 'classname': 'ClassA'}
如果類自定義了__setattr__
, 對例項屬性的賦值就會呼叫它。
類定義中的self.attr
也同樣,所以在__setattr__
下還有self.attr
的賦值操作就會出現無線遞迴的呼叫__setattr__
的情況。
自己實現__setattr__
有很大風險,一般情況都還是繼承object類的__setattr__
方法。
class ClassA(object):
def __init__(self, classname):
self.classname = classname # 這裡 呼叫__setattr__
def __setattr__(self, name, value):
# self.name = value # 如果這裡這樣寫會出現無限遞迴的情況
print('call __setattr__')
insA = ClassA('ClassA') # call __setattr__
print(insA.__dict__) # {}
insA.tag = 'insA' # call __setattr__
print(insA.__dict__) # {}
__getitem__()
如果類把某個屬性定義為序列,可以使用__getitem__()
輸出序列屬性中的某個元素.
class FruitShop():
def __getitem__(self, i):
return self.fruits[i] # 遍歷 FruitShop 例項時,遍歷的是 self.fruits 序列
if __name__ == "__main__":
shop = FruitShop()
shop.fruits = ["apple", "banana"]
print(shop[1]) # banana
print("----")
for item in shop: # 遍歷的是 self.fruits 序列
print(item)
輸出:
banana
----
apple
banana
技術連結