1. 程式人生 > >python魔術方法之裝飾器

python魔術方法之裝飾器

裝飾器 描述器

三個魔術方法:

__get__()

__set__()

__delete__()

object.__get__(self,實例名,owner) #owner = 屬主 ,instance = 屬主類owner的實例

object.__set__(self,實例名,value)

object.__delete__(self,實例名)

更改屬性的行為,當屬性等於實例的時候,則可以進行操作

例:

class A:
    def __init__(self):
        self.a1 = 'a1'
class B:
    x = A()
    def __init__(self):
        pass


這樣是可以執行的,首先定義好了A

通過定義B的x屬性,調用A()

相當於在B類中執行:

print(A().a1)
x = A()
print(x.a1)


這兩個是等價的

標記執行順序

class A:
    def __init__(self):
        print('init')
        self.a1 = 'a1'
class B:
    x = A()
    def __init__(self):
        print('B init')
print(B.x.a1)
init
a1
class A:
    def __init__(self):
        print('init')
        self.a1 = 'a1'
class B:
    x = A()
    def __init__(self):
        print('B init')
        self.x = 100
b = B()
print(B.x.a1)
init
B init
a1


涉及到字典的執行順序,所以,print(b.x.a1)是不行的

print(b.x.a1)
AttributeError: 'int' object has no attribute 'a1'

引入描述器

_

__get__(self,instance,owner)
class A:
    def __init__(self):
        print('A init')
        self.a1 = 'a1'
    def __get__(self, instance, owner):
        print(self,instance,owner)
class B:
    x = A()    #A() 就是一個描述器,當對B()或B的實例的x的屬性進行訪問,則成為A()的實例的方式,則調用__get__方法
    def __int__(self):
        print('B init')
        self.x = 100
print(B.x.a1)


發現報錯提示如下:

print(B.x.a1)

AttributeError: 'NoneType' object has no attribute 'a1'

提示 None類型是不能調用的,當通過一個屬性訪問,如果屬性是另一個類的實例,而恰好這個類又實現了描述器的方法之一

當訪問描述器的時候,如果是get觸發則返回當前實例以及描述器屬主的類型信息

所以,return返回為None的實例,則不能被調用

打印B.x 的類型,可看到為None

print(B.x)
def __get__(self, instance, owner):
    print(self,instance,owner)
<__main__.A object at 0x0000000000B88390> None <class '__main__.B'>
None


對B實例化後打印查看

print('B.x : ',B.x)
print()
b = B()
print('b.x.a1 : ',b.x.a1)


返回如下:

A init
Traceback (most recent call last):
<__main__.A object at 0x0000000000DB80B8> None <class '__main__.B'>
B.x :   None
    print('b.x.a1 :  ',b.x.a1)
<__main__.A object at 0x0000000000DB80B8> <__main__.B object at 0x0000000000DB83C8> <class '__main__.B'>
AttributeError: 'NoneType' object has no attribute 'a1'


發現依舊被攔截,所調用的是一個None類型

歸根結底,都是與類屬性有關系

b = B()
print(B.x)


返回如下

A init
<__main__.A object at 0x0000000000718390> None <class '__main__.B'>
None


對照get定義的方法:

def __get__(self, instance, owner):
    print(self,instance,owner)


執行效果如下:

A init
<__main__.A object at 0x0000000000718390> None <class '__main__.B'>
None


原來的實例返回是None,通過get方法變為了類的屬性

b = B()
print(B.x)
print('-' * 90)
print(b.x.a1)

凡是進入描述器的三個方法之一,都是會被攔截進行操作

返回如下所示:

A init
<__main__.A object at 0x0000000000858390> None <class '__main__.B'>
None
------------------------------------------------------------------------------------------
分別返回了 self, instance, owner
<__main__.A object at 0x0000000000858390> <__main__.B object at 0x0000000000836320> <class '__main__.B'>

當一個類的類屬性等於另一個類的實例的時候,則實現了描述器方法,則是描述器的類

