Python中的魔術方法詳解
介紹
在Python中,所有以“__”雙下劃線包起來的方法,都統稱為“Magic Method”,中文稱『魔術方法』,例如類的初始化方法 __init__ ,Python中所有的魔術方法均在官方文件中有相應描述,但是對於官方的描述比較混亂而且組織比較鬆散。很難找到有一個例子。
構造和初始化
每個Pythoner都知道一個最基本的魔術方法, __init__ 。通過此方法我們可以定義一個物件的初始操作。然而,當呼叫 x = SomeClass() 的時候, __init__ 並不是第一個被呼叫的方法。實際上,還有一個叫做__new__ 的方法,兩個共同構成了“建構函式”。
__new__是用來建立類並返回這個類的例項, 而__init__只是將傳入的引數來初始化該例項。
在物件生命週期呼叫結束時,__del__ 方法會被呼叫,可以將__del__理解為“構析函式”。下面通過程式碼的看一看這三個方法:
1 2 3 4 5 6 7 8 9 |
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其實可以通過魔術方法來完成封裝。我們來看一下:
1 |
__getattr__( self , name):
|
定義當用戶試圖獲取一個不存在的屬性時的行為。這適用於對普通拼寫錯誤的獲取和重定向,對獲取一些不建議的屬性時候給出警告(如果你願意你也可以計算並且給出一個值)或者處理一個 AttributeError 。只有當呼叫不存在的屬性的時候會被返回。
1 |
__setattr__( self , name, value):
|
與__getattr__(self, name)不同,__setattr__ 是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進行個性定製。實現__setattr__時要避免"無限遞迴"的錯誤。
1 |
__delattr__:
|
與 __setattr__ 相同,但是功能是刪除一個屬性而不是設定他們。實現時也要防止無限遞迴現象發生。
1 |
__getattribute__( self , name):
|
__getattribute__定義了你的屬性被訪問時的行為,相比較,__getattr__只有該屬性不存在時才會起作用。因此,在支援__getattribute__的Python版本,呼叫__getattr__前必定會呼叫 __getattribute__。__getattribute__同樣要避免"無限遞迴"的錯誤。需要提醒的是,最好不要嘗試去實現__getattribute__,因為很少見到這種做法,而且很容易出bug。
在進行屬性訪問控制定義的時候很可能會很容易引起“無限遞迴”。如下面程式碼:
1 2 3 4 5 6 7 8 9 |
# 錯誤用法
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。
1 |
__len__( self ):
|
返回容器的長度。對於可變和不可變容器的協議,這都是其中的一部分。
1 |
__getitem__( self , key):
|
定義當某一項被訪問時,使用self[key]所產生的行為。這也是不可變容器和可變容器協議的一部分。如果鍵的型別錯誤將產生TypeError;如果key沒有合適的值則產生KeyError。
1 |
__setitem__( self , key, value):
|
當你執行self[key] = value時,呼叫的是該方法。
1 |
__delitem__( self , key):
|
定義當一個專案被刪除時的行為(比如 del self[key])。這只是可變容器協議中的一部分。當使用一個無效的鍵時應該丟擲適當的異常。
1 |
__iter__( self ):
|
返回一個容器迭代器,很多情況下會返回迭代器,尤其是當內建的iter()方法被呼叫的時候,以及當使用for x in container:方式迴圈的時候。迭代器是它們本身的物件,它們必須定義返回self的__iter__方法。
1 |
__reversed__( self ):
|
實現當reversed()被呼叫時的行為。應該返回序列反轉後的版本。僅當序列可以是有序的時候實現它,例如對於列表或者元組。
1 |
__contains__( self , item):
|
定義了呼叫in和not in來測試成員是否存在的時候所產生的行為。你可能會問為什麼這個不是序列協議的一部分?因為當__contains__沒有被定義的時候,如果沒有定義,那麼Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。
1 |
__missing__( self , key):
|
dict字典型別會有該方法,它定義了key如果在容器中找不到時觸發的行為。比如d = {'a': 1}, 當你執行d[notexist]時,d.__missing__['notexist']就會被呼叫。
例項
下面是書中的例子,用魔術方法來實現Haskell語言中的一個數據結構。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# -*- 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()方法 反射定義魔術方法. 這個魔術方法是:
1 |
__instancecheck__( self , instance):
|
檢查一個例項是不是你定義的類的例項
1 |
__subclasscheck__( self , subclass):
|
檢查一個類是不是你定義的類的子類
這些魔術方法的用例看起來很小, 並且確實非常實用. 它們反應了關於面向物件程式上一些重要的東西在Python上,並且總的來說Python: 總是一個簡單的方法去找某些事情, 即使是沒有必要的. 這些魔法方法可能看起來不是很有用, 但是一旦你需要它們,你會感到慶幸它們的存在。
可呼叫的物件
你也許已經知道,在Python中,方法是最高階的物件。這意味著他們也可以被傳遞到方法中,就像其他物件一樣。這是一個非常驚人的特性。
在Python中,一個特殊的魔術方法可以讓類的例項的行為表現的像函式一樣,你可以呼叫它們,將一個函式當做一個引數傳到另外一個函式中等等。這是一個非常強大的特性,其讓Python程式設計更加舒適甜美。
1 |
__call__( self , [args...]):
|
允許一個類的例項像函式一樣被呼叫。實質上說,這意味著 x() 與 x.__call__() 是相同的。注意 __call__ 的引數可變。這意味著你可以定義 __call__ 為其他你想要的函式,無論有多少個引數。
__call__ 在那些類的例項經常改變狀態的時候會非常有效。呼叫這個例項是一種改變這個物件狀態的直接和優雅的做法。用一個例項來表達最好不過了:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# -*- 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開始引進的關鍵詞。你應該遇過這樣子的程式碼:
1 2 |
with open ( 'foo.txt' ) as bar:
# do something with bar
|
在with宣告的程式碼段中,我們可以做一些物件的開始操作和退出操作,還能對異常進行處理。這需要實現兩個魔術方法: __enter__ 和 __exit__。
1 |
__enter__( self ):
|
定義了當使用with語句的時候,會話管理器在塊被初始建立時要產生的行為。請注意,__enter__的返回值與with語句的目標或者as後的名字繫結。
1 |
__exit__( self , exception_type, exception_value, traceback):
|
定義了當一個程式碼塊被執行或者終止後,會話管理器應該做什麼。它可以被用來處理異常、執行清理工作或做一些程式碼塊執行完畢之後的日常工作。如果程式碼塊執行成功,exception_type,exception_value,和traceback將會為None。否則,你可以選擇處理這個異常或者是直接交給使用者處理。如果你想處理這個異常的話,請確保__exit__在所有語句結束之後返回True。如果你想讓異常被會話管理器處理的話,那麼就讓其產生該異常。
建立物件描述器
描述器是通過獲取、設定以及刪除的時候被訪問的類。當然也可以改變其它的物件。描述器並不是獨立的。相反,它意味著被一個所有者類持有。當建立面向物件的資料庫或者類,裡面含有相互依賴的屬相時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數值,或者表示某個資料的附加屬性。
為了成為一個描述器,一個類必須至少有__get__,__set__,__delete__方法被實現:
1 |
__get__( self , instance, owner):
|
定義了當描述器的值被取得的時候的行為。instance是擁有該描述器物件的一個例項。owner是擁有者本身
1 |
__set__( self , instance, value):
|
定義了當描述器的值被改變的時候的行為。instance是擁有該描述器類的一個例項。value是要設定的值。
1 |
__delete__( self , instance):
|
定義了當描述器的值被刪除的時候的行為。instance是擁有該描述器物件的一個例項。
下面是一個描述器的例項:單位轉換。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# -*- 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所發揮作用的地方。
1 |
__copy__( self ):
|
定義了當對你的類的例項呼叫copy.copy()時所產生的行為。copy.copy()返回了你的物件的一個淺拷貝——這意味著,當例項本身是一個新例項時,它的所有資料都被引用了——例如,當一個物件本身被複制了,它的資料仍然是被引用的(因此,對於淺拷貝中資料的更改仍然可能導致資料在原始物件的中的改變)。
1 |
__deepcopy__( self , memodict = {}):
|
定義了當對你的類的例項呼叫copy.deepcopy()時所產生的行為。copy.deepcopy()返回了你的物件的一個深拷貝——物件和其資料都被拷貝了。memodict是對之前被拷貝的物件的一個快取——這優化了拷貝過程並且阻止了對遞迴資料結構拷貝時的無限遞迴。當你想要進行對一個單獨的屬性進行深拷貝時,呼叫copy.deepcopy(),並以memodict為第一個引數。
其他方法
用於比較的魔術方法
__cmp__(self, other) 是比較方法裡面最基本的的魔法方法
__eq__(self, other) 定義相等符號的行為,==
__ne__(self,other) 定義不等符號的行為,!=
__lt__(self,other) 定義小於符號的行為,<
__gt__(self,other) 定義大於符號的行為,>
__le__(self,other) 定義小於等於符號的行為,<=
__ge__(self,other) 定義大於等於符號的行為,>=
數值計算的魔術方法
單目運算子和函式
__pos__(self) 實現一個取正數的操作
__neg__(self) 實現一個取負數的操作
__abs__(self) 實現一個內建的abs()函式的行為
__invert__(self) 實現一個取反操作符(~操作符)的行為
__round__(self, n) 實現一個內建的round()函式的行為
__floor__(self) 實現math.floor()的函式行為
__ceil__(self) 實現math.ceil()的函式行為
__trunc__(self) 實現math.trunc()的函式行為
雙目運算子或函式
__add__(self, other) 實現一個加法
__sub__(self, other) 實現一個減法
__mul__(self, other) 實現一個乘法
__floordiv__(self, other) 實現一個“//”操作符產生的整除操作()
__div__(self, other) 實現一個“/”操作符代表的除法操作
__truediv__(self, other) 實現真實除法
__mod__(self, other) 實現一個“%”操作符代表的取模操作
__divmod__(self, other) 實現一個內建函式divmod()
__pow__ 實現一個指數操作(“**”操作符)的行為
__lshift__(self, other) 實現一個位左移操作(<<)的功能
__rshift__(self, other) 實現一個位右移操作(>>)的功能
__and__(self, other) 實現一個按位進行與操作(&)的行為
__or__(self, other) 實現一個按位進行或操作的行為
__xor__(self, other) __xor__(self, other)
增量運算
__iadd__(self, other) 加法賦值
__isub__(self, other) 減法賦值
__imul__(self, other) 乘法賦值
__ifloordiv__(self, other) 整除賦值,地板除,相當於 //= 運算子
__idiv__(self, other) 除法賦值,相當於 /= 運算子
__itruediv__(self, other) 真除賦值
__imod_(self, other) 模賦值,相當於 %= 運算子
__ipow__ 乘方賦值,相當於 **= 運算子
__ilshift__(self, other) 左移賦值,相當於 <<= 運算子
__irshift__(self, other) 左移賦值,相當於 >>= 運算子
__iand__(self, other) 與賦值,相當於 &= 運算子
__ior__(self, other) 或賦值
__ixor__(self, other) 異或運算子,相當於 ^= 運算子
型別轉換
__int__(self) 轉換成整型
__long__(self) 轉換成長整型
__float__(self) 轉換成浮點型
__complex__(self) 轉換成 複數型
__oct__(self) 轉換成八進位制
__hex__(self) 轉換成十六進位制
__index__(self) 如果你定義了一個可能被用來做切片操作的數值型,你就應該定義__index__
__trunc__(self) 當 math.trunc(self) 使用時被呼叫__trunc__返回自身型別的整型擷取
__coerce__(self, other) 執行混合型別的運算
相關推薦
python中list方法詳解說明
序號 分類 關鍵字/函式/方法 描述 1 新增 list.insert(索引,資料) 在指定位置插入資料 list.append(資料)
python中new方法詳解。
new_ 方法是什麼? __new__方法其實就是建立物件的方法 new()方法是在類準備將自身例項化時呼叫。 一個類可以有多個位置引數和多個命名引數,而在例項化開始之後,在呼叫 init()方法之前,Python首先呼叫new()方法: def new(cls, *args, **kw
Python中的魔術方法詳解2
介紹 在Python中,所有以“__”雙下劃線包起來的方法,都統稱為“Magic Method”,中文稱『魔術方法』,例如類的初始化方法 __init__ ,Python中所有的魔術方法均在官方文件中有相應描述,但是對於官方的描述比較混亂而且組織比較鬆散。很難找到有一個例子。構造和初始化 每個Python
Python中的魔術方法詳解
介紹 在Python中,所有以“__”雙下劃線包起來的方法,都統稱為“Magic Method”,中文稱『魔術方法』,例如類的初始化方法 __init__ ,Python中所有的魔術方法均在官方文件中有相應描述,但是對於官方的描述比較混亂而且組織比較鬆散。很難找到有一個例子。 構造和初始化 每個Pyt
JavaScript中getBoundingClientRect()方法詳解
script otto java chrome 頁面 支持 urn client fire getBoundingClientRect() 這個方法返回一個矩形對象,包含四個屬性:left、top、right和bottom。分別表示元素各邊與頁面上邊和左邊的距離。
Python中enumerate用法詳解
num readline 文件的 簡單 += () 用法 字符 計數 enumerate()是python的內置函數、適用於python2.x和python3.xenumerate在字典上是枚舉、列舉的意思enumerate參數為可遍歷/可叠代的對象(如列表、字符串)enu
Python List insert()方法詳解
返回 -- 定位 abs pre insert 詳解 頭部 表示 1.功能insert()函數用於將指定對象插入列表的指定位置。 2.語法list.insert(index, obj) 3.參數index: 對象obj需要插入的索引位置。obj: 插入列表中的對象。
boost庫中sleep方法詳解
seconds boost庫 color sys 在線 TP 方法 轉載 details 博客轉載自:https://blog.csdn.net/huang_xw/article/details/8453506 boost庫中sleep有兩個方法: 1. 這個方法只能在線程
python中魔術方法簡述
圖片 water pro 程序 基類 get ffffff http cbc 魔術方法:***實例化:new :實例化一個對象 方法很少使用,一般使用return super().))new(cls)基類ibject方法來創建實例並返回。 hash:返回一個整數,如
python中閉包詳解
ner copy bsp div 執行 gpo 註意 outer 在一起 閉包這個概念好難理解,身邊朋友們好多都稀裏糊塗的,稀裏糊塗的林老冷希望寫下這篇文章能夠對稀裏糊塗的夥伴們有一些幫助~ 請大家跟我理解一下,如果在一個函數的內部定義了另一個函數,外部的我們叫
python~random常用方法詳解
random常用方法詳解 random:生成0到1之間的隨機浮點數 >>> import random >>> random.random() 0.7752409555996718 uniform(a,
JS中陣列方法詳解
JS中陣列的方法有很多,但是自己一直沒有抽時間進行整理分類,故單獨寫一篇博文,對目前我所掌握的JS中陣列相關的方法進行整理,夯實一下自己的基礎。 我將陣列相關的方法分為兩類 1.方法會改變原陣列 相應的方法有:shift、unshift、pop、push、rev
Java 的String中intern方法詳解及測試樣例
intern public String intern() 返回字串物件的規範化表示形式。 一個初始時為空的字串池,它由類 String 私有地維護。 當呼叫 intern 方法時,如果池已經包含一個等於此 String 物件的字串(該物件由 equals(Obj
python:__getitem__方法詳解
__getitem__(self,key): 這個方法返回與指定鍵想關聯的值。對序列來說,鍵應該是0~n-1的整數,其中n為序列的長度。對對映來說,鍵可以是任何型別。 class Tag: def __init__(self,id): self.id=id
聊聊Python中的GIL python中的GIL詳解
對於廣大寫Python的人來說,GIL(Global Interpreter Lock, 全域性直譯器鎖)肯定不陌生,但未必清楚GIL的歷史和全貌是怎樣的,今天我們就來梳理一下GIL。 1. 什麼是GIL GIL的全稱是 Global Interpreter Lock,全域性直譯器鎖。之所以叫這
Python內建方法詳解
1. 簡介 本指南主題是 魔法方法 。 所以,為了修復我感知的Python文件的缺陷,我開始提供更為通俗的,有示例支援的Python魔法方法指南。我一開始 寫了一些博文,現在我把這些博文總起來成為一篇指南。 希望你喜歡這篇指南,一篇友好,通俗易懂的Python魔法方法指南! 2.
python中socket模組詳解
socket模組簡介 網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個socket。socket通常被叫做“套接字”,用於描述IP地址和埠,是一個通訊鏈的控制代碼,可以用來實現不同虛擬機器或不同計算機之間的通訊。在Internet上的主機一般運行了多個服務
Python中的generator詳解
注:本文在原文基礎上做了一點點修改,僅僅作為個人理解與記憶,建議直接檢視原文。 generator使用場景: 1 當我們需要一個公用的,按需生成的資料 2 某個事情執行一部分,另一部分在某個事件發生後再執行下一部分,實現非同步。 注意事項:
python中psutil模組詳解
在Python中獲取系統資訊的另一個好辦法是使用psutil這個第三方模組。顧名思義,psutil = process and system utilities,它不僅可以通過一兩行程式碼實現系統監控,還可以跨平臺使用,支援Linu
Python中函式的詳解
Python中的函式作用跟其他語言的函式作用基本相同,都是帶名字的程式碼塊,用於一些完成具體的工作。 要執行函式定義的特殊任務,可呼叫該函式。需要在程式中,多次執行同一項任務的函式,無需反覆編寫完成該任務的程式碼,而只需要呼叫執行該任務的函式即可。 首先:定義一