Python之路(十二):描述符,类装饰器,元类

python基础之面向对象(描述符、类装饰器及元类)

 

描述符

描述符(__get__,__set__,__delete__)   # 这里着重描述了python的底层实现原理

  1、 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
    __get__():调用一个属性时,触发
    __set__():为一个属性赋值时,触发
    __delete__():采用del删除属性时,触发

复制代码
1 class Foo:   #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
2     def __get__(self,instance,owner):
3         print('get方法')
4     def __set__(self, instance, value):
5         print('set方法')
6     def __delete__(self, instance):
7         print('delete方法')
复制代码

  2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

复制代码
class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法')
    def __delete__(self, instance):
        print('===>delete方法')

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name='egon'
print(f1.name)
del f1.name
#疑问:何时,何地,会触发这三个方法的执行
复制代码

  3、描述符应用在什么时候,什么地方

复制代码
class D:
    def __get__(self, instance, owner):
        print("-->get")
    def __set__(self, instance, value):
        print("-->set")
    def __delete__(self, instance):
        print("-->delete")
class E:
    e = D() # 描述谁?

ee = E()
ee.y = 10 # 此时描述的是e  y则不会被描述
ee.e      # 访问e属性,则会触发__get__
ee.e = 2  # 为e进行赋值操作,则会触发__set__
del ee.e  # 删除e的属性,则会触发__delete__
# print(ee.__dict__)
复制代码

  4、描述符分为俩种形式。

    a.数据描述符(至少实现了__get__()和__set__()两种方法)

class Foo:
     def __set__(self, instance, value):
         print('set')
     def __get__(self, instance, owner):
         print('get')

    b.非数据描述符(没有实现__set__()方法)

1 class Foo:
2     def __get__(self, instance, owner):
3         print('get')    

      注意事项:
      一、描述符本身应该定义成新式类,被代理的类也应该是新式类
      二、必须把描述符定义成另外一个类触发的类属性,不能为定义到构造函数

  5、严格遵循描述符的优先级别,由高到低

     a.类属性—》b.数据数据描述符—》c.实例属性—》d.非数据描述符—》e.找不到的属性触发__getattr__()

复制代码
 1 class Foo:
 2     def __get__(self,instance,owner):
 3         print('===>get方法')
 4     def __set__(self, instance, value):
 5         print('===>set方法')
 6     def __delete__(self, instance):
 7         print('===>delete方法')
 8 
 9 class Bar:
10     x=Foo()   #调用foo()属性,会触发get方法
11 
12 print(Bar.x)  #类属性比描述符有更高的优先级,会触发get方法
13 Bar.x=1       #自己定义了一个类属性,并赋值给x,跟描述符没有关系,所以不会触发描述符的方法
14 # print(Bar.__dict__)
15 print(Bar.x)
16 
17 
18 ===>get方法
19 None
20 1
复制代码
复制代码
#有get,set就是数据描述符,数据描述符比实例属性有更高的优化级

class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法')
    def __delete__(self, instance):
        print('===>delete方法')

class Bar:
    x = Foo()  # 调用foo()属性,会触发get方法

b1=Bar()   #在自己的属性字典里面找,找不到就去类里面找,会触发__get__方法
b1.x       #调用一个属性的时候触发get方法
b1.x=1     #为一个属性赋值的时候触发set方法
del b1.x   #采用del删除属性时触发delete方法

===>get方法
===>set方法
===>delete方法
复制代码
复制代码
 1 #类属性>数据描述符>实例属性
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print('===>get方法')
 6     def __set__(self, instance, value):
 7         print('===>set方法')
 8     def __delete__(self, instance):
 9         print('===>delete方法')
10 
11 class Bar:
12     x = Foo()             #调用foo()属性,会触发get方法
13 
14 b1=Bar()                  #实例化
15 Bar.x=11111111111111111   #不会触发get方法
16 b1.x                      #会触发get方法
17 
18 del Bar.x                 #已经给删除,所以调用不了!报错:AttributeError: 'Bar' object has no attribute 'x'
19 b1.x
复制代码
复制代码
#实例属性>非数据描述符
class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')

class Bar:
    x = Foo()

b1=Bar()
b1.x=1
print(b1.__dict__)  #在自己的属性字典里面,{'x': 1}

{'x': 1}
复制代码
复制代码
 1 #非数据描述符>找不到
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print('===>get方法')
 6 
 7 class Bar:
 8     x = Foo()
 9     def __getattr__(self, item):
10         print('------------>')
11 
12 b1=Bar()
13 b1.xxxxxxxxxxxxxxxxxxx    #调用没有的xxxxxxx,就会触发__getattr__方法
14 
15 
16 ------------>    #解发__getattr__方法
复制代码

   6、关于描述符的应用(类型检测的应用)

