魔術方法
屬性 | 含義 |
---|---|
__name__ | 類、函數、方法等的名字 |
__module__ | 類定義所在的模塊名 |
__class__ | 對象或類所屬的類 |
__bases__ | 類的基類的元組,舒徐為他們在基類列表中出現的順序 |
__doc__ | 類、函數的文檔字符串,如果沒有定義則為None |
__mro__ | 類的mro,class.mro()返回的結果保存在__mro__中 |
__dict__ | 類或實例的屬性,可寫的字典 |
查看屬性
方法:__dict__,返回類或者對象的所有成員列表。dir函數就是調用__dir__()。如果提供__dir__(),則返回屬性的列表,否則會盡量從__dict__屬性中收集信息。
- 如果dir([object])參數包含方法__dir__,該方法會被調用。如果參數不包含,該方法將會最大限度的收集參數信息。
- 對于不同類型的對象有不同的行為:如果對象是模塊對象,列表包含模塊的屬性名;如果對象時類型或者類對象,列表包含類的屬性名,以及它的基類的屬性名;否則列表包含對象的屬性名,它的類的屬性名和類的基類的屬性名。
魔術方法的分類
- 創建和銷毀:__init__和__del__
- hash
- bool
- 可視化
- 運算符重載
- 可調用對象
- 上下文管理
- 反射
- 描述器
- 其他
hash
- __hash__內建函數hash調用的返回值,返回一個整數。如果定義這個方法該類的實例就可hash
- __eq__:對應==操作符,判斷2個對象值是否相等,返回bool值 __hash__方法只是返回一個hash值作為set的key,但是去重,先要用is判斷是不是同一個,然后再用__eq__來判斷是否值相等。 hash值相等,只是hash沖突,不能說明兩個對象是相等的。因此一般來說提供__hash__方法是為了作為set或者dict的key的,所以去重要同時是提供__eq__方法。 可hash對象必須提供__hash__方法,沒有的話,isinstance(p1,collections,HASHABLE)一定為False。
class Point():
def __init__(self,x,y):
self.x = x
self.y = y
def __repr__(self):
return 'Point({},{})'.format(self.x,self.y)
def __eq__(self,other):
return self.x == other.x and self.y == other.y
a = Point(3,4)
b = Point(3,4)
print(a.__repr__(b))
print({a,b})
bool
內建(函數bool(),或者對象放在邏輯表達式的位置,調用這個函數返回布爾值。沒有定義__bool__(),就找__len__()返回長度,非0為真。如果__len__()也沒有定義,那么所有實例都返回真。
class A:
pass
print(bool(A()))
class B:
def __bool__(self):
return False
print(bool(B))
print(bool(B()))
class C:
def __len__(self):
return 0
print(bool(A()))
可視化 方法|意義 ———-|———— __repr__ |內建函數repr()對一個對象獲取字符串表達。如果一個類定義了__repr__但沒有有__str__,那么再請求該類的實例的非正式的字符串標識是,也將調用__repr__ __str__ | str函數、內建函數format、print函數調用,需要返回對象的字符串表達 __bytes__ | bytes的時候,返回一個對象的bytes表達,即返回bytes對象
class A:
def __init__(self):
self.a ='a'
self.b = 'b'
def __eq__(self, other):
return False
def __repr__(self):
return 'REPR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)
def __str__(self):
return 'STR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)
print(A())
print([A()])
print(([str(A())]))
運算符重載
operator模塊提供一下的特殊方法,可以將類的實例使用下面的操作付來操作:
運算符 | 特殊方法 | 含義 |
---|---|---|
<,<=,==,>,>=,!= | __lt__,__le__,__eq__,__gt__,__ge__,__ne__ | 比較運算符 |
+,-,*, /,%,//,**,divmod | __add__,__sub__,__mul__,__truediv__,__mod__,__floordiv__,__pow__,__divmod__ | 算數運算符,位運算符也有對應的方法 |
+=,-=,*=,/=,%=,//=,**= | __iadd__,__isub__,__imul__,__itruediv__,__imod__,__floordiv__,__ipow__ |
- 練習:完成point類設計,實現判斷點相等的方法,病完成向量的加法
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __eq__(self,other):
return self.x == other.x and self.y == other.y
def __add__(self,other):
return Point(self.x + other.x,self.y + other.y)
def __repr__(self):
return 'Point({},{})'.format(self.x,self.y)
p1 = Point(3,4)
p2 = Point(5,9)
p3 = Point(5,9)
print(p1 + p2)
print(p2 == p3,p2 is p3)
- 運算符重載應用場景 在做大量連續的運算時,使用運算符這種數學上常見的表達方式非常簡單、方便。提供運算符重載,比直接提供加方法更加適合程序員的變成習慣。 int類中幾乎實現了所有操作符,可以作為設計類的參考。
容器相關方法
方法 | 意義 |
---|---|
__len__ | 內建函數len(),返回對象的長度(>=0的證書),其實即使把對象當做容器類型看,就如同list或者dict。bool()函數調用的時候,如果沒有__bool__()方法,就會查看__len__()方法是否存在,存在返回非0為真 |
__iter__ | 迭代容器時調用,返回一個新的迭代器對象 |
__contains__ | in成員運算符,沒有實現就調用iter方法遍歷 |
__getitem__ | 實現self[key]訪問。序列對象,可以接受整數為索引,或者切片。對于set和dict,key就是可hash的類型??梢圆淮嬖谝lKeyError異常 |
__setitem__ | 和getitem的訪問能累死,是設置值的方法 |
__missing__ | 字典使用getitem調用時,key不存在執行此方法 |
- 練習:將購物車改造成方便操作的容器類
class Item:
def __init__(self,**kwargs):
self._property = kwargs
def __repr__(self):
return 'name:{} $:{} color:{}'.format(*self._property.values())
class ShopCar:
def __init__(self):
self.items = []
def additem(self,item:Item):
self.items.append(item)
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __setitem__(self,index, value):
self.items[index] = value
myshopcar = ShopCar()
myphone = Item(name='sumsung',price=4898,color='black')
mycar = Item(name='meserati',price=288,color='red')
myshopcar.additem(myphone)
myshopcar.additem(mycar)
print(myshopcar[1])
# myshopcar[0] = None
for item in myshopcar:
print(item)
print(len(myshopcar))
print(myshopcar.__dict__)
print(mycar.__dict__,myphone.__dict__)
可調用對象
Python中一切皆對象,函數也是一樣。
def foo():
print(foo.__name__,foo.__name__)
foo() #等價于foo.__call__()
函數就是對象,對象foo加上(),就是調用對象的__call__方法,在自定義類中定義該方法,那么類的實例就可以像函數對象一樣加個括號調用。
- 練習:定義一個斐波那契數列的類,方便調用,計算第n項
class Fib:
def __init__(self):
self.seq = [0,1,1]
def __len__(self):
return len(self.seq)
def __call__(self,count):
if count < len(self):#此處的判斷是為了減少計算,當已經算過的時候就不再需要重新計算
return self.seq[count]
for n in range(len(self)-1,count):#使用實例數列長度來作為起點可以減少計算,不必每次都從頭算起。
self.seq.append(self.seq[n-1]+self.seq[n])
return self.seq[count]
def __getitem__(self,index):
if index < 0:
raise IndexError('Wrong Index')
if index > len(self):#此處的判斷是如果需要的項沒有計算出來就立即計算
return self(index)
return self.seq[index]
def __iter__(self):
return iter(self.seq)
上下文管理
文件IO操作可以對文件對象使用上下文管理,使用with…as語法。
with open('test') as f:
pass
- 上下文管理對象:當一個對象同事實現了__enter__()和__exit__()方法,他就屬于上下文管理的對象
方法 | 意義 |
---|---|
__enter__ | 進入與此對象相關的上下文。如果存在該方法,with語法會把該方法的返回值作為綁定到as子句中指定的變量上 |
__exit__ | 退出與此對象相關的上下文。 |
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
print("let's do it")
根據上例發現,實例化對象的時候,并不會調用enter,當進入with語句塊調用enter方法,然后執行語句塊,最后退出上下文管理時,調用exit方法。由此表明,with可以開啟一個上下文運行環境,在執行前做一些操作,執行后做一些收尾工作。
- 上下文管理的安全性 分別看下異常和極端情況下對下上下文管理的影響
import sys
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
raise Exception('error')
# sys.exit() #sys模塊方法,可直接退出當前解釋器
print("let's do it")
print('outer le')
上個例子的返回值中觀察發現,不管是報錯還是強制退出解釋器,enter和exit方法都會執行,說明上下文管理很安全
- enter和exit方法
- enter方法沒有其他參數,他的返回值就是上下文中使用的對象,with語法會把他的返回值賦給as子句的變量。
- exit方法有3個參數,exctype、excvalue、tracback,這三個參數都與異常有關,分別是異常類型、異常的返回值、異常的追蹤消息。
- exit方法返回一個等效True的值,則壓制異常;否則繼續拋出異常
- 練習:使用上下文管理顯示加法函數的執行時長
import datetime,time
class TimeIt:
def __init__(self,func):
self._fn = func
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def add(x,y):
time.sleep(3)
return x+y
with TimeIt() as f:
print(add(7,9))
- 如果要求寫成如下形式,代碼要如何修改
with TimeIt(add) as foo:
foo(4,5)
實現如下:
import datetime,time
class TimeIt:
def __init__(self,func):
self._fn = func
def __call__(self,x,y):
return self._fn(x,y)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def add(x,y):
time.sleep(3)
return x+y
with TimeIt() as foo:
print(foo(7,9))
根據以上代碼,將類當做裝飾器來裝飾函數
import datetime,time
from functools import wraps
class TimeIt:
def __init__(self,func):
self._fn = func
wraps(func)(self) #復制add的函數該有的屬性到實例add,等價于@warps(func)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def __call__(self,x,y): #使實例可調用的魔法方法
return self._fn(x,y)
@TimeIt #等價于add = TimeIt(add)
def add(x,y):
"""This is a add function"""
time.sleep(3)
return x+y
add(4,5)
print(add.__dict__)
- 上下文應用場景
- 增強功能:在代碼執行的前后增加代碼,以增強其功能。類死裝飾器的功能
- 資源管理:打開的資源需要關閉,例如文件對象、網絡連接、數據庫連接等
- 權限驗證:在執行代碼前,在enter方法中做權限的驗證
- context.contextmanager 它是一個裝飾器,裝飾一個函數也實現了上下文管理,而且不用像類一樣實現enter和exit方法。 對下面的函數有要求,必須有yield,也就是這個函數必須返回一個生成器,且只有yield一個值。
import contextlib
@contextlib.contextmanager
def foo():
print('enter')
yield #yield的值只能有一個,作為enter方法的返回值
print('exit')
with foo() as f:
#raise EXCEPTION
print(f)
上面的代碼看起來很正常,當時增加一個異常后發現不能保證exit的執行。
import contextlib
@contextlib.contextmanager
def foo():
print('enter')
try:
yield #yield的值只能有一個,作為enter方法的返回值
finaly:
print('exit')
with foo() as f:
raise EXCEPTION
print(f)
當增加了try\finaly語句之后發現exit又可以執行。在yield發生處為生成器函數增加了上下文管理。
- 總結:如果業務邏輯簡單可以使用函數加裝飾器方式,如果業務復雜,用類的方式加enter和exit更加方便。
本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/88766