魔術方法

魔術方法

使用Python的魔術方法的最大優勢在于他們提供了一種簡單的方法來讓對象可以表現的像內置類型一樣。那意味著你可以避免丑陋的,違反直覺的,不標準的的操作方法。
特殊屬性
屬性
含義
__name__
類、函數、方法等名字
__module__
類定義所在的模塊名?
__class__
對象或類所屬的類
__bases__
類的基類的元組,順序為它們在基類列表中出現的順序
__doc__
類、函數的文檔字符串,如果沒有定義則為None
__mro__
類的mro,class.mro()返回的結果保存在__mro__中
__dict__
類或實例的屬性,可寫的字典
查看屬性
方法
意義
__dir__
返回類或者對象的所有成員名稱列表.dir()函數就是調用__dir__().如果提供了__dir__(),則返回屬性的列表,否則就會盡量從__dict__屬性中收集信息
  • 如果dir([obj])參數包含方法__dir__(),該方法將被調用。如果參數不包含__dir__(),該方法將最大限度地收集參數信息。
  • dir()對于不同類型的對象具有不同的行為
  • 如果對象是模塊對象,列表包含模塊的屬性名
  • 如果對象是類型或者類對象,列表包含類的屬性名,及它的積累的屬性名
  • 否則,列表包含對象的屬性名,它的類的屬性名和類的基類的屬性名
import animal
from animal import Animal
class Cat(Animal):
y = ‘cat’
x = ‘abcd’
class Dog(Animal):
def __dir__(self):
return [‘dog’]
print(‘————————‘)
print(‘Current Module\’s name = {}’.format(dir()))#模塊名詞空間內的屬性
print(‘Animal Module\’s name = {}’.format(dir(animal)))#animal模塊名詞空間的屬性
print(“object’s dict={}”.format(sorted(object.__dict__.keys())))#object字典內容
print(“Animal’s dir()={}”.format(dir(Animal)))#類名詞空間下的屬性方法
print(‘Cat dirr()={}’.format(dir(Cat)))#類Cat的dir()
print(‘————————‘)
tom = Cat(‘tome’)
print(sorted(dir(tom)))#實例tom的屬性,Cat類及所有祖先的類屬性
print(sorted(tom.__dir__()))#同上
#dir()的等價,近似如下,__dict__字典中幾乎包括了所有屬性
print(sorted(set(tom.__dict__.keys())|set(Cat.__dict__.keys())|set(object.__dict__.keys())))
print(‘Dog dir = {}’.format(dir(Dog)))
dog = Dog(‘snoppy’)
print(dir(dog))
print(dog.__dict__)
———————————–
{‘_name’: ‘Thunk’, ‘weight’: 10, ‘_Animal__age’: 20}
[‘_Animal__age’, ‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘_name’, ‘weight’, ‘x’]
————————
Current Module’s name = [‘Animal’, ‘Cat’, ‘Dog’, ‘__builtins__’, ‘__cached__’, ‘__doc__’, ‘__file__’, ‘__loader__’, ‘__name__’, ‘__package__’, ‘__spec__’, ‘animal’]
Animal Module’s name = [‘Animal’, ‘__builtins__’, ‘__cached__’, ‘__doc__’, ‘__file__’, ‘__loader__’, ‘__name__’, ‘__package__’, ‘__spec__’, ‘c’]
object’s dict=[‘__class__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’]
Animal’s dir()=[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘x’]
Cat dirr()=[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘x’, ‘y’]
————————
[‘_Animal__age’, ‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘_name’, ‘weight’, ‘x’, ‘y’]
[‘_Animal__age’, ‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘_name’, ‘weight’, ‘x’, ‘y’]
[‘_Animal__age’, ‘__class__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘_name’, ‘weight’, ‘x’, ‘y’]
Dog dir = [‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘x’]
[‘dog’]
{‘_name’: ‘snoppy’, ‘weight’: 10, ‘_Animal__age’: 20}
魔術方法
  • 分類:
    • 創建與銷毀
      • __init__與__del__
    • hash
    • bool
    • 可視化
    • 運算符重載
    • 容器和大小
    • 可調用對象
    • 上下文管理
    • 反射
    • 描述器
    • 其他雜項
