當前位置:
首頁 > 知識 > Python 魔術方法(Magic Method)

Python 魔術方法(Magic Method)

(點擊

上方藍字

,快速關注我們)




來源:j_hao104


my.oschina.net/jhao104/blog/779743


如有好文章投稿,請點擊 → 這裡了解詳情




介紹



在Python中,所有以「__」雙下劃線包起來的方法,都統稱為「Magic Method」,例如類的初始化方法 __init__,Python中所有的魔術方法均在官方文檔中有相應描述,但是對於官方的描述比較混亂而且組織比較鬆散。很難找到有一個例子。




構造和初始化




每個Pythoner都知道一個最基本的魔術方法, __init__ 。通過此方法我們可以定義一個對象的初始操作。然而,當調用 x = SomeClass() 的時候, __init__ 並不是第一個被調用的方法。實際上,還有一個叫做__new__ 的方法,兩個共同構成了「構造函數」。




__new__是用來創建類並返回這個類的實例, 而__init__只是將傳入的參數來初始化該實例。



在對象生命周期調用結束時,__del__ 方法會被調用,可以將__del__理解為「構析函數」。下面通過代碼的看一看這三個方法:





from

os.path

import

join


 


class

FileObject

:


    

"""給文件對象進行包裝從而確認在刪除時文件流關閉"""


 


    

def

__init__

(

self

,

filepath

=

"~"

,

filename

=

"sample.txt"

)

:


        

#讀寫模式打開一個文件


        

self

.

file

=

open

(

join

(

filepath

,

filename

),

"r+"

)


 


    

def

__del__

(

self

)

:


        

self

.

file

.

close

()


        

del

self

.

file




控制屬性訪問




許多從其他語言轉到Python的人會抱怨它缺乏類的真正封裝。(沒有辦法定義私有變數,然後定義公共的getter和setter)。Python其實可以通過魔術方法來完成封裝。我們來看一下:





__getattr__(self, name)




定義當用戶試圖獲取一個不存在的屬性時的行為。這適用於對普通拼寫錯誤的獲取和重定向,對獲取一些不建議的屬性時候給出警告(如果你願意你也可以計算並且給出一個值)或者處理一個 AttributeError 。只有當調用不存在的屬性的時候會被返回。





__setattr__(self, name, value)




與__getattr__(self, name)不同,__setattr__ 是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進行個性定製。實現__setattr__時要避免」無限遞歸」的錯誤。





__delattr__




與 __setattr__ 相同,但是功能是刪除一個屬性而不是設置他們。實現時也要防止無限遞歸現象發生。





__getattribute__(self, name)




__getattribute__定義了你的屬性被訪問時的行為,相比較,__getattr__只有該屬性不存在時才會起作用。因此,在支持__getattribute__的Python版本,調用__getattr__前必定會調用 __getattribute__。__getattribute__同樣要避免」無限遞歸」的錯誤。需要提醒的是,最好不要嘗試去實現__getattribute__,因為很少見到這種做法,而且很容易出bug。




在進行屬性訪問控制定義的時候很可能會很容易引起「無限遞歸」。如下面代碼:





#  錯誤用法


def

__setattr__

(

self

,

name

,

value

)

:


    

self

.

name

=

value


    

# 每當屬性被賦值的時候(如self.name = value), ``__setattr__()`` 會被調用,這樣就造成了遞歸調用。


    

# 這意味這會調用 ``self.__setattr__("name", value)`` ,每次方法會調用自己。這樣會造成程序崩潰。


 


#  正確用法


def

__setattr__

(

self

,

name

,

value

)

:


    

self

.

__dict__

[

name

]

=

value

  

# 給類中的屬性名分配值


    

# 定製特有屬性




Python的魔術方法很強大,但是用時卻需要慎之又慎,了解正確的使用方法非常重要。




創建自定義容器




有很多方法可以讓你的Python類行為向內置容器類型一樣,比如我們常用的list、dict、tuple、string等等。Python的容器類型分為可變類型(如list、dict)和不可變類型(如string、tuple),可變容器和不可變容器的區別在於,不可變容器一旦賦值後,不可對其中的某個元素進行修改。   在講創建自定義容器之前,應該先了解下協議。這裡的協議跟其他語言中所謂的」介面」概念很像,它給你很多你必須定義的方法。然而在Python中的協議是很不正式的,不需要明確聲明實現。事實上,他們更像一種指南。




