1. 程式人生 > >python進階之內建函式和語法糖觸發魔法方法

python進階之內建函式和語法糖觸發魔法方法

前言

前面已經總結了關鍵字、運算子與魔法方法的對應關係,下面總結python內建函式對應的魔法方法。

魔法方法

數學計算

abs(args):返回絕對值,呼叫__abs__;
round(args):返回四捨五入的值,呼叫__round__;
math.floor():向下取整,呼叫__floor__;
math.ceil():向上取整,呼叫__ceil__;
math.trunc():求一個值距離0最近的整數,呼叫__trunc__;
divmod(a,b):返回商和餘,呼叫__divmod__;
pow(a,b):返回冪,呼叫__pow__;
sum():返回和,呼叫__sum__;
float():轉換小數,呼叫__float__;
int():轉換整數,呼叫__int__;
str():轉換字串,呼叫__str__;
sys.getsizeof():物件佔記憶體的大小,呼叫__sizeof__;
bin(*args, **kwargs):呼叫引數的__bin__方法,返回整數的二進位制表示形式,只支援一個引數,只支援int型別
hash():呼叫__hash__方法,獲取一個物件的雜湊值,相等的兩個數雜湊值相等,反過來不一定成立
hex(*args, **kwargs):呼叫__hex__方法,求整數的十六進位制表示形式,只支援Int型別
oct(*args, **kwargs):呼叫__oct__方法,求整數的八進位制表示形式,只支援Int型別

訪問控制

__getattr__(self, name):getattr方法觸發,僅對物件未定義的屬性有效,即如果檢視獲取一個沒有的屬性時會呼叫該方法,前提是該物件未定義__getattribute__(self, name)方法;
__getattribute__(self, name):getattr方法觸發,如果物件定義了該方法,一定觸發,__getattr__方法將不會被呼叫;它也可以被self.name語法糖觸發;
__setattr__(self, name, value):setattr方法觸發,設定一個物件的屬性;也可以被self.name = ''語法糖觸發。
__delattr__(self, name):delattr方法觸發,刪除一個物件的屬性,或由del self.name 形式觸發;

容器型別

在Python中實現自定義容器型別需要用到一些協議。不可變容器型別有如下協議:

  1. 不可變容器,需要定義 _len_ 和 _getitem_ ;
  2. 可變容器,需要定義 _len_ 、_getitem_、_setitem_、_delitem_ ;
  3. 容器可迭代,需要定義 _iter_
  4. 迭代器,必須遵守迭代器協議,需要定義 _iter_ 和 _next_ 方法。
  • 索引語法糖與魔法方法
__len__(self):返回容器的長度;
__getitem__(self, key):使用self[key]形式語法糖獲取元素會觸發;
__setitem__(self, key):使用self[key] = 'xxx'形式複製會觸發;
__delitem__(self, key):使用del self[key]語法糖觸發
__reversed__(self):reversed(self)觸發,反轉容器;
__missing__(self, key):字典結構使用self[key]形式獲取元素,如果元素不存在觸發;
  • 分片語法糖與魔法方法

切片在底層的原理,py2和py3有很大的不同,py2中使用_getslice_、_setslice_、__delslice__三個魔法方法控制,py3中將索引和切片統一由_getitem_、_setitem_、__delitem__控制。

# py2中
ls = [1,2,3,4]
print(ls[1:3]) # py2中該語法糖呼叫__getslice__方法,py3中廢棄
del ls[1:3] # py2中該語法糖呼叫__delslice__方法,py3中廢棄
ls[1:3] = [1,2,2] # py2中該語法糖呼叫__setslice__方法,py3中廢棄
# py3中
class Person(object):
    def __getitem__(self, item):
        print(item)
        return 'getitem'

    def __setitem__(self, key, value):
        print(key, value)
        return 'setitem'

    def __delitem__(self, key):
        print(key)
        return 'delitem'

if __name__ == "__main__":
    person = Person()
    print(person[0]) # person[0] ==> person.__getitem__(0)
    print(person[0:2]) # person[0:2] ==> person.__getitem__(slice(0,2,None))
    person[0:2] = 'test' # ==> person.__setitem__(slice(0,2,None), 'test')
    del person[0:2] # ==> person.__delitem__(slice(0,2,None))