复制代码
class Typed:
    def __get__(self, instance,owner):
        print('get方法')
        print('instance参数【%s】' %instance)
        print('owner参数【%s】' %owner)       # owner是显示对象是属于谁拥有的
    def __set__(self, instance, value):
        print('set方法')
        print('instance参数【%s】' %instance) # instance是被描述类的对象(实例)
        print('value参数【%s】' %value)       # value是被描述的值
    def __delete__(self, instance):
        print('delete方法')
        print('instance参数【%s】'% instance)
class People:
    name=Typed()
    def __init__(self,name,age,salary):
        self.name=name        #触发的是代理
        self.age=age
        self.salary=salary

p1=People('alex',13,13.3)
#'alex'             #触发set方法
p1.name             #触发get方法,没有返回值
p1.name='age'       #触发set方法
print(p1.__dict__)
#{'salary': 13.3, 'age': 13}  # 因为name已经被描述,所以实例的属性字典并不存在name
# 当然也说明一点实例属性的权限并没有数据描述符的权限大


set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【alex】
get方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
owner参数【<class '__main__.People'>】
set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【age】
{'salary': 13.3, 'age': 13}
复制代码

 

复制代码
class Foo:
    def __init__(self,key,pd_type):
        self.key = key
        self.pd_type = pd_type
    def __get__(self, instance, owner):
        print("get")
        return instance.__dict__[self.key] # 返回值是 instace对象属性字典self.key所对应的值
    def __set__(self, instance, value):
        print(value) # 输出value所对应的值
        if not isinstance(value,self.pd_type): # 判断被描述的值 是否 属于这个类的
            raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) # 为否 则抛出类型异常
        instance.__dict__[self.key] = value # True 对instance对象的属性字典进行赋值操作
    def __delete__(self, instance):
        print("delete")
        instance.__dict__.pop(self.key) # 如果进行删除操作,也是对instance对象的属性字典进行删除操作
class Sea:
    name = Foo("name",str)      # 向描述符传入俩个值
    history = Foo("history",int)
    def __init__(self,name,addr,history):
        self.name = name
        self.addr = addr
        self.history = history
s1 = Sea("北冰洋","北半球",10000)
print(s1.__dict__)
print(s1.name) # 对被描述的属性进行访问,触发__get__

北冰洋
10000
{'addr': '北半球', 'history': 10000, 'name': '北冰洋'}
get
北冰洋
复制代码

 

 装饰器和描述符实现类型检测的终极版本

  7、描述符总结

      描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

    描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

    a.利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

复制代码
class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(Tom',1,1)
print(r1.area)
复制代码

 

复制代码
# 伪造的property
class Wzproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        """ 如果类去调用 instance 为None"""
        print("get")
        if instance is None: 
            return self
        setattr(instance,self.func.__name__,self.func(instance)) # 给实例字典设置值,避免重复计算
        return self.func(instance)
class Sea:
    def __init__(self,name,history,speed):
        self.name = name
        self.history = history
        self.speed = speed
    @Wzproperty # test = Wzptoperty(test)
    def test(self):
        return self.history * self.speed
s1 = Sea("大西洋",10,20)
# print(Sea.__dict__)
# print(Sea.test) # 如果类去调用 描述符的instance 此时是None
print(s1.test)
print(s1.test) # 这一次就不会触发描述符,因为实例属性字典就有
"""因为有了为实例的属性字典设置了结果。所以会率先从自己的属性字典找
其次触发非数据描述符,同时也声明了实例属性的权限大于非数据描述。
如果给描述符+__set__,描述符就变为数据描述符,根据权限实例再去用不会先去
自己的属性字典,而是触发描述符的操作"""
print(s1.__dict__)

控制台输出
get
200
200
{'test': 200, 'speed': 20, 'name': '大西洋', 'history': 10} # 实例的属性字典
复制代码

 

复制代码
# 伪造的classmethod
class Wzclassmethod:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        print("get")
        def bar():
            return self.func(owner)  #  test(Sea)
        return bar
    def __set__(self, instance, value):
        print("set")
class Sea:
    long = 10
    kuan = 20
    @Wzclassmethod  # test = Wzclassmethod(test)
    def test(cls):
        print("长%s 宽%s" %(cls.long,cls.kuan))
Sea.test()
复制代码

 

复制代码
# 伪造的staticmethod
import hashlib,time
class Wzstaticmethod:
    def __init__(self,func):
        self.func = func
    def __set__(self, instance, value):
        print("set")
    def __get__(self, instance, owner):
        print("get")
        def bar():
            if instance is None:
                return self.func()
        return bar
    def __delete__(self, instance):
        print("delete")
class Onepiece:
    def __init__(self):
        pass
    @Wzstaticmethod # test = Wzstaticmethod(test)
    def test(x=1):
        hash = hashlib.md5()
        hash.update(str(time.time()).encode("utf-8"))
        filename = hash.hexdigest()
        print(filename)
        return filename
# print(Onepiece.__dict__)
Onepiece.test()
复制代码

类装饰器

  1、基本框架

复制代码
def deco(func):
    print('===================')
    return func  #fuc=test

@deco    #test=deco(test)
def test():
    print('test函数运行')
test()
复制代码

 

复制代码
def deco(obj):
    print('============',obj)
    obj.x=1   #增加属性
    obj.y=2
    obj.z=3
    return obj

@deco   #Foo=deco(Foo)   #@deco语法糖的基本原理
class Foo:
    pass

print(Foo.__dict__)  #加到类的属性字典中

输出
============ <class '__main__.Foo'>
{'__module__': '__main__', 'z': 3, 'x': 1, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'y': 2}
复制代码
复制代码
def Typed(**kwargs):
    def deco(obj):
        obj.x=1
        obj.y=2
        obj.z=3
        return obj
    print('====>',kwargs)
    return deco

@Typed(x=2,y=3,z=4)  #typed(x=2,y=3,z=4)--->deco  会覆盖原有值
class Foo:
    pass
复制代码
复制代码
def Typed(**kwargs):
    def deco(obj):
        for key,val in kwargs.items():
            setattr(obj,key,val)
        return obj
    return deco

@Typed(x=1,y=2,z=3)  #typed(x=1,y=2,z=3)--->deco
class Foo:
    pass
print(Foo.__dict__)

@Typed(name='egon')
class Bar:
    pass
print(Bar.name)

控制台输出
{'y': 2, '__dict__': <attribute '__dict__' of 'Foo' objects>, 'z': 3, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__', 'x': 1, '__doc__': None}

egon
复制代码

元类

  元类(metaclass)

class Foo:
     pass
 
f1=Foo() #f1是通过Foo类实例化的对象

  python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)

  上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1)) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建
print(type(Foo)) # 输出:<type 'type'>  

  1、辣么,什么是元类?

  • 元类是类的类,是类的模板
  • 元类是用来控制如何创建类的,正如类是创建对象的模板一样
  • 元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)
  • type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

  创建类有俩种方式

class Foo:
    def func(self):
        print('from func')
复制代码
def func(self):
         print('from func')
x=1
Foo=type('Foo',(object,),{'func':func,'x':1})


type要接收三个参数
1、将要创建的类名
2、继承的类
3、类的属性字典
复制代码
复制代码
# 方式1
class Foo:
    pass
# 方式2
Bar = type("Bar",(object,),{})
print(Foo.__dict__)
print(Bar.__dict__)


控制台输出
{'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__dict__': <attribute '__dict__' of 'Foo' objects>}
{'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__dict__': <attribute '__dict__' of 'Bar' objects>}
复制代码

  2、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

复制代码
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)
    def __call__(self, *args, **kwargs):
        print("call")

class Slamdunk(metaclass=Mytype): # Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    def __init__(self,name):
        self.name = name

s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__

控制台输出
<class '__main__.Slamdunk'>  # 元类创建的实例(对象)
Slamdunk # 实例名
() # 继承的类,在python3中都默认继承object,即都为新式类
{'__qualname__': 'Slamdunk', '__init__': <function Slamdunk.__init__ at 0x000002106AFBF840>, '__module__': '__main__'} # 实例类的属性字典
call # 实例+() 触发了元类的__call__方法
复制代码
复制代码
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs): # 传的值是怎么传进去的,就去怎么接收
        print("call")
        obj = object.__new__(self) # 生成一个实例
        self.__init__(obj,*args,**kwargs)   # 这里的self是Mytype产生的实例,这一步触发 Slamdunk 的构造方法
        return obj # __call__方法下的返回值是 self 产生的实例 赋值给s1
class Slamdunk(metaclass=Mytype):
    #  Slamdunk = Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    # 触发元类的__init__(元类的构造方法)
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__
print(s1.__dict__) # 可以访问到实例对象的属性字典
复制代码

 

复制代码
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        self.__init__(obj,*args,**kwargs) 
        return obj 
class Slamdunk(metaclass=Mytype):
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
print(s1.__dict__) 

控制台输出
<class '__main__.Slamdunk'>
{'name': '樱木花道'}

# 可以加断点体验
复制代码

 

posted @ 2018-11-16 11:58  __Miracle  阅读(1335)  评论(0编辑  收藏  举报