自定義容器的magic method




下面細緻了解下定義容器可能用到的魔術方法。首先,實現不可變容器的話,你只能定義 __len__ 和 __getitem__ (下面會講更多)。可變容器協議則需要所有不可變容器的所有,另外還需要 __setitem__ 和 __delitem__。如果你希望你的對象是可迭代的話,你需要定義 __iter__ 會返回一個迭代器。迭代器必須遵循迭代器協議,需要有 __iter__(返回它本身) 和 next。





__len__(self)




返回容器的長度。對於可變和不可變容器的協議,這都是其中的一部分。





__getitem__(self, key)




定義當某一項被訪問時,使用self[key]所產生的行為。這也是不可變容器和可變容器協議的一部分。如果鍵的類型錯誤將產生TypeError;如果key沒有合適的值則產生KeyError。





__setitem__(self, key, value)




當你執行self[key] = value時,調用的是該方法。





__delitem__(self, key)




定義當一個項目被刪除時的行為(比如 del self[key])。這只是可變容器協議中的一部分。當使用一個無效的鍵時應該拋出適當的異常。





__iter__(self)




返回一個容器迭代器,很多情況下會返回迭代器,尤其是當內置的iter()方法被調用的時候,以及當使用for x in container:方式循環的時候。迭代器是它們本身的對象,它們必須定義返回self的__iter__方法。





__reversed__(self)




實現當reversed()被調用時的行為。應該返回序列反轉後的版本。僅當序列可以是有序的時候實現它,例如對於列表或者元組。





__contains__(self, item)




定義了調用in和not in來測試成員是否存在的時候所產生的行為。你可能會問為什麼這個不是序列協議的一部分?因為當__contains__沒有被定義的時候,如果沒有定義,那麼Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。





__missing__(self, key)




dict字典類型會有該方法,它定義了key如果在容器中找不到時觸發的行為。比如d = {『a』: 1}, 當你執行d[notexist]時,d.__missing__[『notexist』]就會被調用。




一個例子




下面是書中的例子,用魔術方法來實現Haskell語言中的一個數據結構。





# -*- coding: utf-8 -*-


class

FunctionalList

:


    

""" 實現了內置類型list的功能,並豐富了一些其他方法: head, tail, init, last, drop, take"""


 


    

def

__init__

(

self

,

values

=

None

)

:


        

if

values

is

None

:


            

self

.

values

=

[]


        

else

:


            

self

.

values

=

values


 


    

def

__len__

(

self

)

:


        

return

len

(

self

.

values

)


 


    

def

__getitem__

(

self

,

key

)

:


        

return

self

.

values

[

key

]


 


    

def

__setitem__

(

self

,

key

,

value

)

:


        

self

.

values

[

key

]

=

value


 


    

def

__delitem__

(

self

,

key

)

:


        

del

self

.

values

[

key

]


 


    

def

__iter__

(

self

)

:


        

return

iter

(

self

.

values

)


 


    

def

__reversed__

(

self

)

:


        

return

FunctionalList

(

reversed

(

self

.

values

))


 


    

def

append

(

self

,

value

)

:


        

self

.

values

.

append

(

value

)


    

def

head

(

self

)

:


        

# 獲取第一個元素


        

return

self

.

values

[

0

]


    

def

tail

(

self

)

:


        

# 獲取第一個元素之後的所有元素


        

return

self

.

values

[

1

:

]


    

def

init

(

self

)

:


        

# 獲取最後一個元素之前的所有元素


        

return

self

.

values

[

:-

1

]


    

def

last

(

self

)

:


        

# 獲取最後一個元素


        

return

self

.

values

[

-

1

]


    

def

drop

(

self

,

n

)

:


        

# 獲取所有元素,除了前N個


        

return

self

.

values

[

n

:

]


    

def

take

(

self

,

n

)

:


        

# 獲取前N個元素


        

