三个魔术方法:

__get__()

__set__()

__delete__()

object.__get__(self,实例名,owner)    #owner = 属主 ,instance = 属主类owner的实例

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

object.__delete__(self,实例名)

更改属性的行为,当属性等于实例的时候,则可以进行操作

例:

1
2
3
4
5
6
7
class  A:
     def  __init__( self ):
         self .a1  =  'a1'
class  B:
     =  A()
     def  __init__( self ):
         pass


这样是可以执行的,首先定义好了A

通过定义B的x属性,调用A()

相当于在B类中执行:

1
2
3
print (A().a1)
=  A()
print (x.a1)


这两个是等价的

标记执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class  A:
     def  __init__( self ):
         print ( 'init' )
         self .a1  =  'a1'
class  B:
     =  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:
     =  A()
     def  __init__( self ):
         print ( 'B init' )
         self .x  =  100
=  B()
print (B.x.a1)
init
B init
a1


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

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

引入描述器

_

1
2
3
4
5
6
7
8
9
10
11
12
13
__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:
     =  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

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


对B实例化后打印查看

1
2
3
4
print ( 'B.x : ' ,B.x)
print ()
=  B()
print ( 'b.x.a1 : ' ,b.x.a1)


返回如下:

1
2
3
4
5
6
7
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类型

归根结底,都是与类属性有关系

1
2
=  B()
print (B.x)


返回如下

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


对照get定义的方法:

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


执行效果如下:

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


原来的实例返回是None,通过get方法变为了类的属性

1
2
3
4
=  B()
print (B.x)
print ( '-'  *  90 )
print (b.x.a1)

凡是进入描述器的三个方法之一,都是会被拦截进行操作

返回如下所示:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
class  A:
     def  __init__( self ):
         print ( 'A init' )
     def  __get__( self ,instance,owner):
         print ( 'A get' , self ,instance,owner)
         return  self
class  B:
     =  A()
     def  __init__( self ):
         print ( 'B.init' )
# print(B.x)
=  B()
print (B.x)

返回如下:

1
2
3
4
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__ 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:
     =  A()
     def  __init__( self ):
         self .x  =  100
=  B()
print (b.x)


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

1
2
3
4
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进行跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
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:
     =  A()
     def  __init__( self ):
         self .x  =  10


对每个函数进行标记并跟进:

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

当访问x属性,直接在A()中被__get__拦截

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

查看类型字典

1
2
3
4
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class  A:
     def  __init__( self ,value = 'abc' ):
         self .a1  =  value
     def  __get__( self ,instance,owner):
         return  self
class  B:
     =  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方法

1
2
3
4
{ '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先拦截

1
2
3
4
5
6
7
8
9
10
11
!!!!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,但是什么都没有操作

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


再访问的时候,再将实例返回回来,get就进行操作了

本质

主要看字典,一点点看到底修改了哪些,通过实例的方式无法修改属性

主要的特点是把实例从__dict__中去掉了,造成了该属性如果是数据描述则优先访问的假象

说到底,属性访问顺序就从来没有变过

一句话总结:非数据描述器可以覆盖,数据描述器直接修改类

在py中,所有的方法都是数据描述器

实现一个static装饰器

静态方法的本质

全局函数放到类中,使用时候,通过我们的类对象进行使用

1
2
3
4
5
6
7
8
9
class  A:
     @ staticmethod
     def  bar():
         return  1
     def  test( self ):
         return  2
=  A()
print (f.test)
print (f.bar)


查看结果

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


静态方法是作为一个function传递进来的

首先我们搞明白需求 如何调用的 A.foo 这么调用 

基础框架

1
2
3
4
5
6
7
8
9
10
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的实例

1
2
3
=  A.foo
print (a)
None


相当于在定义foo的时候被传递给StaticMethod(foo)

当前的foo相当于一个实例对象

返回的东西加了括号才可以调用,所以必须返回self

1
2
3
4
5
6
7
8
9
10
11
12
13
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' )
=  A.foo
print ( 'f:' ,f)


这个foo原封不动的返回,打印他们的内存地址查看

1
2
3
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中

1
2
3
4
5
class  A:
# @Static_Met
def  foo():
     print ( 'static' )
print (A.foo)


返回为:

<function A.foo at 0x0000000000E3F9D8>