如果是類屬性上訪問的話,直接觸發攔截

如果是實例屬性訪問,則不會訪問描述器方法觸發

解決返回值問題:return self

class A:
    def __init__(self):
        print('A init')
    def __get__(self,instance,owner):
        print('A get',self,instance,owner)
        return self
class B:
    x = A()
    def __init__(self):
        print('B.init')
# print(B.x)
b = B()
print(B.x)

返回如下:

A init
B.init
A get <__main__.A object at 0x0000000000DA6518> None <class '__main__.B'>
<__main__.A object at 0x0000000000DA6518>


如果只是獲取當前屬性的手段,通過屬性的描述器可以操作屬主

這樣可以解決不能訪問的弊端

在遇到get中應該return一個有意義的值,至於return什麽值合適,需要後期定義,具體就是可以獲取屬主的類及屬性

如果僅實現了__get__,就是非數據描述符

同時實現了__set__ + __get__ 就是數據描述符

對日常來講重要的是get和set同時出現

如果不是訪問類的屬性的話,則不會觸發任何效果,只能是實例才會被攔截

__set__ 方法

class A:
    def __init__(self):
        print('A init')
        self.a1 = 'a1'
    def __get__(self,instance,owner):
        print('!!!B get',self,instance,owner)
        return self
    def __set__(self,instance,value):    # #加入set之後,這裏原本是為實例設置,但是觸發了set
        print('~~A.set',self,instance,value)
class B:
    x = A()
    def __init__(self):
        self.x = 100
b = B()
print(b.x)


可以看到,首先被__set__方法先攔截

A init
~~A.set <__main__.A object at 0x0000000000DD45C0> <__main__.B object at 0x0000000000DB7320> 100
!!!B get <__main__.A object at 0x0000000000DD45C0> <__main__.B object at 0x0000000000DB7320> <class '__main__.B'>
<__main__.A object at 0x0000000000DD45C0>


對b.x進行跟進

class A:
    def __init__(self):
        print('A init')
        self.a1 = 'a1'
    def __get__(self,instance,owner):
        print('!!!B get',self,instance,owner)
        return self
    def __set__(self,instance,value):
        print('~~A.set',self,instance,value)
class B:
    x = A()
    def __init__(self):
        self.x = 10


對每個函數進行標記並跟進:

b = B()
A init
~~A.set <__main__.A object at 0x0000000000A945C0> <__main__.B object at 0x0000000000A77320> 100

當訪問x屬性,直接在A()中被__get__攔截

print(b.x)
!!!B get <__main__.A object at 0x0000000000DA45C0> <__main__.B object at 0x0000000000D87320> <class '__main__.B'

查看類型字典