return

self

.

values

[

:

n

]




其實在collections模塊中已經有了很多類似的實現,比如Counter、OrderedDict等等。




反射




你也可以控制怎麼使用內置在函數sisinstance()和issubclass()方法 反射定義魔術方法. 這個魔術方法是:





__instancecheck__(self, instance)




檢查一個實例是不是你定義的類的實例





__subclasscheck__(self, subclass)




檢查一個類是不是你定義的類的子類




這些魔術方法的用例看起來很小, 並且確實非常實用. 它們反應了關於面向對象程序上一些重要的東西在Python上,並且總的來說Python: 總是一個簡單的方法去找某些事情, 即使是沒有必要的. 這些魔法方法可能看起來不是很有用, 但是一旦你需要它們,你會感到慶幸它們的存在。




可調用的對象




你也許已經知道,在Python中,方法是最高級的對象。這意味著他們也可以被傳遞到方法中,就像其他對象一樣。這是一個非常驚人的特性。




在Python中,一個特殊的魔術方法可以讓類的實例的行為表現的像函數一樣,你可以調用它們,將一個函數當做一個參數傳到另外一個函數中等等。這是一個非常強大的特性,其讓Python編程更加舒適甜美。





__call__(self, [args...])




允許一個類的實例像函數一樣被調用。實質上說,這意味著 x() 與 x.__call__() 是相同的。注意 __call__ 的參數可變。這意味著你可以定義 __call__ 為其他你想要的函數,無論有多少個參數。




__call__ 在那些類的實例經常改變狀態的時候會非常有效。調用這個實例是一種改變這個對象狀態的直接和優雅的做法。用一個實例來表達最好不過了:





# -*- coding: UTF-8 -*-


 


class

Entity

:


    

"""


    調用實體來改變實體的位置


    """


 


def

__init__

(

self

,

size

,

x

,

y

)

:


    

self

.

x

,

self

.

y

=

x

,

y


    

self

.

size

=

size


 


 


def

__call__

(

self

,

x

,

y

)

:


    

"""


    改變實體的位置


    """


    

self

.

x

,

self

.

y

=

x

,

y




上下文管理




with聲明是從Python2.5開始引進的關鍵詞。你應該遇過這樣子的代碼:





with

open

(

"foo.txt"

)

as

bar

:


    

# do something with bar




在with聲明的代碼段中,我們可以做一些對象的開始操作和退出操作,還能對異常進行處理。這需要實現兩個魔術方法: __enter__ 和 __exit__。





__enter__(self)




定義了當使用with語句的時候,會話管理器在塊被初始創建時要產生的行為。請注意,__enter__的返回值與with語句的目標或者as後的名字綁定。





__exit__(self, exception_type, exception_value, traceback)




定義了當一個代碼塊被執行或者終止後,會話管理器應該做什麼。它可以被用來處理異常、執行清理工作或做一些代碼塊執行完畢之後的日常工作。如果代碼塊執行成功,exceptiontype,exceptionvalue,和traceback將會為None。否則,你可以選擇處理這個異常或者是直接交給用戶處理。如果你想處理這個異常的話,請確保__exit在所有語句結束之後返回True。如果你想讓異常被會話管理器處理的話,那麼就讓其產生該異常。




創建對象描述器




描述器是通過獲取、設置以及刪除的時候被訪問的類。當然也可以改變其它的對象。描述器並不是獨立的。相反,它意味著被一個所有者類持有。當創建面向對象的資料庫或者類,裡面含有相互依賴的屬相時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數值,或者表示某個數據的附加屬性。




為了成為一個描述器,一個類必須至少有__get__,__set__,__delete__方法被實現:





__get__(self, instance, owner)




定義了當描述器的值被取得的時候的行為。instance是擁有該描述器對象的一個實例。owner是擁有者本身





__set__(self, instance, value)




定義了當描述器的值被改變的時候的行為。instance是擁有該描述器類的一個實例。value是要設置的值。





__delete__(self, instance)




定義了當描述器的值被刪除的時候的行為。instance是擁有該描述器對象的一個實例。




下面是一個描述器的實例:單位轉換。





