1. 程式人生 > 其它 >(轉)Python 物件協議

(轉)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
技術連結