python魔法函數
1 什麼是魔法函數¶
先來定義一個類:
class Company(object):
def __init__(self, employee_list):
self.employee_list = employee_list
company = Company(['張三', '李四', '王五'])
print(company)
<__main__.Company object at 0x7f7c4046ebd0>
此時,直接對Company實例化的對象進行print輸出時,打印出來的信息是類名稱和地址信息。但如果我們想看的不是這些,而是想輸出employee_list,怎麼做呢?
class Company(object):
def __init__(self, employee_list):
self.employee_list = employee_list
def __str__(self):
return str(self.employee_list)
company = Company(['張三', '李四', '王五'])
print(company)
['張三', '李四', '王五']
在這個例子中,我們添加了一個__str__()
函數,然後再打印輸出Company類實例時,輸出的就是employee_list,但是,我們並沒有顯式地調用__str__()
函數,這是因為,在對一個實例使用print()函數時,Python內部機制自動會調用__str__()
函數。
類似__str__()
這種函數在類內部還有很多,這一類函數,我們統稱為魔法函數。現在,我們明確一下魔法函數的範疇:
魔法函數是指類內部以雙下劃線開頭,並且以雙下劃線結尾的函數,在特定時刻,Python會自動調用這些函數。魔法函數不是通過繼承等機制獲得的,而是類一旦定義,Python內部機制自動會給類賦予這些特殊的函數,且用戶是不能創建魔法函數的,即使函數名以雙下劃線開頭和雙下劃線結尾。通過魔法函數可以實現許多個性化、便捷的操作。
2 Python中的魔法函數¶
2.1 字符串表示¶
-
__str__
-
__repr__
在很多時候,人們都容易將__str__
和__repr__
兩個方法記混,甚至認為這兩的功能是一樣的,但事實上還是有一些差異的。
__str__
在上文中已經說過,是用於將實例對象進行print輸出時使用。如下所示:
class Company(object):
def __init__(self, name=None):
self.name = name
def __str__(self):
return '*****公司名稱為:%s*****' % self.name
c = Company(name='騰訊')
print(c)
*****公司名稱為:騰訊*****
對實例化對象是用print()函數輸出時,Python內部機制會想調用str()方法,在str()方法內部繼續調用__str__
方法實現輸出:
str(c)
'*****公司名稱為:騰訊*****'
但是如果我們不是用print()函數而直接輸出c,那麼,輸出結果依然是原來默認的:
c
<__main__.Company at 0x7f7c4049d050>
這是因為直接輸出類實例化對象時,調用的是__repr__
方法:
class Company(object):
def __init__(self, name=None):
self.name = name
def __str__(self):
return '*****公司名稱為:%s*****' % self.name
def __repr__(self):
return '#####公司名稱為:%s#####' % self.name
c = Company(name='騰訊')
c
#####公司名稱為:騰訊#####
綜上所述,__str__
和__repr__
的區別在於,__str__
方法在對實例化對象是用print()函數輸出時調用,其實時Python內部機制調用str()方法,然後str()方法內部繼續調用__str__
方法獲取輸出字符串。而__repr__
是在開發模式下直接輸出實例化對象時被調用。
2.2 集合、序列相關¶
__len__
Python內置函數中有一個len()函數,這個函數適用於獲取序列類型數據的長度,在對一個實例使用len()方法時,真實輸出的其實是__len__
的返回值。所以,只要一個類內部實現了__len__
方法,就可以對其實例使用__len__
方法。
class Company(object):
def __init__(self, name=None, employee_lst=None):
self.name = name
self.employee_lst = employee_lst
def __len__(self):
return len(self.employee_lst)
c = Company(name='騰訊', employee_lst=['張三', '李四', '王五'])
len(c)
3
__getitem__
、__setitem__
、__delitem__
我們知道,在Python的dict類型數據中,可以通過方括號的方式來賦值、取值和刪除值,例如通過t_dict[‘attr1’] = 1的方式進行賦值,通過t_dict[‘attr1’]可以取得值,通過del t_dict[‘attr1’]可以刪除一個值。那麼在自定義的一個類裏面,通過__getitem__
、__setitem__
、__delitem__
這三個,我們也可以讓我們自定義類的實例化對象擁有這樣的操作。
class Company(object):
def __init__(self):
self.company_info = {}
def __setitem__(self,key,value): # 令類實例化對象可以通過c[key] = value的方式賦值
self.company_info[key] = value
def __getitem__(self,key): # 令類實例化對象可以通過c[key]的方式取值
return self.company_info[key]
def __delitem__(self, key): # 令類實例化對象可以通過del c[key]的方式刪除值
del self.company_info[key]
c = Company()
c['name'] = '騰訊'
c['type'] = 'IT'
print(c['name'])
del c['name']
print(c.company_info)
騰訊 {'type': 'IT'}
有些時候,配合Python的反射機制類使用這三個魔法函數會有更加魔幻的效果,可以直接對實例屬性進行操作:
class Company(object):
def __setitem__(self,key,value):
setattr(self, key, value)
def __getitem__(self,key):
return getattr(self, key)
def __delitem__(self, key):
delattr(self, key)
c = Company()
c['name'] = '騰訊'
c['type'] = 'IT'
c['type']
'IT'
del c['type']
c['type']
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-63-56601054285d> in <module> ----> 1c['type'] <ipython-input-59-b82d5d10cbb4> in __getitem__(self, key) 5 6 def __getitem__(self,key): ----> 7return getattr(self, key) 8 9 def __delitem__(self, key): AttributeError: 'Company' object has no attribute 'type'
__contains__
對於Python中dict類型的數據結構,可以使用in
關鍵字判斷序列內部是否包含某個key,在我們自定義的類中,如果定義了__contains__
方法,那麼也能使用in
關鍵字判斷是否包含某個屬性。
class Company(object):
def __init__(self):
self.company_info = {}
def __contains__(self, key):
return key in self.company_info
c = Company()
c.company_info['name'] = '騰訊'
print('name' in c)
print('type' in c)
True False
結合反射機制使用:
class Company(object):
def __setitem__(self,key,value):
setattr(self, key, value)
def __contains__(self, key):
return hasattr(self, key)
c = Company()
c['name'] = '騰訊'
print('name' in c)
print('type' in c)
True False
2.3 迭代相關¶
__iter__
、__next__
我之前寫過一篇博客《為什麼for循環可以遍歷list:Python中迭代器與生成器》,很詳細得介紹了Python中關於迭代器與生成器的原理。關於迭代器和生成器,其核心就在於__iter__
和__next__
兩個方法。
iter是Iterable的簡寫,表示「可迭代的」,所以,任何內部定義了__iter__
的對象,我們都可以稱之為可迭代對象,在Python中,有一個類專門與之對應:Iterable,我們可以通過判斷對象是否是Iterable類的實例來判斷是否是可迭代對象。進一步的,如果一個類內部定義了__iter__
方法的同時,也定了__next__
方法,那麼,它的實例化對象就是迭代器,也有一個類與迭代器對應,那就是Iterator。
from collections.abc import Iterable
from collections.abc import Iterator
isinstance(123, Iterable) # 整型不是可迭代對象
False
isinstance('abc', Iterator) # 字符串不是迭代器
False
isinstance('abc', Iterable) # 字符串是可迭代對象
True
class Company():
def __iter__(self): # 自定義一個類,只要實現了__iter__方法,就是可迭代對象
pass
print('Company()是可迭代對象嗎:',isinstance(Company(),Iterable))
print('Company()是迭代器嗎:',isinstance(Company(),Iterator))
Company()是可迭代對象嗎: True Company()是迭代器嗎: False
class Company():
def __iter__(self):
pass
def __next__(self): # 自定義一個類,同時實現了__iter__方法和__next__方法,就是迭代器
pass
print('Company()是可迭代對象嗎:',isinstance(Company(),Iterable))
print('Company()是迭代器嗎:',isinstance(Company(),Iterator))
Company()是可迭代對象嗎: True Company()是迭代器嗎: True
知道怎麼區分可迭代對象和迭代器之後,就可以解釋__iter__
和__next__
的作用了。那就是定義了這兩個方法,就可以對實例化對象進行遍歷。以for循環為例,通過for循環對一個可迭代對象進行迭代時,for循環內部機制會自動通過調用iter()方法執行可迭代對象內部定義的__iter__
方法來獲取一個迭代器,然後一次又一次得迭代過程中通過調用next()方法執行迭代器內部定義的__next__
方法獲取下一個元素,當沒有下一個元素時,for循環自動捕獲並處理StopIteration異常。
class B():
def __init__(self, lst):
self.lst = lst
self.index = 0
def __iter__(self):
print('B.__iter__()方法被調用')
return self
def __next__(self):
try:
print('B.__next__()方法被調用')
value = self.lst[self.index]
self.index += 1
return value
except IndexError:
raise StopIteration()
b = B([1, 2, 3])
for i in b:
print(i)
B.__iter__()方法被調用 B.__next__()方法被調用 1 B.__next__()方法被調用 2 B.__next__()方法被調用 3 B.__next__()方法被調用
2.4 可調用¶
__call__
假如有一個對象A,如果A是一個類,我們使用A()進行調用,那麼就是創建一個A類的實例化對象,如果A是一個函數,我們使用A()就是調用函數A。那麼,如果A是一個某個類的實例化對象時,A()是進行什麼操作呢?答案就是調用該類的__call__
方法,我們可以理解為,__call__
就是「()」運算符。
class Company(object):
def __init__(self):
pass
def __call__(self, name):
self.name = name
print('__call__方法被調用,name:%s' % self.name)
c = Company()
c('騰訊')
__call__方法被調用,name:騰訊
現在,我們證實了__call__
就是「()」運算法,那麼,是不是類、函數這些可使用「()」運算符的對象內部都定義有__call__
函數呢?答案是肯定的。
class Company(object):
def __init__(self):
pass
def A():
pass
print('類Company是否有__call_方法:', hasattr(Company, '__call__'))
print('函數A是否有__call_方法:', hasattr(A, '__call__'))
類Company是否有__call_方法: True 函數A是否有__call_方法: True
藉助這一特性,我們可以彌補hasattr()函數的不足。我們知道,通過hasattr()函數可以判斷一個類內部是否有某個屬性,但是沒法判斷到底是變量還是方法,但進一步藉助方法內部肯定定義有__call__
這個特性,就可以進一步判斷。
class Company(object):
def __init__(self):
self.name = None
def func(self):
pass
c = Company()
print('c中是否存在屬性name:', hasattr(c, 'name'))
print('c中是否存在屬性func:', hasattr(c, 'func'))
print('name是函數嗎:', hasattr(c.name, '__call__'))
print('func是函數嗎:', hasattr(c.func, '__call__'))
c中是否存在屬性name: True c中是否存在屬性func: True name是函數嗎: False func是函數嗎: True
2.5 with上下文管理器¶
只要你熟悉Python開發,那麼對with上下文管理就一定不會陌生,例如操作文本時,我們通常習慣with open
來對打開文件,獲得句柄。使用with來打開文件的好處就是在打開文件後進行操作的過程中,無論是否出現異常,Python都會對關閉句柄,也就是一定會進行收尾工作,避免佔用內存資源。
這種上下文管理機制是怎麼實現的呢?這就涉及到我們現在要說的兩個兩個魔法函數__enter__
和__exit__
。
__enter__
:with語句開始執行時候調用
__exit__
:with語句結束時候調用,注意,無論with語句中的代碼是否正常結束,都會執行__exit__
方法
除了讀寫文件之外,我們使用Python來操作數據庫時,也需要做收尾處理,也就是關閉數據庫連接,那麼,這個時候我們也可以用with來進行。
import pymysql
class Dao(object):
def __init__(self, cursor_type=None):
self.conn = pymysql.connect( # 創建數據庫連接
host='192.168.31.201', # 要連接的數據庫所在主機ip
database='test',
user='root', # 數據庫登錄用戶名
password='admin123456', # 登錄用戶密碼
charset='utf8' # 編碼,注意不能寫成utf-8
)
self.cursor = None
if cursor_type:
self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
else:
self.cursor = self.conn.cursor()
def __enter__(self):
return self.cursor # 返回類實例本身
def __exit__(self, exc_type, exc_value, exc_trace):
self.conn.commit() # 提交事務
self.cursor.close() # 關閉游標
self.conn.close() # 關閉數據庫連接
with Dao() as cursor:
cursor.execute("select * from employee;")
e = cursor.fetchall()
print(e)
((1, '張三'), (2, '李四'))
2.8 屬性相關¶
__getattr__
、__setattr__
__getattr__
函數的作用: 在一個類實例中查找一個屬性時,通過__dict__
失敗, 那麼會調用到類的__getattr__
函數,如果沒有定義這個函數,那麼拋出AttributeError異常。也就是說__getattr__
是屬性查找的最後一步。
class Company(object):
def __init__(self, name):
self.company_name = name
def fun(self):
print('fun方法被調用……')
def __getattr__(self, name):
print('__getattr__方法被調用')
raise AttributeError('哥們,你查找的屬性"%s"不存在' % name)
c = Company('騰訊')
如果提前找到了某個屬性,那麼將不會繼續調用__getattr__
:
print(c.company_name)
print(c.fun)
騰訊 <bound method Company.fun of <__main__.Company object at 0x7fa0a8077100>>
當屬性不存在是,將會調用__getattr__
,所以,我們可以通過__getattr__
函數來定義當找不到屬性時候的提醒方式,甚至是返回一個其他的默認值。
c.abc
__getattr__方法被調用
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-16-a2bb1cff9d71> in <module> ----> 1c.abc <ipython-input-13-810c2a9c4f3c> in __getattr__(self, name) 8 def __getattr__(self, name): 9 print('__getattr__方法被調用') ---> 10raise AttributeError('哥們,你查找的屬性"%s"不存在' % name) AttributeError: 哥們,你查找的屬性"abc"不存在
通過__getattr__
方法,我們可以對Python的字典進行改造,另外開始通過dict_name.key
的方式來訪問。
class Dict(dict):
def __init__(self, *args, **kwargs):
super(Dict, self).__init__(*args, **kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
d = Dict({'name': '張三', 'age': '李四'})
d.name
'張三'
__getattr__
是用來獲取屬性,那麼__setattr__
就是用來給屬性賦值,當我們使用實例.key=value
的方式進行賦值的時候就一定會調用__setattr__
方法。
class Company(object):
def __init__(self, name):
self.company_name = name
def __setattr__(self, name, value):
print("__setattr__方法被調用")
# self.name = value # 第一種寫法
# object.__setattr__(self, name, value) # 第二種寫法
self.__dict__[name] = value # 第三種寫法
c = Company('騰訊')
c.company_name = '阿里'
print(c.company_name)
__setattr__方法被調用 __setattr__方法被調用 阿里
為什麼__setattr__
被調用了兩次呢?因為在__init__
中也使用了一次實例.key=value
的方式賦值。
所以,在定義__setattr__
的時候一定要注意,一定不能使用上述代碼中被注釋掉的第一種寫法,因為使用self.name = value
進行賦值時,本身又會再次調用__setattr__
方法,這就造成了無線遞歸,造成bug。所以使用第二和第三種寫法才是正確的。
繼續用__setattr__
方法改造字典:
class Dict(dict):
def __init__(self, *args, **kwargs):
super(Dict, self).__init__(*args, **kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, name):
self[key] = name
d = Dict()
d.name = '張三'
print(d.name)
張三
__getattribute__
__getattribute__
與上面的__getattr__
很相似,區別在於__getattr__
是在類中未找到屬性時調用,而__getattribute__
是不管類中有無查找的屬性存在,都優先調用。不過在使用__getattribute__
方法市,必須注意陷入無限遞歸,當在__getattribute__
代碼塊中,再次執行屬性的獲取操作時,會再次觸發__getattribute__
方法的調用,代碼將會陷入無限遞歸,直到Python遞歸深度限制,所以,在__getattribute__
中獲取屬性時,需要通過父類的__getattribute__
方法獲取對應的屬性。
class Company(object):
def __init__(self, name):
self.company_name = name
def __getattribute__(self, name):
print('__getattribute__方法被調用')
return object.__getattribute__(self, name)
# raise AttributeError('哥們,你查找的屬性"%s"不存在' % name)
c = Company('騰訊')
c.company_name
__getattribute__方法被調用
'騰訊'
c.abc
__getattribute__方法被調用
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-34-a2bb1cff9d71> in <module> ----> 1c.abc <ipython-input-32-e6bee225b017> in __getattribute__(self, name) 5 def __getattribute__(self, name): 6 print('__getattribute__方法被調用') ----> 7return object.__getattribute__(self, name) 8 # raise AttributeError('哥們,你查找的屬性"%s"不存在' % name) AttributeError: 'Company' object has no attribute 'abc'
__dict__
、dir()
、__dir__
上文中提到過__dict__
,__dict__
是對象的一個屬性,並不是函數,它的作用是返回對象的所有屬性名為key,屬性值為value的一個字典,注意,這裡所說的所有屬性是指數據對象本身的屬性,例如類的__dict__
只包含類本身的屬性和函數,而類實例也只包含類實例的屬性。這一點與dir()
函數不同,dir()
將會返回一個列表,列表中包含對象所有有關的屬性名。也就是說,__dict__
是dir()
的子集。而dir()
實際上調用的是__dir__
方法。
class Company(object):
def __init__(self, name):
self.company_name = name
def fun(self):
print('fun方法被調用……')
c = Company('騰訊')
c.__dict__
{'company_name': '騰訊'}
Company.__dict__
mappingproxy({'__module__': '__main__', '__init__': <function __main__.Company.__init__(self, name)>, 'fun': <function __main__.Company.fun(self)>, '__dict__': <attribute '__dict__' of 'Company' objects>, '__weakref__': <attribute '__weakref__' of 'Company' objects>, '__doc__': None})
dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'company_name', 'fun']
c.__dir__()
['company_name', '__module__', '__init__', 'fun', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
Related Posts
- 2021 年 1 月 25 日
Selective Search for Object Recognition
作者: J. R. R. Uijlings, K. E. A. van de Sande, T. Gevers, A. … ..