# -*- coding: UTF-8 -*-


class

Meter

(

object

)

:


    

"""


    對於單位"米"的描述器


    """


 


    

def

__init__

(

self

,

value

=

0.0

)

:


        

self

.

value

=

float

(

value

)


 


    

def

__get__

(

self

,

instance

,

owner

)

:


        

return

self

.

value


 


    

def

__set__

(

self

,

instance

,

value

)

:


        

self

.

value

=

float

(

value

)


 


 


class

Foot

(

object

)

:


    

"""


    對於單位"英尺"的描述器


    """


 


    

def

__get__

(

self

,

instance

,

owner

)

:


        

return

instance

.

meter

*

3.2808


 


    

def

__set__

(

self

,

instance

,

value

)

:


        

instance

.

meter

=

float

(

value

)

/

3.2808


 


 


class

Distance

(

object

)

:


    

"""


    用米和英寸來表示兩個描述器之間的距離


    """


    

meter

=

Meter

(

10

)


    

foot

=

Foot

()




使用時:





>>>

d

=

Distance

()


>>>

print

d

.

foot


>>>

print

d

.

meter


32.808


10.0




複製




有時候,尤其是當你在處理可變對象時,你可能想要複製一個對象,然後對其做出一些改變而不希望影響原來的對象。這就是Python的copy所發揮作用的地方。





__copy__(self)




定義了當對你的類的實例調用copy.copy()時所產生的行為。copy.copy()返回了你的對象的一個淺拷貝——這意味著,當實例本身是一個新實例時,它的所有數據都被引用了——例如,當一個對象本身被複制了,它的數據仍然是被引用的(因此,對於淺拷貝中數據的更改仍然可能導致數據在原始對象的中的改變)。





__deepcopy__(self, memodict={})




定義了當對你的類的實例調用copy.deepcopy()時所產生的行為。copy.deepcopy()返回了你的對象的一個深拷貝——對象和其數據都被拷貝了。memodict是對之前被拷貝的對象的一個緩存——這優化了拷貝過程並且阻止了對遞歸數據結構拷貝時的無限遞歸。當你想要進行對一個單獨的屬性進行深拷貝時,調用copy.deepcopy(),並以memodict為第一個參數。




附錄




用於比較的魔術方法







數值計算的魔術方法




單目運算符和函數







雙目運算符或函數







增量運算









類型轉換







看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

隨機之美,隨機森林
Django 使用 Celery 實現非同步任務
用 Python 做股市數據分析(二)
Python 爬蟲實戰:股票數據定向爬蟲
Python: 受限制的 "函數調用"

TAG:Python開發者 |

您可能感興趣

心靈魔術-Derren Brown:The Push
魔術貼設計!全新 Air Jordan 1 High Double Strap 即將發布
sk8mafia 新晉職業滑手 Stephen Lawyer 魔術滑板片段!
adidas 滑板天團巡街,Brian Peacock 展現精準魔術腳
光影魔術師Johnson Wee
Jupyter Notebook最實用的5個魔術命令
LoveLive!小泉花陽魔術師Cosplay
社交網路紅人 Alexandra Hackett 「魔術手」下的改造單品
前瞻-魔術vs開拓者:雙槍的showtime
《LoveLive!》南小鳥魔術師覺醒cosplay欣賞
珠寶魔術師——RenéLalique
Virgil Abloh先稍稍!Supreme這次先找來了魔術師
剪輯魔術:網紅滑手 Versace 領銜 Nike SB 新片
Chanel 香奈兒 毛線魔術貼大logo拖鞋
耐克Nike WMNS Air Force 1 Low 低幫空軍一號經典板鞋「水晶鐳射魔術貼換勾」
全新魔術貼設計?!Fear of God與Nike聯名新鞋諜照釋出!
Mobvoi的雙屏Ticwatch Pro聽起來像無稽之談,運行起來像魔術
99!無補單 魔術貼厚底涼鞋女!斐樂FILA disruptor2 sandal FS1HTZ3081
《Fate/Grand Order》終局特異點開幕!花之魔術師梅林限時登場!
美女《LoveLive!》東條希cos,魔術師已經覺醒!