# 結果
0
getitem
slice(0, 2, None)
getitem
slice(0, 2, None) test
slice(0, 2, None)

python在處理索引語法糖的時候,將索引當做引數傳入相關getitem、setitem、delitem的魔法方法;在處理切片語法糖的時候先呼叫slice方法得到slice例項物件,將其作為引數呼叫相關的魔法方法。

拷貝

__copy__(self):如果物件定義了該方法,copy.copy()就會呼叫該方法返回拷貝物件;
__deepcopy__(self, x):如果物件定義了該方法,copy.deepcopy()就會呼叫該方法返回拷貝物件;

序列化

序列化我們可以簡單理解成對任何資料的一種描述方法,如果多種平臺遵循了相同的序列化協議,資料之間的傳遞就會變得方便。python預設的序列化模組為pickle。

  • 序列化的簡單例子
class Person(object):
    def __init__(self):
        self.name = 'cai'

if __name__ == "__main__":
    import pickle
    person = Person()
    with open('./person.txt', 'wb') as f:
        # 序列化後儲存
        pickle.dump(person,f)

    with open('./person.txt', 'rb') as f:
        # 反序列化
        per = pickle.load(f)
        print(per.name)

# 我們可以把一個類儲存起來,後續讀取它直接使用。
  • 相關的魔法方法
__getinitargs__(self):該魔法方法在py3中似乎被廢棄,原本的功能是在序列化時獲取例項化引數,應該返回一個元組;
__getnewargs__(self):對新式類,通過這個方法改變類在反pickle時傳遞給__new__ 的引數;應該返回一個引數元組。
__getstate__(self):定義物件被序列化時的狀態,而不使用物件的 __dict__ 屬性,必須返回一個字典,他會去替代 __dict__ 屬性,在序列化時被呼叫;
__setstate__(self,state):當一個物件被反pickle時,如果定義了 __setstate__ ,物件的狀態會傳遞給這個魔法方法,而不是直接應用到物件的 __dict__ 屬性, state引數是序列化前的__dict__屬性。
class Person(object):
    def __init__(self,name):
        print('init')
        self.name = name

    def __getinitargs__(self):
        print('initargs')
        return 'zhao',

    def __getnewargs__(self):
        print('newargs')
        return 'wang',

    def __getstate__(self):
        print('getstate')
        return {'name':'xiao'}

    def __setstate__(self, state):
        print('setstate')
        print(state)
        self.__dict__ = state

if __name__ == "__main__":
    import pickle
    person = Person('cai')
    with open('./person.txt', 'wb') as f:
        # 序列化後儲存
        pickle.dump(person,f)

    with open('./person.txt', 'rb') as f:
        # 反序列化
        per = pickle.load(f)
        print(per.name)

# 結果
__new__
init
newargs
getstate
__new__
setstate
{'name': 'xiao'}
xiao

說明:

  1. pickle序列化物件之前,先執行__getnewargs__或__new__方法的引數;

  2. 然後執行__getstate__方法,返回的值替代物件的__dict__屬性值;

  3. 反序列化時呼叫new方法,以getnewargs返回的值作為引數建立例項;

  4. 最後呼叫__setstate__方法,將getstate方法的返回值作為state引數;

  5. 所以由於反序列化時不會呼叫init方法初始化,getinitargs和getnewargs方法的作用都變得不大;

其他

__instancecheck__(self, instance):instance觸發,判斷物件的型別
__subclasscheck__(self, subclass):issubclass觸發,判斷一個物件是另一個物件的子類;
__call__:callable觸發,判斷一個物件是否可呼叫;
__dir__(self):dir()觸發,獲取物件的所有屬性、方法的名字組成的列表;
  • __str__和__repr__

呼叫str觸發_str_,呼叫repr()觸發_repr_,但是print()也可以觸發__str__和__repr__,如果物件定義了_str_,則print()一般觸發_str_,否則觸發_repr_;但列表以及字典等容器總是會使用_repr_ 方法.

__str__和__repr__的區別

  1. 一般來說,_str_ 的返回結果在於強可讀性,而 _repr_ 的返回結果在於準確性;

  2. 預設情況下,在需要卻找不到 __str__方法的時候,會自動呼叫 _repr_ 方法。

總結

熟悉了python語法糖、內建函式與魔法方法之間的關係後,顯然對於如何寫好一個優雅易用的類有很大的幫助。

參考