Hash
方法
意義
__hash__
內建函數hash()調用的返回值,返回一個整數。如果定義這個方法該類的實例就可hash
class A:
def __init__(self):
self.a = ‘a’
self.b = ‘b’
def __hash__(self):
return 1
def __eq__(self, other):
return self.a == other.a
print(hash(A()))
print((A(),A()))
print({A(),A()})
s = {A(),A()}
print(s)
——————————-
1
(<__main__.A object at 0x000000872C048278>, <__main__.A object at 0x000000872C048390>)
{<__main__.A object at 0x000000872C048278>}
{<__main__.A object at 0x000000872C048278>}
other的解釋:
class A:
def __init__(self):
self.a = ‘a’
self.b = ‘c’
def __hash__(self):
return 1
def __eq__(self, other):
# if not isinstance(other,A):
# return False
return self.a == other.a
class B:
def __init__(self):
self.a = ‘a’
self.b = ‘b’
def __eq__(self, other):
return self.b == other.b
class C:
c = ‘c’
a = A()
c = a
b = B()
print(a == b)#用a的eq方法與b的other.a比較由于都是“a”所以為ture
print(a is c)#
print(id(a), id(c))#id相同因為c對象為a的引用
print(b == C)#如果b在前則使用b的eq方法,由于C類沒有定義b屬性所以報錯
———————-
Traceback (most recent call last):
True
File “F:/pycharm_product/python/1113/1.py”, line 28, in <module>
157269657528 157269657528
print(b == C)#如果b在前則使用b的eq方法,由于C類沒有定義b屬性所以報錯
File “F:/pycharm_product/python/1113/1.py”, line 17, in __eq__
return self.b == other.b
AttributeError: type object ‘C’ has no attribute ‘b’
  • hash值相同不代表對象相同,比如hash算法為%3取模,此時3,6,9的hash值相同但是對象并不相同。
set去重會先判斷is是否相同,如果不同再判斷==是否相同,如都不同則會放到集合中
In [52]: b = (1,2,3)
In [53]: a = (1,2,3)
In [54]: id(b)
Out[54]: 917501314464
In [55]: id(a)
Out[55]: 917522737264
In [56]: c = {a,b}
In [57]: c
Out[57]: {(1, 2, 3)}#雖然內存地址不同但是內容相同還是做了去重處理
方法
意義
__eq__
對應==操作符,判斷2個對象是否相等返回bool值
  • __hash__方法只是返回一個hash值作為set的key,但是去重,還需要__eq__來判斷對象是否相等。
  • hash值相等,只是hash沖突,不能說明兩個對象是相等的
  • 因此,一般來說hash方法是為了作為set或者dict的key,所以去重同時需要eq方法
  • 可hash對象昂必須提供__hash__方法,沒有提供的話,instance(p1,collections.Hashable)一定為False。去重要提供__eq__方法。
  • 練習:
  • 設計二維坐標Point,比較2個坐標是否相等?
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
a = Point(3,4)
c = Point(3,4)
d = Point(4,5)
print(a == c)
print(a == d)
print(a is c)
print(a is d)
———————-
True
False
False
False
  • 思考:為什么list不可以hash?
Alt text
* 通過查看原碼發現__hash__ = None,也就是說如果調用__hash__()相當于調用None(),一定報錯
* 所有的類都繼承了object,而這個類是具有__hash__()方法的,如果一個類不能被hash,就把hash設置為None
bool
  • 四大皆空<list,tuple,set,dict>,__len__()為0所以必定為False
方法
意義
__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(C()))
——————–
True
True
False
False
可視化
方法
意義
__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 __repr__(self):
return ‘repr:{},{}’.format(self.a,self.b)
def __str__(self):
return ‘str:{},{}’.format(self.a,self.b)
print(A())#print函數使用__str__
print([A()])#[]使用__str__,但其內部使用__repr__
print(([str(A())]))#[]使用__str__,str()函數也使用__str__
print(‘str:a,b’)
s = ‘b’
print([‘a’],(s,))
——————–
str:a,b
[repr:a,b]
[‘str:a,b’]
str:a,b
[‘a’] (‘b’,)
運算符重載
  • operator模塊提供一下的特殊方法,可以將類的實例使用下面的操作符來操作