print(b.__dict__)
{}
print(B.__dict__)
{'x': <__main__.A object at 0x0000000000D77588>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__module__': '__main__', '__init__': <function B.__init__ at 0x0000000000DDAAE8>, '__dict__': <attribute '__dict__' of 'B' objects>}

看到沒有dict內容

照常來講會修改dict,但是觸發了set描述器,也就self.x = 這條語句沒有被加入到dict

總結:

set如果對實例化中的屬性定義,則對屬性做修改

說到底就是如果實例的字典裏沒有,則去類的dict中去查找,set是對類的dict進行修改

通過這樣的方式繞開了字典搜索

官方解釋:有set,實例的優先級最高,如果沒有set則類的優先級比較高

總結:

get:

class A:
    def __init__(self,value='abc'):
        self.a1 = value
    def __get__(self,instance,owner):
        return self
class B:
    x = A()
    def __init__(self):
        self.x = A(123)
print(B.x)
print(B.x.a1)
<__main__.A object at 0x0000000000DB84A8>
abc
print(b.x.a1)
123
 
print(B.x.a1)
~~~~A__get__ <__main__.A object at 0x0000000000DA84A8> None <class '__main__.B'>
abc


print(b.__dict__),發現實例的dict中存在x方法

{'x': <__main__.A object at 0x00000000006F7940>}
print(B.__dict__)
{'__init__': <function B.__init__ at 0x0000000000E2AA60>, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x00000000006F70F0>, '__dict__': <attribute '__dict__' of 'B' objects>, '__doc__': None, '__module__': '__main__'}
set:


只要設置相關的屬性,實例方法添加不上dict,而set優先級別高,可以看到都是針對A的對象

print(b.x.a1) 被set先攔截

!!!!A__set__ <__main__.A object at 0x0000000000746550> <__main__.B object at 0x0000000000727278> <__main__.A object at 0x00000000007272B0>
~~~~A__get__ <__main__.A object at 0x0000000000746550> <__main__.B object at 0x0000000000727278> <class '__main__.B'>
abc
print(B.x.a1)
!!!!A__set__ <__main__.A object at 0x0000000000726550> <__main__.B object at 0x0000000000707278> <__main__.A object at 0x00000000007072B0>
~~~~A__get__ <__main__.A object at 0x0000000000726550> None <class '__main__.B'>
abc
print(b.__dict__),發現實例的dict中不存在方法
{}
print(B.__dict__)
{'x': <__main__.A object at 0x0000000000DB7518>, '__module__': '__main__', '__init__': <function B.__init__ at 0x0000000000E1BAE8>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>}


一句話總結:一旦使用set,只能操作類屬性

下面例子中,雖然會觸發set,但是什麽都沒有操作

b = B()
b.xxx = 777
!!!!A__set__ <__main__.A object at 0x0000000000BE6550> <__main__.B object at 0x0000000000BC7278> 777
{'xxxx': 777}


再訪問的時候,再將實例返回回來,get就進行操作了

本質

主要看字典,一點點看到底修改了哪些,通過實例的方式無法修改屬性

主要的特點是把實例從__dict__中去掉了,造成了該屬性如果是數據描述則優先訪問的假象

說到底,屬性訪問順序就從來沒有變過

一句話總結:非數據描述器可以覆蓋,數據描述器直接修改類

在py中,所有的方法都是數據描述器

實現一個static裝飾器

靜態方法的本質

全局函數放到類中,使用時候,通過我們的類對象進行使用

class A:
    @staticmethod
    def bar():
        return 1
    def test(self):
        return 2
f = A()
print(f.test)
print(f.bar)


查看結果

<bound method A.test of <__main__.A object at 0x0000000000D86278>>
<function A.bar at 0x0000000000DF11E0>


靜態方法是作為一個function傳遞進來的

首先我們搞明白需求 如何調用的 A.foo 這麽調用

基礎框架

class StaticMethod:
    def __init__(self,fn):
        self.fn = fn
    def __get__(self,instance,owner):
        print(self,instance,owner)
class A:
    @StaticMethod
    def foo():
        print('static')
print(A.__dict__)


調用返回None,因為沒有A的實例

a = A.foo
print(a)
None


相當於在定義foo的時候被傳遞給StaticMethod(foo)

當前的foo相當於一個實例對象

返回的東西加了括號才可以調用,所以必須返回self

class Static_Method:
    def __init__(self,fn):
        print('fn:',fn)
        self.fn = fn
    def __get__(self,instance,owner):
        print(self,instance,owner)
        return self.fn
class A:
    @Static_Method
    def foo():
        print('static')
f = A.foo
print('f:',f)


這個foo原封不動的返回,打印他們的內存地址查看

fn: <function A.foo at 0x0000000000DEAA60>
<__main__.Static_Method object at 0x0000000000A764E0> None <class '__main__.A'>
f: <function A.foo at 0x0000000000DEAA60>


技術分享圖片

等價式:foo = Static_Method(foo)

就是說,調用的時候,必須以func類型傳遞到Statice_Method中

class A:
# @Static_Met
def foo():
    print('static')
print(A.foo)


返回為:

<function A.foo at 0x0000000000E3F9D8>



python魔術方法之裝飾器