1.簡介
魔法方法是python內(nèi)置方法,不需要主動(dòng)調(diào)用。
魔法方法存在的目的是為了給python的解釋器進(jìn)行調(diào)用。
幾乎每個(gè)魔法方法都有一個(gè)對應(yīng)的內(nèi)置函數(shù),或者運(yùn)算符。
當(dāng)我們對這個(gè)對象使用這些內(nèi)置函數(shù)或者運(yùn)算符時(shí),就會(huì)調(diào)用類中的對應(yīng)魔法方法,可以理解為重寫內(nèi)置函數(shù)。
2.詳細(xì)介紹
2.1 構(gòu)造和初始化
_init_我們很熟悉了,它在對象初始化的時(shí)候調(diào)用,我們一般將它理解為"構(gòu)造函數(shù)"。
實(shí)際上, 當(dāng)我們實(shí)例化時(shí),如:x = SomeClass(),其調(diào)用_init_并不是第一個(gè)執(zhí)行的, _new_才是。所以準(zhǔn)確來說,是_new_和_init_共同構(gòu)成了"構(gòu)造函數(shù)".
_new_是用來創(chuàng)建類并返回這個(gè)類的實(shí)例, 而_init_只是將傳入的參數(shù)來初始化該實(shí)例.
_new_在創(chuàng)建一個(gè)實(shí)例的過程中必定會(huì)被調(diào)用,但_init_就不一定,比如通過pickle.load的方式反序列化一個(gè)實(shí)例時(shí)就不會(huì)調(diào)用_init_。
_new_方法總是需要返回該類的一個(gè)實(shí)例,而_init_不能返回除了None的任何值。比如下面例子:
classFoo(object):
def__init__(self):
print 'foo __init__'
return None # 必須返回None,否則拋TypeError
def__del__(self):
print 'foo __del__'
實(shí)際中,你很少會(huì)用到_new_,除非你希望能夠控制類的創(chuàng)建。
如果要講解_new_,往往需要牽扯到metaclass(元類)的介紹。
對于new的重載,Python文檔中也有了詳細(xì)的介紹。
2.2 析構(gòu)函數(shù)
在對象的生命周期結(jié)束時(shí), _del_會(huì)被調(diào)用,可以將_del_理解為"析構(gòu)函數(shù)".
_del_定義的是當(dāng)一個(gè)對象進(jìn)行垃圾回收時(shí)候的行為。
有一點(diǎn)容易被人誤解, 實(shí)際上,x._del_() 并不是對于del x的實(shí)現(xiàn),但是往往執(zhí)行del x時(shí)會(huì)調(diào)用x._del_().
怎么來理解這句話呢? 繼續(xù)用上面的Foo類的代碼為例:
foo = Foo()
foo.__del__()
print foo
del foo
print foo # NameError, foo is not defined
如果調(diào)用了foo._del_(),對象本身仍然存在. 但是調(diào)用了del foo, 就再也沒有foo這個(gè)對象了.
請注意,如果解釋器退出的時(shí)候?qū)ο筮€存在,就不能保證 _del_ 被確切的執(zhí)行了。所以_del_并不能替代良好的編程習(xí)慣。
比如,在處理socket時(shí),及時(shí)關(guān)閉結(jié)束的連接。
2.3 屬性訪問控制
總有人要吐槽Python缺少對于類的封裝,比如希望Python能夠定義私有屬性,然后提供公共可訪問的getter和 setter。Python其實(shí)可以通過魔術(shù)方法來實(shí)現(xiàn)封裝。
_getattr_(self, name)
該方法定義了你試圖訪問一個(gè)不存在的屬性時(shí)的行為。因此,重載該方法可以實(shí)現(xiàn)捕獲錯(cuò)誤拼寫然后進(jìn)行重定向, 或者對一些廢棄的屬性進(jìn)行警告。
_setattr_(self, name, value)
_setattr_ 是實(shí)現(xiàn)封裝的解決方案,它定義了你對屬性進(jìn)行賦值和修改操作時(shí)的行為。
不管對象的某個(gè)屬性是否存在,它都允許你為該屬性進(jìn)行賦值,因此你可以為屬性的值進(jìn)行自定義操作。有一點(diǎn)需要注意,實(shí)現(xiàn)_setattr_時(shí)要避免"無限遞歸"的錯(cuò)誤,下面的代碼示例中會(huì)提到。
_delattr_(self, name)
_delattr_與_setattr_很像,只是它定義的是你刪除屬性時(shí)的行為。
實(shí)現(xiàn)_delattr_是同時(shí)要避免"無限遞歸"的錯(cuò)誤。
_getattribute_(self, name)
_getattribute_定義了你的屬性被訪問時(shí)的行為,相比較,_getattr_只有該屬性不存在時(shí)才會(huì)起作用。
因此,在支持_getattribute_的Python版本,調(diào)用_getattr_前必定會(huì)調(diào)用 _getattribute_。_getattribute_同樣要避免"無限遞歸"的錯(cuò)誤。
需要提醒的是,最好不要嘗試去實(shí)現(xiàn)_getattribute_,因?yàn)楹苌僖姷竭@種做法,而且很容易出bug。
例子說明_setattr_的無限遞歸錯(cuò)誤:
def__setattr__(self, name, value):
self.name = value
# 每一次屬性賦值時(shí), __setattr__都會(huì)被調(diào)用,因此不斷調(diào)用自身導(dǎo)致無限遞歸了。
因此正確的寫法應(yīng)該是:
def__setattr__(self, name, value):
self.__dict__[name] = value
_delattr_如果在其實(shí)現(xiàn)中出現(xiàn)del self.name 這樣的代碼也會(huì)出現(xiàn)"無限遞歸"錯(cuò)誤,這是一樣的原因。
下面的例子很好的說明了上面介紹的4個(gè)魔術(shù)方法的調(diào)用情況:
class Access(object):
def__getattr__(self, name):
print '__getattr__'
return super(Access, self).__getattr__(name)
def__setattr__(self, name, value):
print '__setattr__'
return super(Access, self).__setattr__(name, value)
def__delattr__(self, name):
print '__delattr__'
return super(Access, self).__delattr__(name)
def__getattribute__(self, name):
print '__getattribute__'
return super(Access, self).__getattribute__(name)
access = Access()
access.attr1 = True # __setattr__調(diào)用
access.attr1 # 屬性存在,只有__getattribute__調(diào)用
try:
access.attr2 # 屬性不存在, 先調(diào)用__getattribute__, 后調(diào)用__getattr__
except AttributeError:
pass
del access.attr1 # __delattr__調(diào)用
2.4 描述符對象
我們從一個(gè)例子來入手,介紹什么是描述符,并介紹_get_,_set_, _delete_ 的使用。(放在這里介紹是為了跟上一小節(jié)介紹的魔術(shù)方法作對比)
我們知道,距離既可以用單位"米"表示,也可以用單位"英尺"表示。
現(xiàn)在我們定義一個(gè)類來表示距離,它有兩個(gè)屬性: 米和英尺。
class Meter(object):
'''Descriptor for a meter.'''
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):
'''Descriptor for a foot.'''
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()
foot = Foot()
d = Distance()
print d.meter, d.foot # 0.0, 0.0
d.meter = 1
print d.meter, d.foot # 1.0 3.2808
d.meter = 2
print d.meter, d.foot # 2.0 6.5616
在上面例子中,在還沒有對Distance的實(shí)例賦值前, 我們認(rèn)為meter和foot應(yīng)該是各自類的實(shí)例對象, 但是輸出卻是數(shù)值。這是因?yàn)?strong>get發(fā)揮了作用.
我們只是修改了meter,并且將其賦值成為int,但foot也修改了。這是set發(fā)揮了作用.
描述器對象(Meter、Foot)不能獨(dú)立存在, 它需要被另一個(gè)所有者類(Distance)所持有。
描述器對象可以訪問到其擁有者實(shí)例的屬性,比如例子中Foot的instance.meter。
在面向?qū)ο缶幊虝r(shí),如果一個(gè)類的屬性有相互依賴的關(guān)系時(shí),使用描述器來編寫代碼可以很巧妙的組織邏輯。
在Django的ORM中, models.Model中的IntegerField等, 就是通過描述器來實(shí)現(xiàn)功能的。
一個(gè)類要成為描述器,必須實(shí)現(xiàn)get, set, delete 中的至少一個(gè)方法。下面簡單介紹下:
get(self, instance, owner)
參數(shù)instance是擁有者類的實(shí)例。參數(shù)owner是擁有者類本身。get在其擁有者對其讀值的時(shí)候調(diào)用。
set(self, instance, value)
set在其擁有者對其進(jìn)行修改值的時(shí)候調(diào)用。
delete(self, instance)
delete在其擁有者對其進(jìn)行刪除的時(shí)候調(diào)用。
2.5 構(gòu)造自定義容器(Container)
在Python中,常見的容器類型有: dict, tuple, list, string。
其中tuple, string是不可變?nèi)萜?,dict, list是可變?nèi)萜鳌?br>
可變?nèi)萜骱筒豢勺內(nèi)萜鞯膮^(qū)別在于,不可變?nèi)萜饕坏┵x值后,不可對其中的某個(gè)元素進(jìn)行修改。
比如定義了l = [1, 2, 3]和t = (1, 2, 3)后, 執(zhí)行l(wèi)[0] = 0是可以的,但執(zhí)行t[0] = 0則會(huì)報(bào)錯(cuò)。
如果我們要自定義一些數(shù)據(jù)結(jié)構(gòu),使之能夠跟以上的容器類型表現(xiàn)一樣,那就需要去實(shí)現(xiàn)某些協(xié)議。
這里的協(xié)議跟其他語言中所謂的"接口"概念很像,一樣的需要你去實(shí)現(xiàn)才行,只不過沒那么正式而已。
如果要自定義不可變?nèi)萜黝愋停恍枰x_len_ 和 _getitem_方法;
如果要自定義可變?nèi)萜黝愋?,還需要在不可變?nèi)萜黝愋偷幕A(chǔ)上增加定義_setitem_ 和 _delitem_。
如果你希望你的自定義數(shù)據(jù)結(jié)構(gòu)還支持"可迭代", 那就還需要定義_iter_。
_len_(self)
需要返回?cái)?shù)值類型,以表示容器的長度。該方法在可變?nèi)萜骱筒豢勺內(nèi)萜髦斜仨殞?shí)現(xiàn)。
_getitem_(self, key)
當(dāng)你執(zhí)行self[key]的時(shí)候,調(diào)用的就是該方法。該方法在可變?nèi)萜骱筒豢勺內(nèi)萜髦幸捕急仨殞?shí)現(xiàn)。
調(diào)用的時(shí)候,如果key的類型錯(cuò)誤,該方法應(yīng)該拋出TypeError;
如果沒法返回key對應(yīng)的數(shù)值時(shí),該方法應(yīng)該拋出ValueError。
_setitem_(self, key, value)
當(dāng)你執(zhí)行self[key] = value時(shí),調(diào)用的是該方法。
_delitem_(self, key)
當(dāng)你執(zhí)行del self[key]的時(shí)候,調(diào)用的是該方法。
_iter_(self)
該方法需要返回一個(gè)迭代器(iterator)。當(dāng)你執(zhí)行for x in container: 或者使用iter(container)時(shí),該方法被調(diào)用。
_reversed_(self)
如果想要該數(shù)據(jù)結(jié)構(gòu)被內(nèi)建函數(shù)reversed()支持,就還需要實(shí)現(xiàn)該方法。
_contains_(self, item)
如果定義了該方法,那么在執(zhí)行item in container 或者 item not in container時(shí)該方法就會(huì)被調(diào)用。
如果沒有定義,那么Python會(huì)迭代容器中的元素來一個(gè)一個(gè)比較,從而決定返回True或者False。
_missing_(self, key)
dict字典類型會(huì)有該方法,它定義了key如果在容器中找不到時(shí)觸發(fā)的行為。
比如d = {'a': 1}, 當(dāng)你執(zhí)行d[notexist]時(shí),d.missing('notexist')就會(huì)被調(diào)用。
下面舉例,使用上面講的魔術(shù)方法來實(shí)現(xiàn)Haskell語言中的一個(gè)數(shù)據(jù)結(jié)構(gòu)。
# -*- coding: utf-8 -*-
class FunctionalList:
''' 實(shí)現(xiàn)了內(nèi)置類型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):
# 獲取第一個(gè)元素
return self.values[0]
def tail(self):
# 獲取第一個(gè)元素之后的所有元素
return self.values[1:]
def init(self):
# 獲取最后一個(gè)元素之前的所有元素
return self.values[:-1]
def last(self):
# 獲取最后一個(gè)元素
return self.values[-1]
def drop(self, n):
# 獲取所有元素,除了前N個(gè)
return self.values[n:]
def take(self, n):
# 獲取前N個(gè)元素
return self.values[:n]
我們再舉個(gè)例子,實(shí)現(xiàn)Perl語言的AutoVivification,它會(huì)在你每次引用一個(gè)值未定義的屬性時(shí)為你自動(dòng)創(chuàng)建數(shù)組或者字典。
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __missing__(self, key):
value = self[key] = type(self)()
return value
weather = AutoVivification()
weather['china']['guangdong']['shenzhen'] = 'sunny'
weather['china']['hubei']['wuhan'] = 'windy'
weather['USA']['California']['Los Angeles'] = 'sunny'
print weather
# 結(jié)果輸出:{'china': {'hubei': {'wuhan': 'windy'}, 'guangdong': {'shenzhen': 'sunny'}}, 'USA': {'California': {'Los Angeles': 'sunny'}}}
在Python中,關(guān)于自定義容器的實(shí)現(xiàn)還有更多實(shí)用的例子,但只有很少一部分能夠集成在Python標(biāo)準(zhǔn)庫中,比如Counter,OrderedDict等
2.6 上下文管理
with聲明是從Python2.5開始引進(jìn)的關(guān)鍵詞。你應(yīng)該遇過這樣子的代碼:
with open('foo.txt') as bar:
# do something with bar
在with聲明的代碼段中,我們可以做一些對象的開始操作和清除操作,還能對異常進(jìn)行處理。
這需要實(shí)現(xiàn)兩個(gè)魔術(shù)方法:_enter_ 和 _exit_。
_enter_(self)
_enter_會(huì)返回一個(gè)值,并賦值給as關(guān)鍵詞之后的變量。在這里,你可以定義代碼段開始的一些操作。
_exit_(self, exception_type, exception_value, traceback)
_exit_定義了代碼段結(jié)束后的一些操作,可以這里執(zhí)行一些清除操作,或者做一些代碼段結(jié)束后需要立即執(zhí)行的命令,比如文件的關(guān)閉,socket斷開等。如果代碼段成功結(jié)束,那么exception_type, exception_value, traceback 三個(gè)參數(shù)傳進(jìn)來時(shí)都將為None。如果代碼段拋出異常,那么傳進(jìn)來的三個(gè)參數(shù)將分別為: 異常的類型,異常的值,異常的追蹤棧。
如果_exit_返回True, 那么with聲明下的代碼段的一切異常將會(huì)被屏蔽。
如果_exit_返回None, 那么如果有異常,異常將正常拋出,這時(shí)候with的作用將不會(huì)顯現(xiàn)出來。
舉例說明:
該示例中,IndexError始終會(huì)被隱藏,而TypeError始終會(huì)拋出。
classDemoManager(object):
def __enter__(self):
pass
def __exit__(self, ex_type, ex_value, ex_tb):
if ex_type is IndexError:
print ex_value.__class__
return True
if ex_type is TypeError:
print ex_value.__class__
return # return None
with DemoManager() as nothing:
data = [1, 2, 3]
data[4] # raise IndexError, 該異常被__exit__處理了
with DemoManager() as nothing:
data = [1, 2, 3]
data['a'] # raise TypeError, 該異常沒有被__exit__處理
'''
輸出:
<type 'exceptions.IndexError'>
<type 'exceptions.TypeError'>
Traceback (most recent call last):
...
'''
2.7 對象的序列化
Python對象的序列化操作是pickling進(jìn)行的。pickling非常的重要,以至于Python對此有單獨(dú)的模塊pickle,還有一些相關(guān)的魔術(shù)方法。使用pickling, 你可以將數(shù)據(jù)存儲在文件中,之后又從文件中進(jìn)行恢復(fù)。
下面舉例來描述pickle的操作。從該例子中也可以看出,如果通過pickle.load 初始化一個(gè)對象, 并不會(huì)調(diào)用_init_方法。
# -*- coding: utf-8 -*-
from datetime import datetime
import pickle
class Distance(object):
def__init__(self, meter):
print 'distance __init__'
self.meter = meter
data = {
'foo': [1, 2, 3],
'bar': ('Hello', 'world!'),
'baz': True,
'dt': datetime(2016, 10, 01),
'distance': Distance(1.78),
}
print ('before dump:', data)
with open('data.pkl', 'wb') as jar:
pickle.dump(data, jar) # 將數(shù)據(jù)存儲在文件中
del data
print 'data is deleted!'
with open('data.pkl', 'rb') as jar:
data = pickle.load(jar) # 從文件中恢復(fù)數(shù)據(jù)
print ('after load:', data)
值得一提,從其他文件進(jìn)行pickle.load操作時(shí),需要注意有惡意代碼的可能性。另外,Python的各個(gè)版本之間,pickle文件可能是互不兼容的。
pickling并不是Python的內(nèi)建類型,它支持所有實(shí)現(xiàn)pickle協(xié)議(可理解為接口)的類。pickle協(xié)議有以下幾個(gè)可選方法來自定義Python對象的行為。
_getinitargs_(self)
如果你希望unpickle時(shí),_init_方法能夠調(diào)用,那么就需要定義_getinitargs_, 該方法需要返回一系列參數(shù)的元組,這些參數(shù)就是傳給_init_的參數(shù)。
該方法只對old-style class有效。所謂old-style class,指的是不繼承自任何對象的類,往往定義時(shí)這樣表示: class A:, 而非class A(object):
_getnewargs_(self)
跟_getinitargs_很類似,只不過返回的參數(shù)元組將傳值給_new_
_getstate_(self)
在調(diào)用pickle.dump時(shí),默認(rèn)是對象的_dict_屬性被存儲,如果你要修改這種行為,可以在_getstate_方法中返回一個(gè)state。state將在調(diào)用pickle.load時(shí)傳值給_setstate_
_setstate_(self, state)
一般來說,定義了_getstate_,就需要相應(yīng)地定義_setstate_來對_getstate_返回的state進(jìn)行處理。
_reduce_(self)
如果pickle的數(shù)據(jù)包含了自定義的擴(kuò)展類(比如使用C語言實(shí)現(xiàn)的Python擴(kuò)展類)時(shí),就需要通過實(shí)現(xiàn)reduce方法來控制行為了。由于使用過于生僻,這里就不展開繼續(xù)講解了。
令人容易混淆的是,我們知道, reduce()是Python的一個(gè)內(nèi)建函數(shù), 需要指出_reduce_并非定義了reduce()的行為,二者沒有關(guān)系。
_reduce_ex_(self)
_reduce_ex_ 是為了兼容性而存在的, 如果定義了_reduce_ex, 它將代替_reduce 執(zhí)行。
下面的代碼示例很有意思,我們定義了一個(gè)類Slate(中文是板巖的意思)。這個(gè)類能夠記錄歷史上每次寫入給它的值,但每次pickle.dump時(shí)當(dāng)前值就會(huì)被清空,僅保留了歷史。
# -*- coding: utf-8 -*-
import pickle
import time
classSlate:
'''Class to store a string and a changelog, and forget its value when pickled.'''
def__init__(self, value):
self.value = value
self.last_change = time.time()
self.history = []
defchange(self, new_value):
# 修改value, 將上次的valeu記錄在history
self.history.append((self.last_change, self.value))
self.value = new_value
self.last_change = time.time()
defprint_changes(self):
print 'Changelog for Slate object:'
for k, v in self.history:
print '%s %s' % (k, v)
def__getstate__(self):
# 故意不返回self.value和self.last_change,
# 以便每次unpickle時(shí)清空當(dāng)前的狀態(tài),僅僅保留history
return self.history
def__setstate__(self, state):
self.history = state
self.value, self.last_change = None, None
slate = Slate(0)
time.sleep(0.5)
slate.change(100)
time.sleep(0.5)
slate.change(200)
slate.change(300)
slate.print_changes() # 與下面的輸出歷史對比
with open('slate.pkl', 'wb') as jar:
pickle.dump(slate, jar)
del slate # delete it
with open('slate.pkl', 'rb') as jar:
slate = pickle.load(jar)
print 'current value:', slate.value # None
print slate.print_changes() # 輸出歷史記錄與上面一致
2.8 運(yùn)算符相關(guān)的魔術(shù)方法
運(yùn)算符相關(guān)的魔術(shù)方法實(shí)在太多了,也很好理解,不打算多講。在其他語言里,也有重載運(yùn)算符的操作,所以我們對這些魔術(shù)方法已經(jīng)很了解了。
2.8.1 比較運(yùn)算符
_cmp_(self, other)
如果該方法返回負(fù)數(shù),說明self < other; 返回正數(shù),說明self > other; 返回0說明self == other。
強(qiáng)烈不推薦來定義_cmp_, 取而代之, 最好分別定義_lt_等方法從而實(shí)現(xiàn)比較功能。
_cmp_在Python3中被廢棄了。
_eq_(self, other)
定義了比較操作符==的行為.
_ne_(self, other)
定義了比較操作符!=的行為.
_lt_(self, other)
定義了比較操作符<的行為.
_gt_(self, other)
定義了比較操作符>的行為.
_le_(self, other)
定義了比較操作符<=的行為.
_ge_(self, other)
定義了比較操作符>=的行為.
下面我們定義一種類型Word, 它會(huì)使用單詞的長度來進(jìn)行大小的比較, 而不是采用str的比較方式。
但是為了避免 Word('bar') == Word('foo') 這種違背直覺的情況出現(xiàn),并沒有定義_eq_, 因此Word會(huì)使用它的父類(str)中的_eq_來進(jìn)行比較。
下面的例子中也可以看出: 在編程語言中, 如果a >=b and a <= b, 并不能推導(dǎo)出a == b這樣的結(jié)論。
# -*- coding: utf-8 -*-
class Word(str):
'''存儲單詞的類,定義比較單詞的幾種方法'''
def__new__(cls, word):
# 注意我們必須要用到__new__方法,因?yàn)閟tr是不可變類型
# 所以我們必須在創(chuàng)建的時(shí)候?qū)⑺跏蓟? if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] # 單詞是第一個(gè)空格之前的所有字符
return str.__new__(cls, word)
def__gt__(self, other):
return len(self) > len(other)
def__lt__(self, other):
return len(self) < len(other)
def__ge__(self, other):
return len(self) >= len(other)
def__le__(self, other):
return len(self) <= len(other)
print 'foo < fool:', Word('foo') < Word('fool') # True
print 'foolish > fool:', Word('foolish') > Word('fool') # True
print 'bar >= foo:', Word('bar') >= Word('foo') # True
print 'bar <= foo:', Word('bar') <= Word('foo') # True
print 'bar == foo:', Word('bar') == Word('foo') # False, 用了str內(nèi)置的比較方法來進(jìn)行比較
print 'bar != foo:', Word('bar') != Word('foo') # True
2.8.2 一元運(yùn)算符和函數(shù)
_pos_(self)
實(shí)現(xiàn)了'+'號一元運(yùn)算符(比如+some_object)
_neg_(self)
實(shí)現(xiàn)了'-'號一元運(yùn)算符(比如-some_object)
_invert_(self)
實(shí)現(xiàn)了號(波浪號)一元運(yùn)算符(比如some_object)
_abs_(self)
實(shí)現(xiàn)了abs()內(nèi)建函數(shù).
_round_(self, n)
實(shí)現(xiàn)了round()內(nèi)建函數(shù). 參數(shù)n表示四舍五進(jìn)的精度.
_floor_(self)
實(shí)現(xiàn)了math.floor(), 向下取整.
_ceil_(self)
實(shí)現(xiàn)了math.ceil(), 向上取整.
_trunc_(self)
實(shí)現(xiàn)了math.trunc(), 向0取整.
2.8.3 算術(shù)運(yùn)算符
_add_(self, other)
實(shí)現(xiàn)了加號運(yùn)算.
_sub_(self, other)
實(shí)現(xiàn)了減號運(yùn)算.
_mul_(self, other)
實(shí)現(xiàn)了乘法運(yùn)算.
_floordiv_(self, other)
實(shí)現(xiàn)了//運(yùn)算符.
_div_(self, other)
實(shí)現(xiàn)了/運(yùn)算符. 該方法在Python3中廢棄. 原因是Python3中,division默認(rèn)就是true division.
_truediv_(self, other)
實(shí)現(xiàn)了true division. 只有你聲明了from future import division該方法才會(huì)生效.
_mod_(self, other)
實(shí)現(xiàn)了%運(yùn)算符, 取余運(yùn)算.
_divmod_(self, other)
實(shí)現(xiàn)了divmod()內(nèi)建函數(shù).
_pow_(self, other)
實(shí)現(xiàn)了**操作. N次方操作.
_lshift_(self, other)
實(shí)現(xiàn)了位操作<<.
_rshift_(self, other)
實(shí)現(xiàn)了位操作>>.
_and_(self, other)
實(shí)現(xiàn)了位操作&.
_or_(self, other)
實(shí)現(xiàn)了位操作|
_xor_(self, other)
實(shí)現(xiàn)了位操作^
2.8.4 反算術(shù)運(yùn)算符
這里只需要解釋一下概念即可。
假設(shè)針對some_object這個(gè)對象:
some_object + other
上面的代碼非常正常地實(shí)現(xiàn)了some_object的_add_方法。那么如果遇到相反的情況呢?
other + some_object
這時(shí)候,如果other沒有定義_add方法,但是some_object定義了_radd, 那么上面的代碼照樣可以運(yùn)行。
這里的_radd(self, other)就是_add(self, other)的反算術(shù)運(yùn)算符。
所以,類比的,我們就知道了更多的反算術(shù)運(yùn)算符, 就不一一展開了:
_rsub(self, other)
_rmul(self, other)
_rmul(self, other)
_rfloordiv(self, other)
_rdiv(self, other)
_rtruediv(self, other)
_rmod(self, other)
_rdivmod(self, other)
_rpow(self, other)
_rlshift(self, other)
_rrshift(self, other)
_rand(self, other)
_ror(self, other)
_rxor(self, other)
2.8.5 增量賦值
這也是只要理解了概念就容易掌握的運(yùn)算。舉個(gè)例子:
x = 5
x += 1 # 這里的+=就是增量賦值,將x+1賦值給了x
因此對于a += b, _iadd_ 將返回a + b, 并賦值給a。
所以很容易理解下面的魔術(shù)方法了:
_iadd(self, other)
_isub(self, other)
_imul(self, other)
_ifloordiv(self, other)
_idiv(self, other)
_itruediv(self, other)
_imod(self, other)
_ipow(self, other)
_ilshift(self, other)
_irshift(self, other)
_iand(self, other)
_ior(self, other)
_ixor_(self, other)
2.8.6 類型轉(zhuǎn)化
__int__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為int的行為.
__long__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為long的行為.
__float__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為float的行為.
__complex__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為complex(復(fù)數(shù), 也即1+2j這樣的虛數(shù))的行為.
__oct__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為八進(jìn)制數(shù)的行為.
__hex__(self)
實(shí)現(xiàn)了類型轉(zhuǎn)化為十六進(jìn)制數(shù)的行為.
__index__(self)
在切片運(yùn)算中將對象轉(zhuǎn)化為int, 因此該方法的返回值必須是int。用一個(gè)例子來解釋這個(gè)用法。
class Thing(object):
def__index__(self):
return 1
thing = Thing()
list_ = ['a', 'b', 'c']
print list_[thing] # 'b'
print list_[thing:thing] # []
上面例子中, list_[thing]的表現(xiàn)跟list_[1]一致,正是因?yàn)門hing實(shí)現(xiàn)了__index__方法。
可能有的人會(huì)想,list_[thing]為什么不是相當(dāng)于list_[int(thing)]呢? 通過實(shí)現(xiàn)Thing的__int__方法能否達(dá)到這個(gè)目的呢?
顯然不能。如果真的是這樣的話,那么list_[1.1:2.2]這樣的寫法也應(yīng)該是通過的。
而實(shí)際上,該寫法會(huì)拋出TypeError: slice indices must be integers or None or have an __index__ method
下面我們再做個(gè)例子,如果對一個(gè)dict對象執(zhí)行dict_[thing]會(huì)怎么樣呢?
dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing] # raise KeyError
這個(gè)時(shí)候就不是調(diào)用__index__了。雖然list和dict都實(shí)現(xiàn)了__getitem__方法, 但是它們的實(shí)現(xiàn)方式是不一樣的。
如果希望上面例子能夠正常執(zhí)行, 需要實(shí)現(xiàn)Thing的__hash__ 和 __eq__方法.
classThing(object):
def__hash__(self):
return 1
def__eq__(self, other):
return hash(self) == hash(other)
dict_ = {1: 'apple', 2: 'banana', 3: 'cat'}
print dict_[thing] # apple
__coerce__(self, other)
實(shí)現(xiàn)了混合模式運(yùn)算。
要了解這個(gè)方法,需要先了解coerce()內(nèi)建函數(shù): 官方文檔上的解釋是, coerce(x, y)返回一組數(shù)字類型的參數(shù), 它們被轉(zhuǎn)化為同一種類型,以便它們可以使用相同的算術(shù)運(yùn)算符進(jìn)行操作。如果過程中轉(zhuǎn)化失敗,拋出TypeError。
比如對于coerce(10, 10.1), 因?yàn)?0和10.1在進(jìn)行算術(shù)運(yùn)算時(shí),會(huì)先將10轉(zhuǎn)為10.0再來運(yùn)算。因此coerce(10, 10.1)返回值是(10.0, 10.1).
__coerce__在Python3中廢棄了。
2.8.7其他魔術(shù)方法
還沒講到的魔術(shù)方法還有很多,但有些我覺得很簡單,或者很少見,就不再累贅展開說明了。
__str__(self)
對實(shí)例使用str()時(shí)調(diào)用。
__repr__(self)
對實(shí)例使用repr()時(shí)調(diào)用。str()和repr()都是返回一個(gè)代表該實(shí)例的字符串,主要區(qū)別在于: str()的返回值要方便人來看,而repr()的返回值要方便計(jì)算機(jī)看。
__unicode__(self)
對實(shí)例使用unicode()時(shí)調(diào)用。unicode()與str()的區(qū)別在于: 前者返回值是unicode, 后者返回值是str。unicode和str都是basestring的子類。
當(dāng)你對一個(gè)類只定義了__str__但沒定義__unicode__時(shí),__unicode__會(huì)根據(jù)__str__的返回值自動(dòng)實(shí)現(xiàn),即return unicode(self.__str__());
但返回來則不成立。
class StrDemo2:
def__str__(self):
return 'StrDemo2'
class StrDemo3:
def__unicode__(self):
return u'StrDemo3'
demo2 = StrDemo2()
print (str(demo2)) # StrDemo2
print (unicode(demo2)) # StrDemo2
demo3 = StrDemo3()
print (str(demo3)) # <__main__.StrDemo3 instance>
print (unicode(demo3)) # StrDemo3
__format__(self, formatstr)
"Hello, {0:abc}".format(a)等價(jià)于format(a, "abc"), 等價(jià)于a.format("abc")。
這在需要格式化展示對象的時(shí)候非常有用,比如格式化時(shí)間對象。
__hash__(self)
對實(shí)例使用hash()時(shí)調(diào)用, 返回值是數(shù)值類型。
__nonzero__(self)
對實(shí)例使用bool()時(shí)調(diào)用, 返回True或者False。
你可能會(huì)問, 為什么不是命名為__bool__? 我也不知道。
我只知道該方法在Python3中改名為__bool__了。
__dir__(self)
對實(shí)例使用dir()時(shí)調(diào)用。通常實(shí)現(xiàn)該方法是沒必要的。
__sizeof__(self)
對實(shí)例使用sys.getsizeof()時(shí)調(diào)用。返回對象的大小,單位是bytes。
__instancecheck__(self, instance)
對實(shí)例調(diào)用isinstance(instance, class)時(shí)調(diào)用。 返回值是布爾值。它會(huì)判斷instance是否是該類的實(shí)例。
__subclasscheck__(self, subclass)
對實(shí)例使用issubclass(subclass, class)時(shí)調(diào)用。返回值是布爾值。它會(huì)判斷subclass否是該類的子類。
__copy__(self)
對實(shí)例使用copy.copy()時(shí)調(diào)用。返回"淺復(fù)制"的對象。
__deepcopy__(self, memodict={})
對實(shí)例使用copy.deepcopy()時(shí)調(diào)用。返回"深復(fù)制"的對象。
__call__(self, [args...])
該方法允許類的實(shí)例跟函數(shù)一樣表現(xiàn):
classXClass:
def__call__(self, a, b):
return a + b
defadd(a, b):
return a + b
x = XClass()
print 'x(1, 2)', x(1, 2)
print 'callable(x)', callable(x) # True
print 'add(1, 2)', add(1, 2)
print 'callable(add)', callable(add) # True
2.8.8 Python3中的差異
- Python3中,str與unicode的區(qū)別被廢除了,因而unicode沒有了,取而代之地出現(xiàn)了bytes.
- Python3中,division默認(rèn)就是true division, 因而div廢棄.
- coerce因存在冗余而廢棄.
- cmp因存在冗余而廢棄.
- nonzero改名為bool.