Python面向對象-魔術方法
實例化相關
對象的實例化過程如下所示:
示例:
class Programer():
def __new__(cls,*args,**kwargs):
print('call __new__ method')
print(args)
return super(Programer,cls).__new__(cls)
def __init__(self,name,age):
print('call __init__ method')
self.name = name
self .age = age
if __name__ == '__main__':
programer = Programer('Albert',25)
print(programer.__dict__)
運行結果:
call __new__ method
('Albert', 25)
call __init__ method
{'name': 'Albert', 'age': 25}
在這個過程中:
__new__(cls[, ...])
__new__
是在一個對象實例化的時候所調用的第一個方法- 它的第一個參數是這個類,其他的參數是用來直接傳遞給
__init__
方法 __new__
決定是否要使用該__init__
方法,因為__new__
可以調用其他類的構造方法或者直接返回別的實例對象來作為本類的實例,如果__new__
沒有返回實例對象,則__init__
不會被調用__new__
主要是用於繼承一個不可變的類型比如一個tuple
或者string
__init__(self[, ...])
- 構造器,當一個實例被創建的時候調用的初始化方法
運算相關
- 比較操作符
__lt__(self, other)
定義小於號的行為:x < y
調用x.__lt__(y)
__le__(self, other)
定義小於等於號的行為:x <= y
調用x.__le__(y)
__eq__(self, other)
定義等於號的行為:x == y
調用x.__eq__(y)
__ne__(self, other)
定義不等號的行為:x != y
調用x.__ne__(y)
__gt__(self, other)
定義大於號的行為:x > y
調用x.__gt__(y)
__ge__(self, other)
定義大於等於號的行為:x >= y
調用x.__ge__(y)
- 算數運算符
__add__(self, other)
定義加法的行為:+
__sub__(self, other)
定義減法的行為:-
__mul__(self, other)
定義乘法的行為:*
__truediv__(self, other)
定義真除法的行為:/
__floordiv__(self, other)
定義整數除法的行為://
__mod__(self, other)
定義取模算法的行為:%
__divmod__(self, other)
定義當被divmod()
調用時的行為__pow__(self, other[, modulo])
定義當被power()
調用或**
運算時的行為__lshift__(self, other)
定義按位左移位的行為:<<
__rshift__(self, other)
定義按位右移位的行為:>>
__and__(self, other)
定義按位與操作的行為:&
__xor__(self, other)
定義按位異或操作的行為:^
__or__(self, other)
定義按位或操作的行為:\|
示例:
class Programer(object):
def __init__(self,name,age):
self.name = name
if isinstance(age,int):
self.age = age
else:
raise Exception('age must be int')
def __eq__(self,other):
if isinstance(other,Programer): # 首先判斷是否Programer對象
if self.age == other.age:
return True
else:
return False
else:
raise Exception('The type of object must be Programer')
def __add__(self,other):
if isinstance(other,Programer):
return self.age + other.age
else:
raise Exception('The type of object must be Programer')
if __name__ == '__main__':
p1 = Programer('Albert',25)
p2 = Programer('Bill',30)
print(p1==p2)
print(p1+p2)
運行結果:
False
55
展現相關
__str__
- 把對象轉換成適合人看的字符串
__repr__
- 把對象轉換成適合機器看的字符串,即可以使用
eval
函數運行
- 把對象轉換成適合機器看的字符串,即可以使用
__dir__(self)
- 定義當
dir()
被調用時的行為
- 定義當
示例:
class Programer(object):
def __init__(self,name,age):
self.name = name
if isinstance(age,int):
self.age = age
else:
raise Exception('age must be int')
def __str__(self):
return '%s is %s years old'%(self.name,self.age)
def __dir__(self):
return self.__dict__.keys()
if __name__ == '__main__':
p = Programer('Albert',25)
print(p)
print(dir(p))
運行結果:
Albert is 25 years old
['age', 'name']
屬性控制相關
設置對象屬性
__setattr__(self, name, value)
獲取對象屬性
__getattr__(self, name)
- 當查詢不到時才會調用
__getattribute__(self, name)
- 每次訪問時一定會被調到
刪除對象屬性
__del__(self)
限制添加屬性
__slots__
由於
Python
是動態語言,任何實例在運行期都可以動態地添加屬性。如果要限制添加的屬性,例如,
Student
類只允許添加name
、gender
和score
這3個屬性,就可以利用Python
的一個特殊的__slots__
來實現。顧名思義,
__slots__
是指一個類允許的屬性列表:
示例1
class Programer(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __getattribute__(self,name):
# return getattr(self,name) # 會產生無限遞歸
# 調用父類的getattribute方法,不會產生無限遞歸
return super(Programer,self).__getattribute__(name)
def __setattr__(self,name,value):
# setattr(self,name,value) # 會產生無限遞歸
self.__dict__[name] = value
if __name__ == '__main__':
p = Programer('Albert',25)
print(p.name)
運行結果:
Albert
示例2
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
現在,對實例進行操作:
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'
__slots__
的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態的屬性,使用__slots__
也能節省內存。
示例3
假設Person類通過__slots__
定義了name
和gender
,請在派生類Student
中通過__slots__
繼續添加score
的定義,使Student
類可以實現name
、gender
和score
3個屬性。
Student
類的__slots__
只需要包含Person
類不包含的score
屬性即可。
參考代碼:
class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
__slots__ = ('score',)
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print s.score
容器相關
len__(self)
返回容器的長度,可變和不可變類型都需要實現。
__getitem__(self, key)
定義對容器中某一項使用
self[key]
的方式進行讀取操作時的行為。這也是可變和不可變容器類型都需要實現的一個方法。它應該在鍵的類型錯誤式產生 TypeError 異常,同時在沒有與鍵值相匹配的內容時產生KeyError
異常。__setitem__(self, key)
定義對容器中某一項使用
self[key]
的方式進行賦值操作時的行為。它是可變容器類型必須實現的一個方法,同樣應該在合適的時候產生KeyError
和TypeError
異常。__iter__(self, key)
它應該返回當前容器的一個叠代器。叠代器以一連串內容的形式返回,最常見的是使用
iter()
函數調用,以及在類似for x in container:
的循環中被調用。叠代器是他們自己的對象,需要定義__iter__
方法並在其中返回自己。__reversed__(self)
定義了對容器使用
reversed()
內建函數時的行為。它應該返回一個反轉之後的序列。當你的序列類是有序時,類似列表和元組,再實現這個方法,__contains__(self, item)
__contains__
定義了使用 in 和 not in 進行成員測試時類的行為。你可能好奇為什麽這個方法不是序列協議的一部分,原因是,如果 contains 沒有定義,Python就會叠代整個序列,如果找到了需要的一項就返回 True 。__missing__(self ,key)
__missing__
在字典的子類中使用,它定義了當試圖訪問一個字典中不存在的鍵時的行為(目前為止是指字典的實例,例如我有一個字典d
,“george”
不是字典中的一個鍵,當試圖訪問d[“george’]
時就會調用d.__missing__(“george”) )
。
示例:
class FunctionalList:
'''一個列表的封裝類,實現了一些額外的函數式
方法,例如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 reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
# 取得第一個元素
return self.values[0]
def tail(self):
# 取得除第一個元素外的所有元素
return self.valuse[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]
就是這些,一個(微不足道的)有用的例子,向你展示了如何實現自己的序列。當然啦,自定義序列有更大的用處,而且絕大部分都在標準庫中實現了(
Python
是自帶電池的,記得嗎?),像Counter
,OrderedDict
和NamedTuple
。
上下文相關
在Python 2.5
中引入了一個全新的關鍵詞,隨之而來的是一種新的代碼復用方法—— with 聲明
。上下文管理的概念在Python
中並不是全新引入的(之前它作為標準庫的一部分實現),直到PEP 343
被接受,它才成為一種一級的語言結構。可能你已經見過這種寫法了:
with open('foo.txt') as bar:
# 使用bar進行某些操作
當對象使用 with
聲明創建時,上下文管理器允許類做一些設置和清理工作。上下文管理器的行為由下面兩個魔法方法所定義:
__enter__(self)
定義使用
with 聲明
創建的語句塊最開始上下文管理器應該做些什麽。註意__enter__
的返回值會賦給with 聲明
的目標,也就是as
之後的東西。__exit__(self, exception_type, exception_value, traceback)
定義當
with 聲明
語句塊執行完畢(或終止)時上下文管理器的行為。它可以用來處理異常,進行清理,或者做其他應該在語句塊結束之後立刻執行的工作。如果語句塊順利執行,exception_type
,exception_value
和traceback
會是None
。否則,你可以選擇處理這個異常或者讓用戶來處理。如果你想處理異常,確保__exit__
在完成工作之後返回True
。如果你不想處理異常,那就讓它發生吧。
對一些具有良好定義的且通用的設置和清理行為的類,__enter__
和 __exit__
會顯得特別有用。你也可以使用這幾個方法來創建通用的上下文管理器,用來包裝其他對象。下面是一個例子:
class Closer:
'''一個上下文管理器,可以在with語句中
使用close()自動關閉對象'''
def __init__(self, obj):
self.obj = obj
def __enter__(self, obj):
return self.obj # 綁定到目標
def __exit__(self, exception_type, exception_value, traceback):
try:
self.obj.close()
except AttributeError: # obj不是可關閉的
print 'Not closable.'
return True # 成功地處理了異常
這是一個 Closer
在實際使用中的例子,使用一個FTP
連接來演示(一個可關閉的socket
):
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
... conn.dir()
...
# 為了簡單,省略了某些輸出
>>> conn.dir()
# 很長的 AttributeError 信息,不能使用一個已關閉的連接
>>> with Closer(int(5)) as i:
... i += 1
...
Not closable.
>>> i
6
看到我們的包裝器是如何同時優雅地處理正確和不正確的調用了嗎?這就是上下文管理器和魔法方法的力量。Python
標準庫包含一個 contextlib
模塊,裏面有一個上下文管理器 contextlib.closing()
基本上和我們的包裝器完成的是同樣的事情(但是沒有包含任何當對象沒有close()
方法時的處理)。
復制相關
有些時候,特別是處理可變對象時,你可能想拷貝一個對象,改變這個對象而不影響原有的對象。這時就需要用到Python
的 copy
模塊了。然而(幸運的是),Python
模塊並不具有感知能力, 因此我們不用擔心某天基於Linux
的機器人崛起。但是我們的確需要告訴Python
如何有效率地拷貝對象。
__copy__(self)
定義對類的實例使用
copy.copy()
時的行為。copy.copy()
返回一個對象的淺拷貝,這意味著拷貝出的實例是全新的,然而裏面的數據全都是引用的。也就是說,對象本身是拷貝的,但是它的數據還是引用的(所以淺拷貝中的數據更改會影響原對象)。__deepcopy__(self, memodict=)
定義對類的實例使用
copy.deepcopy()
時的行為。copy.deepcopy()
返回一個對象的深拷貝,這個對象和它的數據全都被拷貝了一份。memodict
是一個先前拷貝對象的緩存,它優化了拷貝過程,而且可以防止拷貝遞歸數據結構時產生無限遞歸。當你想深拷貝一個單獨的屬性時,在那個屬性上調用copy.deepcopy()
,使用memodict
作為第一個參數。
這些魔法方法有什麽用武之地呢?像往常一樣,當你需要比默認行為更加精確的控制時。例如,如果你想拷貝一個對象,其中存儲了一個字典作為緩存(可能會很大),拷貝緩存可能是沒有意義的。如果這個緩存可以在內存中被不同實例共享,那麽它就應該被共享。
持久化相關
Pickle
不僅僅可以用於內建類型,任何遵守pickle
協議的類都可以被pickle
。Pickle
協議有四個可選方法,可以讓類自定義它們的行為(這和C語言擴展略有不同,那不在我們的討論範圍之內)。
__getinitargs__(self)
如果你想讓你的類在反
pickle
時調用__init__
,你可以定義__getinitargs__(self)
,它會返回一個參數元組,這個元組會傳遞給__init__
。註意,這個方法只能用於舊式類。__getnewargs__(self)
對新式類來說,你可以通過這個方法改變類在反pickle時傳遞給
__new__
的參數。這個方法應該返回一個參數元組。__getstate__(self)
你可以自定義對象被
pickle
時被存儲的狀態,而不使用對象的__dict__
屬性。 這個狀態在對象被反pickle
時會被__setstate__
使用。__setstate__(self)
當一個對象被反
pickle
時,如果定義了__setstate__
,對象的狀態會傳遞給這個魔法方法,而不是直接應用到對象的__dict__
屬性。這個魔法方法和__getstate__
相互依存:當這兩個方法都被定義時,你可以在Pickle
時使用任何方法保存對象的任何狀態。__reduce__(self)
當定義擴展類型時(也就是使用
Python
的C語言API
實現的類型),如果你想pickle
它們,你必須告訴Python如何pickle
它們。__reduce__
被定義之後,當對象被Pickle
時就會被調用。它要麽返回一個代表全局名稱的字符串,Pyhton會查找它並pickle
,要麽返回一個元組。這個元組包含2到5個元素,其中包括:一個可調用的對象,用於重建對象時調用;一個參數元素,供那個可調用對象使用;被傳遞給__setstate__
的狀態(可選);一個產生被pickle
的列表元素的叠代器(可選);一個產生被pickle的字典元素的叠代器(可選);__reduce_ex__(self)
__reduce_ex__
的存在是為了兼容性。如果它被定義,在pickle時__reduce_ex__
會代替__reduce__
被調用。__reduce__
也可以被定義,用於不支持__reduce_ex__
的舊版pickle
的API調用。
示例
我們的例子是 Slate ,它會記住它的值曾經是什麽,以及那些值是什麽時候賦給它的。然而 每次被pickle時它都會變成空白,因為當前的值不會被存儲:
import time
class Slate:
'''存儲一個字符串和一個變更日誌的類
每次被pickle都會忘記它當前的值'''
def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}
def change(self, new_value):
# 改變當前值,將上一個值記錄到歷史
self.history[self.last_change] = self.value
self.value = new_value)
self.last_change = time.asctime()
def print_change(self):
print 'Changelog for Slate object:'
for k,v in self.history.items():
print '%s\t %s' % (k,v)
def __getstate__(self):
# 故意不返回self.value或self.last_change
# 我們想在反pickle時得到一個空白的slate
return self.history
def __setstate__(self):
# 使self.history = slate,last_change
# 和value為未定義
self.history = state
self.value, self.last_change = None, None
Python面向對象-魔術方法