運算符
特殊方法
含義
<,<=,==,>,>=,!=
__lt__,__le__,__eq__,__gt__,__ge__,__ne__
比較運算符
+,-,*, /,%,//,**,divmod
__add__,__sub__,__mul__,__truediv__,__mod__,__floordiv__,__pow__,__divmod__
算數運算符,位運算符也有對應的方法
+=,-=,*=,/=,%=,//=,**=
__iadd__,__isub__,__imul__,__itruediv__,__imod__,__floordiv__,__ipow__
class A:
def __init__(self,x):
self.x = x
def __sub__(self, other):
return self.x – other.x
def __isub__(self, other):
tmp = self.x – other.x
return A(tmp)
def __str__(self):
return str(self.x)
x = A(5)
y = A(4)
print(x – y,x.__sub__(y))#直接使用減法跟調用sub方法效果一樣,運算符重載
x -= y#實例化對象調用運算符
print(x)
—————————————–
1 1
1
  • 練習:完成Point類設計,實現判斷點相等的方法,并完成向量的加法
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __repr__(self):
return ‘self.x={},self.y={}’.format(self.x,self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return self.x+other.x,self.y+other.y
def add(self,other):
return Point(self.x + other.x,other.y + self.y)
p1 = Point(1,1)
p2 = Point(1,1)
points = (p1,p2)
print(points[0].add(points[1]))
#運算符重載
print(points[0] + points[1])
#print(Point(*(points[0] + points[1])))提示*的選項不可迭代,原因是上面的__add__魔術方法中的return為Point(self.x+other.x,self.y+other.y)對象
print(p1 == p2)
運算符重載應用場景
  • 往往是用面向對象實現的類,需要做大量的運算,而運算符是這種在數學上最常見的表達方式。例如,上例中的對+進行了運算符重載,實現了Point類的二元操作,重新定義為Point+Point
  • 提供運算符重載,比直接提供加法方法要更加適合該領域內使用者的習慣
  • int類,幾乎實現了所有操作符
容器的相關方法
方法
意義
__len__
內建函數len(),返回對象的長度(>0的整數),其實即使把對象當做容器類型看,就如同list或者dict。bool()函數調用的時候,如果沒有__bool__()方法,則會看__len__()方法是否存在,存在返回非0為真
__iter__
迭代容器時,調用,返回一個新的迭代器對象
__contains__
in成員運算符,沒有實現,就調用__iter__方法遍歷
__getitem__
實現self[key]訪問。序列對象,key接受整數為索引,或者切片。對于set和dict,key為hashable。key不存在引發keyError異常
__setitem__
和__getitem__的訪問類似,是設置值得方法
__missing__
‘字典使用’__getitem__()調用時,key不存在執行該方法
*練習:將購物車改造成方便操作的容器類
class Cart:
def __init__(self):
self.items = []#定義購物車容器
def __len__(self):
return len(self.items)#返回對象長度
def additem(self,item):
self.items.append(item)#向購物車內添加商品
def __add__(self, other):#運算符重載,實例化后使用+即代表add
self.items.append(other)
return self
def __getitem__(self, index):#實例化對象通過索引獲取value
return self.items[index]
def __setitem__(self,key,value):#實例化對象通過索引方式設置value
print(key,value)
self.items[key] = value
#self.[key] = value#不可以
def __iter__(self):#迭代和in注意要求返回必須是一個迭代器
return iter(self.items)#將列表容器轉為裝飾器對象,需要for或next獲取內容
def __repr__(self):
return str(self.items)
cart = Cart()
cart.additem(1)
cart.additem(‘a’)
cart.additem(2)
print(len(cart))
print(‘~’*40)
for x in cart:
print(x)
print(‘~’*40)
print(2 in cart)#in操作,這個例子沒有實現contains方法,就調用iter方法遍歷
print(cart[1])#索引操作即調用getitem方法
cart[2] = ‘b’#調用setitem方法
print(cart[2])
print(cart + 2 + 3 + 4 + 5)#鏈式變成實現加法
cart.__add__(2).__add__(3)
print(‘~’*40)
for i in cart:
print(i)
print(cart.items)
———————–
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1
a
2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
True
a
2 b
b
[1, ‘a’, ‘b’, 2, 3, 4, 5]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1
a
b
2
3
4
5
2
3
[1, ‘a’, ‘b’, 2, 3, 4, 5, 2, 3]
可調用對象
  • python中一切皆對象,函數也不例外
def foo():
print(foo.__module__,foo.__name__)
foo()#跟下面等價
foo.__call__()
———————-
__main__ foo
__main__ foo
  • 函數即對象,對象foo加上(),就是調用對象的__call__()方法
  • 可調用對象
方法
意義
__call__
類中的第一個該方法,實例就可以像函數一樣調用
  • 可調用對象:定義一個類,并實例化得到其實例,將實例像函數一樣調用。
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __call__(self, *args, **kwargs):
return “Point({},{})”.format(self.x,self.y)
p = Point(4,5)
print(p)
print(p())
class Adder:
def __call__(self, *args):
ret = 0
for x in args:
ret += x
self.ret = ret
return ret
adder = Adder()
print(adder(4,5,6))
print(adder.ret)
————————
<__main__.Point object at 0x0000004F80B98780>
Point(4,5)
15
15
  • 練習:定義一個斐波那契數列的類,方便調用,計算第n項
#想法一,交換值
class Fib():
def __init__(self):
self.x = 0
self.y = 1
self.z = 0
self.fib = [0,1]
def __call__(self, *args, **kwargs):#由于args返回為一個tuple所以需要解構或者取第一個值
self.f = args[0]
for _ in range(2,self.f+1):
self.z = self.x + self.y
self.x = self.y
self.y = self.z
return self.z
fib = Fib()
print(fib(5))
—————-
5
#方法二,優勢當傳入60,再傳入40時返回速度很快,因為第二個if將已存在的index直接取出
class Fib:
def __init__(self):
self.items = [0,1,1]
def __call__(self, index):
if index < 0:
raise IndexError(‘Wrong Index’)
if index < len(self.items):
return self.items[index]
for i in range(3,index+1):
self.items.append(self.items[i-1] + self.items[i-2])
return self.items[index]
print(Fib()(4))
#方法三:使用容器方法來實現,使用類實現斐波那契很好實現,還可以緩存數據,便于檢索
class Fib:
def __init__(self):
self.items = [0,1,1]
def __call__(self, index):
return self[index]
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
if index < 0:
raise IndexError(‘Wrong Index’)
if index < len(self.items):
return self.items[index]
for i in range(len(self),index+1):
self.items.append(self.items[i-1] + self.items[i-2])
return self.items[index]
def __str__(self):
return str(self.items)
__repr__ = __str__
fib = Fib()
print(fib(5),len(fib))
print(fib(10),len(fib))
for x in fib:
print(x)
print(fib[5],fib[6])
上下文管理
  • 文件IO操作可以對文件對象使用上下文管理,使用with…as語法
with open(‘test’) as f:
pass
#仿照文件操作寫一個類,實現上下文管理
class Point:
pass
with Point() as p:#AttributerError:__exit__,添加完exit后又報enter繼續添加
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(‘do sth.’)
  • 實例化對象的時候,并不會調用enter,進入with語句塊調用__enter__方法,然后執行語句體,最后離開with語句塊的時候,調用__exit__方法
  • with可以開啟一個上下文運行環境,在執行前做一些準備,執行后做一些收尾工作
上下文管理的安全性
  • 看看異常對上下文的影響,通過下例可以看出在enter和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:
raise Exception(‘error’)
print(‘do sth.’)
—————————–
Traceback (most recent call last):
init
File “F:/pycharm_product/python/1115/8.py”, line 12, in <module>
enter
raise Exception(‘error’)
exit
Exception: error
  • 極端例子:調用sys.exit(),它會退出當前解釋器
  • 打開Python解釋器,在里面敲sys.exit(),窗口直接關閉了,也就是說碰到這一句,Python運行環境直接退出了
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:
sys.exit()
print(‘do sth.’)
print(‘outer’)
———————–
init
enter
exit
  • 從執行結果來看,依然執行了__exit__函數,哪怕是退出Python運行環境
  • 由此說明上下文管理很安全
with語句
class Point:
def __init__(self):
print(‘init’)
def __enter__(self):
print(‘enter’)
def __exit__(self, exc_type, exc_val, exc_tb):
print(‘exit’)
p = Point()
with p as f:
print(p == f)#不相等
print(‘do sth.’)
———————————
init
enter
False
do sth.
exit
  • 不相等的原因在于__enter__方法上,它將自己的返回值賦給了f,繼續修改測試看下例
import sys
class Point:
def __init__(self):
print(‘init’)
def __enter__(self):
print(‘enter’)
return self#加上此句則p與f相等
def __exit__(self, exc_type, exc_val, exc_tb):
print(‘exit’)
p = Point()
with p as f:
print(p == f)
print(p is f)
print(‘do sth.’)
——————
init
enter
True
True
do sth.
exit
  • __enter__方法返回值就是上下文使用的對象,with語法會把它的返回值賦給as子句的變量
  • __enter__方法和__exit__方法的參數
  • __enter__方法沒有其他參數
  • __exit__方法有三個參數(self.exctype,excvalue,traceback)
  • 這三個參數都與異常有關
  • 如果該上下文退出時沒有異常,這三個參數都是None
  • 如果有異常,參數意義如下:
    • exc_type:異常類型
    • exc_value:異常的值
    • traceback,異常的追蹤信息
    • __exit方法返回一個等效True的值,則壓制異常;否則繼續拋出異常
import sys
class Point:
def __init__(self):
print(‘init’)
def __enter__(self):
print(‘enter’)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
print(‘exit’)
return ‘abc’
p = Point()
with p as f:
raise Exception(‘New Error’)
print(‘do sth.’)
print(‘outer’)
——————————-
init
enter
<class ‘Exception’>
New Error#獲取到的錯誤值
<traceback object at 0x000000517DA3BE88>
exit
outer
  • 練習:為加法函數計時
  • 1.使用裝飾器顯示該函數的執行時長
  • 2.使用上下文管理顯示該函數的執行時長
#裝飾器實現
import datetime
from functools import wraps
import time
def timeit(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() – start).total_seconds()
print(‘{} took {}s’.format(fn.__name__,delta))
return ret
return wrapper
@timeit
def add(x,y):
time.sleep(2)
return x+y
print(add(4,5))
#安全上下文實現:
import datetime
from functools import wraps
import time
def timeit(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() – start).total_seconds()
print(‘{} took {}s deco ‘.format(fn.__name__,delta))
return ret
return wrapper
@timeit
def add(x,y):
time.sleep(2)
return x+y
class TimeIt:
def __init__(self,fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.delta = (datetime.datetime.now() – self.start).total_seconds()
print(‘{} took {}s context’.format(self.fn.__name__,self.delta))
pass
def __call__(self, x,y):
print(x,y)
return self.fn(x,y)
# with TimeIt(add):
# add(4,5)
with TimeIt(add) as foo:
foo(4,5)
——————————————————————-
#把類當做裝飾器用
import datetime
from functools import wraps
import time
class TimeIt:
def __init__(self,fn):
self.fn = fn
def __call__(self, *args,**kwargs):
self.start = datetime.datetime.now()
ret = self.fn(*args,**kwargs)
self.delta = (datetime.datetime.now() – self.start).total_seconds()
print(‘{} took {}s. call’.format(self.fn.__name__,self.delta))
return ret
@TimeIt
def add(x,y):
time.sleep(2)
return x+y
add(4,5)
print(add.__doc__)
——————————-
add took 2.000316s. call
None#無法獲取被裝飾函數的原屬性,如文檔字符串
———————————————————————-
##解決無法顯示被裝飾函數的字符串文檔問題,原始方法可以將init中加入self.__doc__=fn.__doc__,不過這樣繁瑣且不全面
import datetime
from functools import wraps
import time
class TimeIt:
def __init__(self,fn):
self.fn = fn
wraps(fn)(self)#
def __call__(self, *args,**kwargs):
self.start = datetime.datetime.now()
ret = self.fn(*args,**kwargs)
self.delta = (datetime.datetime.now() – self.start).total_seconds()
print(‘{} took {}s. call’.format(self.fn.__name__,self.delta))
return ret
@TimeIt
def add(x,y):
“””This is add func”””
time.sleep(2)
return x+y
add(4,5)
print(add.__doc__)
上下文應用場景
1.增強功能:在代碼執行的前后增加代碼,以增強其功能,類似裝飾器的功能
2.資源管理:打開了資源需要關閉,例如文件對象,網絡連接,數據庫連接等
3.權限驗證:在執行代碼前,做權限的驗證,在__enter__中處理
contextlib.contextmanager
  • 它是一個裝飾器實現上下文管理,裝飾一個函數,而不用像類一樣實現__enter__和__exit__方法
  • 對下面的函數有要求,必須有yield,也就是說這個函數必須返回一個生成器,且只有yield一個值
import contextlib
@contextlib.contextmanager
def foo():
print(‘enter’)
yield
print(‘exit’)
with foo() as f:
#raise Exception()只能打印enter無法執行yield和exit
print(f)
—————————-
enter
None
exit
  • f接收yield語句的返回值
  • 下面增加一個異常,發現不能保證exit執行,解決辦法:增加try finally。
import contextlib
@contextlib.contextmanager
def foo():
print(‘enter’)
try:
yield#yield 5,yield的值只能有一個,作為__enter__方法的返回值
finally:
print(‘exit’)
with foo() as f:
raise Exception()
print(f)
————————-
Traceback (most recent call last):
enter
exit
File “F:/pycharm_product/python/1117/1.py”, line 12, in <module>
raise Exception()
Exception
  • 這樣做得意義是,當yield發生處為生成器函數增加了上下文管理
import contextlib
import datetime
import time
@contextlib.contextmanager
def add(x,y):
start = datetime.datetime.now()
try:
yield x + y#yield 5,yield的值只能有一個,作為__enter__方法的返回值
finally:
delta = (datetime.datetime.now() – start).total_seconds()
print(delta)
with add(4,5) as f:
#raise Exception()
time.sleep(2)
print(f)
———————————–
9
2.000035
總結:如果業務邏輯簡單可以使用函數加裝飾器方式,如果業務復雜,用類的方式加__enter__和__exit__
反射
  • 概述:
    • 運行時,區別于編譯時,指的是程序被加載到內容中執行的時候
    • 反射,reflection,指的是運行時獲取類型定義信息
    • 一個對象能夠在運行時,像照鏡子一樣,反射出其類型信息
    • 簡單說,在python中,能夠通過一個對象,找出其type、class、attribute或method的能力,成為反射或者自省
  • 反射相關函數和方法
  • 需求:有一個Point類,查看它實例的屬性,并修改它,動態為實例加屬性
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return “Point({},{})”.format(self.x,self.y)
def show(self):
print(self.x,self.y)
p = Point(4,5)
print(p)
print(p.__dict__)
p.__dict__[‘y’] = 6
print(p.__dict__)
p.z = 10
print(p.__dict__)
print(‘~’*40)
print(dir(p))
print(‘~’*40)
print(p.__dir__())
—————————————–
Point(4,5)
{‘x’: 4, ‘y’: 5}
{‘x’: 4, ‘y’: 6}
{‘z’: 10, ‘x’: 4, ‘y’: 6}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘show’, ‘x’, ‘y’, ‘z’]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[‘z’, ‘__ge__’, ‘__ne__’, ‘__delattr__’, ‘__le__’, ‘x’, ‘__str__’, ‘__format__’, ‘__eq__’, ‘__init__’, ‘__weakref__’, ‘__class__’, ‘__dir__’, ‘__getattribute__’, ‘__reduce__’, ‘__new__’, ‘__dict__’, ‘__hash__’, ‘__subclasshook__’, ‘__reduce_ex__’, ‘__sizeof__’, ‘__setattr__’, ‘show’, ‘__repr__’, ‘y’, ‘__gt__’, ‘__doc__’, ‘__module__’, ‘__lt__’]
  • 上例通過屬性字典__dict__來訪問對象的屬性,本質上也是利用的反射能力
  • python提供了內置的函數
內置函數
意義
getattr(object,name[,default])
通過name返回object的屬性值,當屬性不存在,將使用defalut返回,如果沒有default,則拋出AttributeError。name必須為字符串
setattr(object,name,value)
object的屬性存在,則覆蓋,不存在,新增
hasattr(object,name)
判斷對象是否有這個名字的屬性,name必須為字符串
#測試getattr,setattr,hasattr
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return “Point({},{})”.format(self.x,self.y)
def show(self):
print(‘3’)
p1 = Point(4,5)
p2 = Point(10,10)
#getattr(Point,’show’)()#類無法調用自己的方法,可以使用類方法來增加類調用功能
print(repr(p1),repr(p2),sep=’\n’)
print(p1.__dict__)
setattr(p1,’y’,16)
setattr(p1,’z’,10)
print(getattr(p1,’__dict__’))
#動態調用方法如果show方法存在則調用
if hasattr(Point,’show’):
getattr(p1,’show’)()#getattr可以處理類或者實例化的對象
#動態增加方法
#為類增加方法
if not hasattr(Point,’add’):
setattr(Point,’add’,lambda self,other:Point(self.x+other.x,self.y+other.y))
print(Point.__dict__)
print(‘~’*40)
print(Point.add)
print(p1.add)
print(p1.add(p2))#綁定
print(‘~’*40)
#為實例增加方法,未綁定
if not hasattr(p1,’sub’):
setattr(p1,’sub’,lambda self,other:Point(self.x-other.x,self.y-other.y))
print(p1.sub(p1,p1))
print(p1.sub)
#add在誰里面,sub在誰里面
print(p1.__dict__)
print(Point.__dict__)
————————————–
<__main__.Point object at 0x000000B0E7EA89B0>
<__main__.Point object at 0x000000B0E7EA89E8>
{‘x’: 4, ‘y’: 5}
{‘x’: 4, ‘y’: 16, ‘z’: 10}
3
{‘__init__’: <function Point.__init__ at 0x000000B0E7E9B7B8>, ‘show’: <function Point.show at 0x000000B0E7E9B8C8>, ‘__dict__’: <attribute ‘__dict__’ of ‘Point’ objects>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘Point’ objects>, ‘__module__’: ‘__main__’, ‘__str__’: <function Point.__str__ at 0x000000B0E7E9B840>, ‘__doc__’: None, ‘add’: <function <lambda> at 0x000000B0E7E9B730>}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<function <lambda> at 0x000000B0E7E9B730>
<bound method <lambda> of <__main__.Point object at 0x000000B0E7EA89B0>>
Point(14,26)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Point(0,0)
<function <lambda> at 0x000000B0E7E9B950>
{‘sub’: <function <lambda> at 0x000000B0E7E9B950>, ‘y’: 16, ‘z’: 10, ‘x’: 4}
{‘__init__’: <function Point.__init__ at 0x000000B0E7E9B7B8>, ‘show’: <function Point.show at 0x000000B0E7E9B8C8>, ‘__dict__’: <attribute ‘__dict__’ of ‘Point’ objects>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘Point’ objects>, ‘__module__’: ‘__main__’, ‘__str__’: <function Point.__str__ at 0x000000B0E7E9B840>, ‘__doc__’: None, ‘add’: <function <lambda> at 0x000000B0E7E9B730>}
上例中的綁定和未綁定?
  • 思考:這種動態增加屬性的方式和裝飾器修飾一個類、Mixin方式的差異?
  • 這種動態增刪屬性的方式是運行時改變類或者實例的方式,但是裝飾器或者Mixin都是定義時就決定了,因此反射能力具有更大的靈活性
  • 練習
  • 1.命令分發器,通過名稱找對應的函數執行。思路:名稱找對象的方法
#方法一:函數實現分發器
def dispatcher():
cmds = {}
def reg(cmd,fn):
if isinstance(cmd,str):
cmds[cmd] = fn
else:
print(‘error’)
#print(cmds)#查看字典中的內容,cmd:lambda內存地址
def run():
while True:
cmd = input(“plz input command:”)
if cmd.strip() == ‘quit’:
return
cmds.get(cmd.strip(),defaultfn)()#get得到一個函數方法所以后面需要加()來調用,如果get不存在的key則返回默認的defaultfn函數。
def defaultfn():
pass
return reg,run
reg,run = dispatcher()
reg(‘cmd1’,lambda :1)
reg(‘cmd2’,lambda :2)
run()
#方法二:用類方法實現分發器
#類實現分發器:
#1.注冊方法
#2.執行方法
class Dispatcher():
def __init__(self):
self._run()#不用實例化也能用?,其實在類的調用中已經實例化,只不過沒將實例化的對象賦給一個名字
def cmd1(self):
print(“I’m cmd1”)
def cmd2(self):
print(“I’m cmd2”)
def _run(self):
while True:
cmd = input(“please input key”)
if cmd.strip() == “quit”:
break
getattr(self,cmd,lambda : print(‘Unknown Command {}’.format(cmd)))()
Dispatcher()
#print(Dispatcher.__dict__)
  • 方法二中,使用getattr方法找到對象的屬性的方式,比自己維護一個字典來建立名稱和函數之間的關系好得多
反射相關的魔術方法
  • __getattr__()、__setattr__()、__delattr__(),三個魔術方法
  • __getattr__()測試:
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def show(self):
print(self.x,self.y)
def __getattr__(self, item):
return “missing {}”.format(item)
p1 = Point(4,5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
————————-
4
6
0
missing t
一個類的屬性會按照繼承關系找,如果找不到,就會執行getattr方法,如果沒有這個方法,就會拋出AttributeError異常表示找不到屬性
查找屬性順序為:
instance.dict—>instance.class.dict—>繼承的祖先類(直到object)的dict—>調用getattr()
  • __setattr__()舉例:
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def show(self):
print(self.x,self.y)
def __getattr__(self, item):
return “missing {}”.format(item)
def __setattr__(self, key, value):
print(“setattr {} ={}”.format(key,value))
p1 = Point(4,5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
p1.x = 50
print(p1.__dict__)
p1.__dict__[‘x’] = 60
print(p1.__dict__)
print(p1.x)
—————————
setattr x =4
setattr y =5
missing x
6
0
missing t
setattr x =50
{}#因為setattr魔術方法對攔截屬性的添加,修改操作所以x,y都沒在字典中
{‘x’: 60}
60
實例通過點設置屬性,如同self.x = x,就會調用__setattr__(),屬性要加到實例的dict中,就需要自己完成
setattr方法,可以攔截實例屬性的添加、修改操作,如果要設置生效,需要自己操作實例的dict,此例子中需要再setattr函數中加入self.__dict__[key] = value
  • __delattr__()舉例:
class Base:
n = 0
class Point(Base):
Z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def __delattr__(self, item):
print(‘Can not del {}’.format(item))
p = Point(14,5)
del p.x
p.z = 15
del p.z
del p.Z
print(Point.__dict__)
print(p.__dict__)
del Point.Z
print(Point.__dict__)
——————————
Can not del x
Can not del z
Can not del Z
{‘__doc__’: None, ‘__delattr__’: <function Point.__delattr__ at 0x0000002959D3B7B8>, ‘Z’: 6, ‘__module__’: ‘__main__’, ‘__init__’: <function Point.__init__ at 0x0000002959D3B730>}
{‘x’: 14, ‘y’: 5, ‘z’: 15}
{‘__doc__’: None, ‘__delattr__’: <function Point.__delattr__ at 0x0000002959D3B7B8>, ‘__module__’: ‘__main__’, ‘__init__’: <function Point.__init__ at 0x0000002959D3B730>}
可以阻止通過實例刪除屬性的操作。但是通過類依然可以刪除屬性。如同Z
  • __getattribute__舉例:
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def __getattr__(self, item):
return “missing {}”.format(item)
def __getattribute__(self, item):
return item
p1 = Point(4,5)
print(p1.__dict__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
print(Point.__dict__)
print(Point.z)
————————————
__dict__
x
z
n
t
{‘__init__’: <function Point.__init__ at 0x0000000100FCB730>, ‘__doc__’: None, ‘z’: 6, ‘__module__’: ‘__main__’, ‘__getattribute__’: <function Point.__getattribute__ at 0x0000000100FCB840>, ‘__getattr__’: <function Point.__getattr__ at 0x0000000100FCB7B8>}
6
  • 實例的所有的屬性訪問,第一個都會調用getattribute方法,它阻止了屬性的查找,該方法應該返回(計算后的)值或者拋出一個AttributeError異常
  • 他的return值將作為屬性查找的結果,如果拋出AttributError異常,則會直接調用getattr方法,因為表示屬性沒有找到
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def __getattr__(self, item):
return “missing {}”.format(item)
def __getattribute__(self, item):
# raise AttributeError(‘Not Found’)
# pass
# return self.__dict__[item]
return object.__getattribute__(self,item)
p1 = Point(4,5)
print(p1.__dict__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
print(Point.__dict__)
print(Point.z)
————————————-
{‘y’: 5, ‘x’: 4}
4
6
0
missing t
{‘__getattribute__’: <function Point.__getattribute__ at 0x00000068B1D3B840>, ‘__doc__’: None, ‘__init__’: <function Point.__init__ at 0x00000068B1D3B730>, ‘z’: 6, ‘__getattr__’: <function Point.__getattr__ at 0x00000068B1D3B7B8>, ‘__module__’: ‘__main__’}
6
  • getattribute方法中為了避免在該方法中無限遞歸,他的實現應該永遠調用基類的同名方法以訪問需要的任何屬性,例如object.getattribute(self,name).
  • 注意:除非你明確的知道getattribute方法用來做什么,否則不要使用它
總結:
魔術方法
意義
__getattr__()
通過搜索實例、實例的類及祖先類查不到屬性,就會調用此方法
__setattr__()
通過.訪問實例屬性,進行增加、修改都要調用它
__delattr__()
當通過實例來刪除屬性時調用此方法
__getattribute__()
實例所有的屬性調用都從這個方法開始
  • 屬性查找順序
  • 實例調用__getattribute__()–>instance.__dict__–>instance.__class__.__dict__–>繼承祖先類(直到object)的__dict__–>調用__getattr__()

本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/88799

(0)
Thunk_LeeThunk_Lee
上一篇 2017-11-21
下一篇 2017-11-21

相關推薦

  • Linux文件的權限與解析

    一,文件的基本權限: 通常,你使用ls -l 命令,就會看到這樣的行:   讓我們解析一下這些字段代表的意思: -rw-r–r–. 1 root root 1018 Nov 6 2016 usb_modeswitch.conf 文件類型權限  硬鏈接數 文件所有者 文件所屬組 文件容量  文件最后被修改時…

    2017-07-30
  • Linux命令的別名與管道命令的詳解

    Linux中命令的別名與管道命令的詳解 命令別名 在使用和維護liunx時,將會使用大量的命令,而一些命令加上參數時輸入比較繁瑣,此時我們可以定義一個別名來代替繁瑣的命令。 系統定義的別名 可以輸入 alias 命令查看系統中已經定義好的命令的別名; [root@localhost ~]# alias alias cp=’cp -i’ alias egrep…

    Linux干貨 2018-03-03
  • 網絡管理之多網卡bond,mode1

    一.概述 Bonding     就是將多塊網卡綁定同一IP地址對外提供服務,可以實現高可用或者負載均衡。當然,直接給兩塊網卡設置同一IP地址是不可能的。通過bonding,虛擬一塊網卡對外提供連接,物理網卡的被修改為相同的MAC地址。 Bonding的工作模式     Mode…

    Linux干貨 2016-09-15
  • Python內置數據結構——集合set

    集合 定義 set翻譯為集合 collection翻譯為集合類型,是一個較大的概念 set是一個可變的、無序的、不重復的元素組成的集合 set的元素要求必須可以hash,目前已學的不可hash的類型只有list、set 元素不可以索引 set可以迭代 set的初始化 set_1 =set() #表示定義一個空集合set_1 set_1 =set(iterab…

    Linux干貨 2017-10-03
  • select循環解析

    前言:   select命令用于創建菜單,在select循環執行時會根據list數組給出選擇菜單,用戶選擇后的結果保存在變量中,然后給出菜單,等待用戶選擇。select是個死循環,如果用戶用戶想跳出選擇循環,需要在循環體中根據條件增加break語句。 格式: select variable in list do 循環體命令 done 示例: 在這個…

    Linux干貨 2016-08-24
欧美性久久久久