前言
距離上一篇已經(jīng)三個(gè)多星期了,最近比較累挣柬,下班回到家,很早就休息了睛挚,所以更新的進(jìn)度有點(diǎn)慢邪蛔。
目錄
一、Python 的 Magic Method
在 Python 中扎狱,所有以 "__" 雙下劃線包起來的方法侧到,都統(tǒng)稱為"魔術(shù)方法"。比如我們接觸最多的 __init__
淤击。魔術(shù)方法有什么作用呢匠抗?
使用這些魔術(shù)方法,我們可以構(gòu)造出優(yōu)美的代碼污抬,將復(fù)雜的邏輯封裝成簡單的方法汞贸。
那么一個(gè)類中有哪些魔術(shù)方法呢?
我們可以使用 Python 內(nèi)置的方法 dir()
來列出類中所有的魔術(shù)方法.示例如下:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
pass
if __name__ == '__main__':
print(dir(User()))
輸出的結(jié)果:
可以看到印机,一個(gè)類的魔術(shù)方法還是挺多的矢腻,截圖也沒有截全,不過我們只需要了解一些常見和常用的魔術(shù)方法就好了耳贬。
二踏堡、構(gòu)造(__new__
)和初始化(__init__
)
通過上一篇的內(nèi)容猎唁,我們已經(jīng)知道定義一個(gè)類時(shí)咒劲,我們經(jīng)常會(huì)通過 __init__(self)
的方法在實(shí)例化對(duì)象的時(shí)候顷蟆,對(duì)屬性進(jìn)行設(shè)置。比如下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name, age):
self.name = name;
self.age = age;
user=User('兩點(diǎn)水',23)
實(shí)際上腐魂,創(chuàng)建一個(gè)類的過程是分為兩步的帐偎,一步是創(chuàng)建類的對(duì)象,還有一步就是對(duì)類進(jìn)行初始化蛔屹。__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__
方法。
def __new__(cls)
是在 def __init__(self)
方法之前調(diào)用的兔毒,作用是返回一個(gè)實(shí)例對(duì)象漫贞。還有一點(diǎn)需要注意的是:__new__
方法總是需要返回該類的一個(gè)實(shí)例,而 __init__
不能返回除了 None
的任何值
具體的示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __new__(cls, *args, **kwargs):
# 打印 __new__方法中的相關(guān)信息
print('調(diào)用了 def __new__ 方法')
print(args)
# 最后返回父類的方法
return super(User, cls).__new__(cls)
def __init__(self, name, age):
print('調(diào)用了 def __init__ 方法')
self.name = name
self.age = age
if __name__ == '__main__':
usr = User('兩點(diǎn)水', 23)
看看輸出的結(jié)果:
調(diào)用了 def __new__ 方法
('兩點(diǎn)水', 23)
調(diào)用了 def __init__ 方法
通過打印的結(jié)果來看育叁,我們就可以知道一個(gè)類創(chuàng)建的過程是怎樣的了迅脐,先是調(diào)用了 __new__
方法來創(chuàng)建一個(gè)對(duì)象,把參數(shù)傳給 __init__
方法進(jìn)行實(shí)例化豪嗽。
其實(shí)在實(shí)際開發(fā)中谴蔑,很少會(huì)用到 __new__
方法,除非你希望能夠控制類的創(chuàng)建龟梦。通常講到 __new__
隐锭,都是牽扯到 metaclass
(元類)的。
當(dāng)然當(dāng)一個(gè)對(duì)象的生命周期結(jié)束的時(shí)候计贰,析構(gòu)函數(shù) __del__
方法會(huì)被調(diào)用钦睡。但是這個(gè)方法是 Python 自己對(duì)對(duì)象進(jìn)行垃圾回收的。
三躁倒、屬性的訪問控制
之前也有講到過赎婚,Python 沒有真正意義上的私有屬性。然后這就導(dǎo)致了對(duì) Python 類的封裝性比較差樱溉。我們有時(shí)候會(huì)希望 Python 能夠定義私有屬性挣输,然后提供公共可訪問的 get 方法和 set 方法。Python 其實(shí)可以通過魔術(shù)方法來實(shí)現(xiàn)封裝福贞。
方法 | 說明 |
---|---|
__getattr__(self, name) |
該方法定義了你試圖訪問一個(gè)不存在的屬性時(shí)的行為撩嚼。因此,重載該方法可以實(shí)現(xiàn)捕獲錯(cuò)誤拼寫然后進(jìn)行重定向, 或者對(duì)一些廢棄的屬性進(jìn)行警告挖帘。 |
__setattr__(self, name, value) |
定義了對(duì)屬性進(jìn)行賦值和修改操作時(shí)的行為完丽。不管對(duì)象的某個(gè)屬性是否存在,都允許為該屬性進(jìn)行賦值.有一點(diǎn)需要注意,實(shí)現(xiàn) __setattr__ 時(shí)要避免"無限遞歸"的錯(cuò)誤拇舀, |
__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ò)誤抠璃。 |
通過上面的方法表可以知道站楚,在進(jìn)行屬性訪問控制定義的時(shí)候你可能會(huì)很容易的引起一個(gè)錯(cuò)誤,可以看看下面的示例:
def __setattr__(self, name, value):
self.name = value
# 每當(dāng)屬性被賦值的時(shí)候搏嗡, ``__setattr__()`` 會(huì)被調(diào)用窿春,這樣就造成了遞歸調(diào)用。
# 這意味這會(huì)調(diào)用 ``self.__setattr__('name', value)`` 采盒,每次方法會(huì)調(diào)用自己旧乞。這樣會(huì)造成程序崩潰。
def __setattr__(self, name, value):
# 給類中的屬性名分配值
self.__dict__[name] = value
# 定制特有屬性
上面方法的調(diào)用具體示例如下:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __getattr__(self, name):
print('調(diào)用了 __getattr__ 方法')
return super(User, self).__getattr__(name)
def __setattr__(self, name, value):
print('調(diào)用了 __setattr__ 方法')
return super(User, self).__setattr__(name, value)
def __delattr__(self, name):
print('調(diào)用了 __delattr__ 方法')
return super(User, self).__delattr__(name)
def __getattribute__(self, name):
print('調(diào)用了 __getattribute__ 方法')
return super(User, self).__getattribute__(name)
if __name__ == '__main__':
user = User()
# 設(shè)置屬性值磅氨,會(huì)調(diào)用 __setattr__
user.attr1 = True
# 屬性存在,只有__getattribute__調(diào)用
user.attr1
try:
# 屬性不存在, 先調(diào)用__getattribute__, 后調(diào)用__getattr__
user.attr2
except AttributeError:
pass
# __delattr__調(diào)用
del user.attr1
輸出的結(jié)果:
調(diào)用了 __setattr__ 方法
調(diào)用了 __getattribute__ 方法
調(diào)用了 __getattribute__ 方法
調(diào)用了 __getattr__ 方法
調(diào)用了 __delattr__ 方法
四良蛮、對(duì)象的描述器
一般來說,一個(gè)描述器是一個(gè)有“綁定行為”的對(duì)象屬性 (object attribute)悍赢,它的訪問控制被描述器協(xié)議方法重寫决瞳。這些方法是 __get__()
, __set__()
, 和 __delete__()
。有這些方法的對(duì)象叫做描述器左权。
默認(rèn)對(duì)屬性的訪問控制是從對(duì)象的字典里面 (__dict__
) 中獲取 (get) , 設(shè)置 (set) 和刪除 (delete) 皮胡。舉例來說, a.x
的查找順序是, a.__dict__['x']
, 然后 type(a).__dict__['x']
, 然后找 type(a)
的父類 ( 不包括元類 (metaclass) ).如果查找到的值是一個(gè)描述器, Python 就會(huì)調(diào)用描述器的方法來重寫默認(rèn)的控制行為赏迟。這個(gè)重寫發(fā)生在這個(gè)查找環(huán)節(jié)的哪里取決于定義了哪個(gè)描述器方法屡贺。注意, 只有在新式類中時(shí)描述器才會(huì)起作用。在之前的篇節(jié)中已經(jīng)提到新式類和舊式類的锌杀,有興趣可以查看之前的篇節(jié)來看看甩栈,至于新式類最大的特點(diǎn)就是所有類都繼承自 type 或者 object 的類。
在面向?qū)ο缶幊虝r(shí)糕再,如果一個(gè)類的屬性有相互依賴的關(guān)系時(shí)巴柿,使用描述器來編寫代碼可以很巧妙的組織邏輯蔓涧。在 Django 的 ORM 中,models.Model中的 InterField 等字段, 就是通過描述器來實(shí)現(xiàn)功能的。
我們先看下下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name='兩點(diǎn)水', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('獲取 name 值')
return self.name
def __set__(self, obj, val):
print('設(shè)置 name 值')
self.name = val
class MyClass(object):
x = User('兩點(diǎn)水', '男')
y = 5
if __name__ == '__main__':
m = MyClass()
print(m.x)
print('\n')
m.x = '三點(diǎn)水'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)
輸出的結(jié)果如下:
獲取 name 值
兩點(diǎn)水
設(shè)置 name 值
獲取 name 值
三點(diǎn)水
獲取 name 值
三點(diǎn)水
5
通過這個(gè)例子,可以很好的觀察到這 __get__()
和 __set__()
這些方法的調(diào)用荒椭。
再看一個(gè)經(jīng)典的例子
我們知道跪另,距離既可以用單位"米"表示,也可以用單位"英尺"表示洁段。
現(xiàn)在我們定義一個(gè)類來表示距離,它有兩個(gè)屬性: 米和英尺晌柬。
#!/usr/bin/env python3
# -*- 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()
foot = Foot()
if __name__ == '__main__':
d = Distance()
print(d.meter, d.foot)
d.meter = 1
print(d.meter, d.foot)
d.meter = 2
print(d.meter, d.foot)
輸出的結(jié)果:
0.0 0.0
1.0 3.2808
2.0 6.5616
在上面例子中,在還沒有對(duì) Distance 的實(shí)例賦值前, 我們認(rèn)為 meter 和 foot 應(yīng)該是各自類的實(shí)例對(duì)象, 但是輸出卻是數(shù)值。這是因?yàn)?__get__
發(fā)揮了作用.
我們只是修改了 meter ,并且將其賦值成為 int 绑嘹,但 foot 也修改了稽荧。這是 __set__
發(fā)揮了作用.
描述器對(duì)象 (Meter、Foot) 不能獨(dú)立存在, 它需要被另一個(gè)所有者類 (Distance) 所持有工腋。描述器對(duì)象可以訪問到其擁有者實(shí)例的屬性姨丈,比如例子中 Foot 的 instance.meter
畅卓。
五、自定義容器(Container)
經(jīng)過之前編章的介紹构挤,我們知道在 Python 中髓介,常見的容器類型有: dict, tuple, list, string惕鼓。其中也提到過可容器和不可變?nèi)萜鞯母拍罱钕帧F渲?tuple, string 是不可變?nèi)萜鳎琩ict, list 是可變?nèi)萜鳌? 可變?nèi)萜骱筒豢勺內(nèi)萜鞯膮^(qū)別在于箱歧,不可變?nèi)萜饕坏┵x值后矾飞,不可對(duì)其中的某個(gè)元素進(jìn)行修改。當(dāng)然具體的介紹呀邢,可以看回之前的文章洒沦,有圖文介紹。
那么這里先提出一個(gè)問題价淌,這些數(shù)據(jù)結(jié)構(gòu)就夠我們開發(fā)使用嗎申眼?不夠的時(shí)候,或者說有些特殊的需求不能單單只使用這些基本的容器解決的時(shí)候蝉衣,該怎么辦呢括尸?
這個(gè)時(shí)候就需要自定義容器了,那么具體我們?cè)撛趺醋瞿兀?/p>
功能 | 說明 |
---|---|
自定義不可變?nèi)萜黝愋?/td> | 需要定義 __len__ 和 __getitem__ 方法 |
自定義可變類型容器 | 在不可變?nèi)萜黝愋偷幕A(chǔ)上增加定義 __setitem__ 和 __delitem__
|
自定義的數(shù)據(jù)類型需要迭代 | 需定義 __iter__
|
返回自定義容器的長度 | 需實(shí)現(xiàn) __len__(self)
|
自定義容器可以調(diào)用 self[key] 病毡,如果 key 類型錯(cuò)誤濒翻,拋出TypeError ,如果沒法返回key對(duì)應(yīng)的數(shù)值時(shí),該方法應(yīng)該拋出ValueError |
需要實(shí)現(xiàn) __getitem__(self, key)
|
當(dāng)執(zhí)行 self[key] = value 時(shí) |
調(diào)用是 __setitem__(self, key, value) 這個(gè)方法 |
當(dāng)執(zhí)行 del self[key] 方法 |
其實(shí)調(diào)用的方法是 __delitem__(self, key)
|
當(dāng)你想你的容器可以執(zhí)行 for x in container: 或者使用 iter(container) 時(shí) |
需要實(shí)現(xiàn) __iter__(self) 啦膜,該方法返回的是一個(gè)迭代器 |
來看一下使用上面魔術(shù)方法實(shí)現(xiàn) Haskell 語言中的一個(gè)數(shù)據(jù)結(jié)構(gòu):
#!/usr/bin/env python3
# -*- 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]
六、運(yùn)算符相關(guān)的魔術(shù)方法
運(yùn)算符相關(guān)的魔術(shù)方法實(shí)在太多了,j就大概列舉下面兩類:
1僧家、比較運(yùn)算符
魔術(shù)方法 | 說明 |
---|---|
__cmp__(self, other) |
如果該方法返回負(fù)數(shù)雀摘,說明 self < other ; 返回正數(shù),說明 self > other ; 返回 0 說明 self == other 八拱。強(qiáng)烈不推薦來定義 __cmp__ , 取而代之, 最好分別定義 __lt__ , __eq__ 等方法從而實(shí)現(xiàn)比較功能届宠。 __cmp__ 在 Python3 中被廢棄了。 |
__eq__(self, other) |
定義了比較操作符 == 的行為 |
__ne__(self, other) |
定義了比較操作符 != 的行為 |
__lt__(self, other) |
定義了比較操作符 < 的行為 |
__gt__(self, other) |
定義了比較操作符 > 的行為 |
__le__(self, other) |
定義了比較操作符 <= 的行為 |
__ge__(self, other) |
定義了比較操作符 >= 的行為 |
來看個(gè)簡單的例子就能理解了:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class Number(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
print('__eq__')
return self.value == other.value
def __ne__(self, other):
print('__ne__')
return self.value != other.value
def __lt__(self, other):
print('__lt__')
return self.value < other.value
def __gt__(self, other):
print('__gt__')
return self.value > other.value
def __le__(self, other):
print('__le__')
return self.value <= other.value
def __ge__(self, other):
print('__ge__')
return self.value >= other.value
if __name__ == '__main__':
num1 = Number(2)
num2 = Number(3)
print('num1 == num2 ? --------> {} \n'.format(num1 == num2))
print('num1 != num2 ? --------> {} \n'.format(num1 == num2))
print('num1 < num2 ? --------> {} \n'.format(num1 < num2))
print('num1 > num2 ? --------> {} \n'.format(num1 > num2))
print('num1 <= num2 ? --------> {} \n'.format(num1 <= num2))
print('num1 >= num2 ? --------> {} \n'.format(num1 >= num2))
輸出的結(jié)果為:
__eq__
num1 == num2 ? --------> False
__eq__
num1 != num2 ? --------> False
__lt__
num1 < num2 ? --------> True
__gt__
num1 > num2 ? --------> False
__le__
num1 <= num2 ? --------> True
__ge__
num1 >= num2 ? --------> False
2乘粒、算術(shù)運(yùn)算符
魔術(shù)方法 | 說明 | |
---|---|---|
__add__(self, other) |
實(shí)現(xiàn)了加號(hào)運(yùn)算 | |
__sub__(self, other) |
實(shí)現(xiàn)了減號(hào)運(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)了位操作 ^
|
最后,如果對(duì)本文感興趣的灯萍,可以關(guān)注下公眾號(hào):