前言
距離上一篇已經三個多星期了,最近比較累,下班回到家,很早就休息了,所以更新的進度有點慢。
目錄
一、Python 的 Magic Method
在 Python 中,所有以 "" 雙下劃線包起來的方法,都統稱為"魔術方法"。比如我們接觸最多的 `init__` 。魔術方法有什麼作用呢?
使用這些魔術方法,我們可以構造出優美的程式碼,將複雜的邏輯封裝成簡單的方法。
那麼一個類中有哪些魔術方法呢?
我們可以使用 Python 內建的方法 dir()
來列出類中所有的魔術方法.示例如下:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
pass
if __name__ == '__main__':
print(dir(User()))複製程式碼
輸出的結果:
可以看到,一個類的魔術方法還是挺多的,截圖也沒有截全,不過我們只需要瞭解一些常見和常用的魔術方法就好了。
二、構造(__new__
)和初始化(__init__
)
通過上一篇的內容,我們已經知道定義一個類時,我們經常會通過 __init__(self)
的方法在例項化物件的時候,對屬性進行設定。比如下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name, age):
self.name = name;
self.age = age;
user=User('兩點水',23)複製程式碼
實際上,建立一個類的過程是分為兩步的,一步是建立類的物件,還有一步就是對類進行初始化。__new__
是用來建立類並返回這個類的例項, 而__init__
只是將傳入的引數來初始化該例項.__new__
在建立一個例項的過程中必定會被呼叫,但 __init__
就不一定,比如通過pickle.load 的方式反序列化一個例項時就不會呼叫 __init__
方法。
def __new__(cls)
是在 def __init__(self)
方法之前呼叫的,作用是返回一個例項物件。還有一點需要注意的是:__new__
方法總是需要返回該類的一個例項,而 __init__
不能返回除了 None
的任何值
具體的示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __new__(cls, *args, **kwargs):
# 列印 __new__方法中的相關資訊
print('呼叫了 def __new__ 方法')
print(args)
# 最後返回父類的方法
return super(User, cls).__new__(cls)
def __init__(self, name, age):
print('呼叫了 def __init__ 方法')
self.name = name
self.age = age
if __name__ == '__main__':
usr = User('兩點水', 23)複製程式碼
看看輸出的結果:
呼叫了 def __new__ 方法
('兩點水', 23)
呼叫了 def __init__ 方法複製程式碼
通過列印的結果來看,我們就可以知道一個類建立的過程是怎樣的了,先是呼叫了 __new__
方法來建立一個物件,把引數傳給 __init__
方法進行例項化。
其實在實際開發中,很少會用到 __new__
方法,除非你希望能夠控制類的建立。通常講到 __new__
,都是牽扯到 metaclass
(元類)的。
當然當一個物件的生命週期結束的時候,解構函式 __del__
方法會被呼叫。但是這個方法是 Python 自己對物件進行垃圾回收的。
三、屬性的訪問控制
之前也有講到過,Python 沒有真正意義上的私有屬性。然後這就導致了對 Python 類的封裝性比較差。我們有時候會希望 Python 能夠定義私有屬性,然後提供公共可訪問的 get 方法和 set 方法。Python 其實可以通過魔術方法來實現封裝。
方法 | 說明 |
---|---|
__getattr__(self, name) |
該方法定義了你試圖訪問一個不存在的屬性時的行為。因此,過載該方法可以實現捕獲錯誤拼寫然後進行重定向, 或者對一些廢棄的屬性進行警告。 |
__setattr__(self, name, value) |
定義了對屬性進行賦值和修改操作時的行為。不管物件的某個屬性是否存在,都允許為該屬性進行賦值.有一點需要注意,實現 __setattr__ 時要避免"無限遞迴"的錯誤, |
__delattr__(self, name) |
__delattr__ 與 __setattr__ 很像,只是它定義的是你刪除屬性時的行為。實現 __delattr__ 是同時要避免"無限遞迴"的錯誤 |
__getattribute__(self, name) |
__getattribute__ 定義了你的屬性被訪問時的行為,相比較,__getattr__ 只有該屬性不存在時才會起作用。因此,在支援 __getattribute__ 的 Python 版本,呼叫__getattr__ 前必定會呼叫 __getattribute__``__getattribute__ 同樣要避免"無限遞迴"的錯誤。 |
通過上面的方法表可以知道,在進行屬性訪問控制定義的時候你可能會很容易的引起一個錯誤,可以看看下面的示例:
def __setattr__(self, name, value):
self.name = value
# 每當屬性被賦值的時候, ``__setattr__()`` 會被呼叫,這樣就造成了遞迴呼叫。
# 這意味這會呼叫 ``self.__setattr__('name', value)`` ,每次方法會呼叫自己。這樣會造成程式崩潰。
def __setattr__(self, name, value):
# 給類中的屬性名分配值
self.__dict__[name] = value
# 定製特有屬性複製程式碼
上面方法的呼叫具體示例如下:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __getattr__(self, name):
print('呼叫了 __getattr__ 方法')
return super(User, self).__getattr__(name)
def __setattr__(self, name, value):
print('呼叫了 __setattr__ 方法')
return super(User, self).__setattr__(name, value)
def __delattr__(self, name):
print('呼叫了 __delattr__ 方法')
return super(User, self).__delattr__(name)
def __getattribute__(self, name):
print('呼叫了 __getattribute__ 方法')
return super(User, self).__getattribute__(name)
if __name__ == '__main__':
user = User()
# 設定屬性值,會呼叫 __setattr__
user.attr1 = True
# 屬性存在,只有__getattribute__呼叫
user.attr1
try:
# 屬性不存在, 先呼叫__getattribute__, 後呼叫__getattr__
user.attr2
except AttributeError:
pass
# __delattr__呼叫
del user.attr1複製程式碼
輸出的結果:
呼叫了 __setattr__ 方法
呼叫了 __getattribute__ 方法
呼叫了 __getattribute__ 方法
呼叫了 __getattr__ 方法
呼叫了 __delattr__ 方法複製程式碼
四、物件的描述器
一般來說,一個描述器是一個有“繫結行為”的物件屬性 (object attribute),它的訪問控制被描述器協議方法重寫。這些方法是 __get__()
, __set__()
, 和 __delete__()
。有這些方法的物件叫做描述器。
預設對屬性的訪問控制是從物件的字典裡面 (__dict__
) 中獲取 (get) , 設定 (set) 和刪除 (delete) 。舉例來說, a.x
的查詢順序是, a.__dict__['x']
, 然後 type(a).__dict__['x']
, 然後找 type(a)
的父類 ( 不包括元類 (metaclass) ).如果查詢到的值是一個描述器, Python 就會呼叫描述器的方法來重寫預設的控制行為。這個重寫發生在這個查詢環節的哪裡取決於定義了哪個描述器方法。注意, 只有在新式類中時描述器才會起作用。在之前的篇節中已經提到新式類和舊式類的,有興趣可以檢視之前的篇節來看看,至於新式類最大的特點就是所有類都繼承自 type 或者 object 的類。
在物件導向程式設計時,如果一個類的屬性有相互依賴的關係時,使用描述器來編寫程式碼可以很巧妙的組織邏輯。在 Django 的 ORM 中,models.Model中的 InterField 等欄位, 就是通過描述器來實現功能的。
我們先看下下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name='兩點水', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('獲取 name 值')
return self.name
def __set__(self, obj, val):
print('設定 name 值')
self.name = val
class MyClass(object):
x = User('兩點水', '男')
y = 5
if __name__ == '__main__':
m = MyClass()
print(m.x)
print('\n')
m.x = '三點水'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)複製程式碼
輸出的結果如下:
獲取 name 值
兩點水
設定 name 值
獲取 name 值
三點水
獲取 name 值
三點水
5複製程式碼
通過這個例子,可以很好的觀察到這 __get__()
和 __set__()
這些方法的呼叫。
再看一個經典的例子
我們知道,距離既可以用單位"米"表示,也可以用單位"英尺"表示。
現在我們定義一個類來表示距離,它有兩個屬性: 米和英尺。
#!/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)複製程式碼
輸出的結果:
0.0 0.0
1.0 3.2808
2.0 6.5616複製程式碼
在上面例子中,在還沒有對 Distance 的例項賦值前, 我們認為 meter 和 foot 應該是各自類的例項物件, 但是輸出卻是數值。這是因為 __get__
發揮了作用.
我們只是修改了 meter ,並且將其賦值成為 int ,但 foot 也修改了。這是 __set__
發揮了作用.
描述器物件 (Meter、Foot) 不能獨立存在, 它需要被另一個所有者類 (Distance) 所持有。描述器物件可以訪問到其擁有者例項的屬性,比如例子中 Foot 的 instance.meter
。
五、自定義容器(Container)
經過之前編章的介紹,我們知道在 Python 中,常見的容器型別有: dict, tuple, list, string。其中也提到過可容器和不可變容器的概念。其中 tuple, string 是不可變容器,dict, list 是可變容器。 可變容器和不可變容器的區別在於,不可變容器一旦賦值後,不可對其中的某個元素進行修改。當然具體的介紹,可以看回之前的文章,有圖文介紹。
那麼這裡先提出一個問題,這些資料結構就夠我們開發使用嗎?不夠的時候,或者說有些特殊的需求不能單單隻使用這些基本的容器解決的時候,該怎麼辦呢?
這個時候就需要自定義容器了,那麼具體我們該怎麼做呢?
功能 | 說明 |
---|---|
自定義不可變容器型別 | 需要定義 __len__ 和 __getitem__ 方法 |
自定義可變型別容器 | 在不可變容器型別的基礎上增加定義 __setitem__ 和 __delitem__ |
自定義的資料型別需要迭代 | 需定義 __iter__ |
返回自定義容器的長度 | 需實現 __len__(self) |
自定義容器可以呼叫 self[key] ,如果 key 型別錯誤,丟擲TypeError ,如果沒法返回key對應的數值時,該方法應該丟擲ValueError |
需要實現 __getitem__(self, key) |
當執行 self[key] = value 時 |
呼叫是 __setitem__(self, key, value) 這個方法 |
當執行 del self[key] 方法 |
其實呼叫的方法是 __delitem__(self, key) |
當你想你的容器可以執行 for x in container: 或者使用 iter(container) 時 |
需要實現 __iter__(self) ,該方法返回的是一個迭代器 |
來看一下使用上面魔術方法實現 Haskell 語言中的一個資料結構:
#!/usr/bin/env python3
# -*- 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]複製程式碼
六、運算子相關的魔術方法
運算子相關的魔術方法實在太多了,j就大概列舉下面兩類:
1、比較運算子
魔術方法 | 說明 |
---|---|
__cmp__(self, other) |
如果該方法返回負數,說明 self < other ; 返回正數,說明 self > other ; 返回 0 說明 self == other 。強烈不推薦來定義 __cmp__ , 取而代之, 最好分別定義 __lt__ , __eq__ 等方法從而實現比較功能。 __cmp__ 在 Python3 中被廢棄了。 |
__eq__(self, other) |
定義了比較操作符 == 的行為 |
__ne__(self, other) |
定義了比較操作符 != 的行為 |
__lt__(self, other) |
定義了比較操作符 < 的行為 |
__gt__(self, other) |
定義了比較操作符 > 的行為 |
__le__(self, other) |
定義了比較操作符 <= 的行為 |
__ge__(self, other) |
定義了比較操作符 >= 的行為 |
來看個簡單的例子就能理解了:
#!/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))複製程式碼
輸出的結果為:
__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、算術運算子
魔術方法 | 說明 | |
---|---|---|
__add__(self, other) |
實現了加號運算 | |
__sub__(self, other) |
實現了減號運算 | |
__mul__(self, other) |
實現了乘法運算 | |
__floordiv__(self, other) |
實現了 // 運算子 | |
___div__(self, other) |
實現了/運算子. 該方法在 Python3 中廢棄. 原因是 Python3 中,division 預設就是 true division | |
__truediv__(self, other) |
實現了 true division. 只有你宣告瞭 from __future__ import division 該方法才會生效 |
|
__mod__(self, other) |
實現了 % 運算子, 取餘運算 | |
__divmod__(self, other) |
實現了 divmod() 內建函式 | |
__pow__(self, other) |
實現了 ** 操作. N 次方操作 |
|
__lshift__(self, other) |
實現了位操作 << |
|
__rshift__(self, other) |
實現了位操作 >> |
|
__and__(self, other) |
實現了位操作 & |
|
__or__(self, other) |
實現了位操作 ` | ` |
__xor__(self, other) |
實現了位操作 ^ |
最後,如果對本文感興趣的,可以關注下公眾號: