• 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
    北冰洋
    用描述符实现的类型检测
    # 描述符和类装饰器的结合使用
    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]
        def __set__(self,instance,value):
            """ instance是对象(实例) """
            print("set")
            if not isinstance(value,self.pd_type): # 如果判断类型为False
                raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) # 抛出异常
            instance.__dict__[self.key] = value #操作对象(实例)的属性字典设置值
        def __delete__(self, instance):
            print("delete")
            instance.__dict__.pop(self.key)
    def Typed(**kwargs):
        def func(obj):
            for key,value in kwargs.items(): # 遍历字典的键值对
                setattr(obj,key,Foo(key,value)) # 为Sea的属性字典设置值。 并执行Foo 给Foo传入俩个值
                # print("--->" ,obj)
            return obj # func的返回值是Sea
        return func
    @Typed(name=str,addr=str,history=int)  # Typed函数运行结束实际上就是 @func --> sea=func(Sea)
    # Typed运行,并将参数全部传给kwargs
    class Sea:
        def __init__(self,name,addr,history):
            self.name = name
            self.addr = addr
            self.history = history
    s1 = Sea("大西洋","地球",10000)
    print(s1.__dict__)
    print(Sea.__dict__) # 此时的name,addr,history均被Foo所描述
    装饰器和描述符实现类型检测的终极版本

      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的特性回顾
    # 伪造的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} # 实例的属性字典
    伪造的property以及阐释
    # 伪造的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()
    伪造的classmethod
    # 伪造的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()
    伪造的staticmethod

    类装饰器

      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
    View Code
    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')
    方式A,用class定义
    def func(self):
             print('from func')
    x=1
    Foo=type('Foo',(object,),{'func':func,'x':1})
    
    
    type要接收三个参数
    1、将要创建的类名
    2、继承的类
    3、类的属性字典
    方式B,用type生成
    # 方式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': '樱木花道'}
    
    # 可以加断点体验
    实现创建类的流程 精简版
  • 相关阅读:
    ajax翻页效果模仿yii框架
    一个伪ajax图片上传代码的例子
    php下intval()和(int)转换有哪些区别
    php中iconv函数使用方法
    php字符串截取问题
    ASP.net UrlRewrite的防盗链功能
    ASP.NET中application对象
    javascript回车完美实现tab切换功能
    有关c#装箱和拆箱知识整理
    PHP四大安全策略
  • 原文地址:https://www.cnblogs.com/zhangliang91/p/10547503.html
Copyright © 2020-2023  润新知