美文网首页Python小推车python学习
Python学习打call第三十三天:描述器

Python学习打call第三十三天:描述器

作者: 暖A暖 | 来源:发表于2019-03-06 11:01 被阅读55次

    1.什么是描述器

    • 描述器必须是类属性,Python中,一个类实现了__get____set____delete__三个任意一个方法都称为描述 器;

    • 如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主;

    2.描述器的定义划分

    • 如果一个类仅仅实现了__get__()方法,称为非数据描述器non-data descriptor;

    • 如果一个类实现了__get__()__set__()方法,称为数据描述器data descriptor;

    3.非数据描述器

    # 示例1
    class Student1:
        def __init__(self):
            self.course = 'Python'
            print('Student1.__init__')
    
    class Student2:
        stu1 = Student1()     # Student1()返回的是Student1类的实例
    
        def __init__(self):
            print('Student2.__init__')
    
    print(Student2.stu1.course)
    
    # 创建Student2的实例对象
    stu2 = Student2()
    print(stu2.stu1.course)
    
    
    # 示例2, 引入描述器
    class Stduent1:
        def __init__(self):
            self.course = 'Python'
            print('Stduent1.__init__')
    
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
    
    class Stduent2:
        stu1 = Stduent1()
        def __init__(self):
            print('Stduent2.__init__')
    
    print(Stduent2.stu1.course)
    # Stduent2.stu1会访问Stduent1的实例,默认会调用__get__方法,但是__get__方法没有将实例返回,因此,Stduent2.stu1.course会报错
    
    stu2 = Stduent2()
    print(stu2.stu1.course)  # 一样的报错
    
    
    # 示例3, 引入描述器
    class Stduent1:
        def __init__(self):
            self.course = 'Python'
            print('Stduent1.__init__')
    
        def __get__(self, instance, owner):
            # 这里的self为Stduent1的实例. instance为实例, 如果是类访问,那么instance为None. owner是调用者的类
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self # 返回Student1的实例self
    
    class Stduent2:
        stu1 = Stduent1()
        def __init__(self):
            print('Stduent2.__init__')
    
    print(Stduent2.stu1.course)
    
    stu2 = Stduent2()
    print(stu2.stu1.course)
    
    • 函数包含一个 __get__()方法以便在属性访问时绑定方法,这就是说所有的函数都是非数据描述器,它们返回绑定还是非绑定的方法取决于他们是被实例调用还是被类调用;

    4.数据描述器

    # 示例1:
    class Student1:
        def __init__(self):
            self.course = 'Python'
            print('Student1.__init__')
    
        def __get__(self, instance, owner):
            # 这里的self为Student1的实例. instance为实例, 如果是类访问,那么instance为None. owner是调用者的类
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self   # 返回Student1的实例self
    
    class Student2:
        stu1 = Student1()
    
        def __init__(self):
            print('Student2.__init__')
            self.y = Student1()   # 没有调用__get__方法
    
    print(Student2.stu1.course)
    
    stu2 = Student2()
    print(stu2.y)
    
    
    # 示例2:数据描述器
    class Student1:
        def __init__(self):
            self.course = 'Python'
            print('Student1.__init__')
    
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self
    
        def __set__(self, instance, value):
            print('self={} instance={} value={}'.format(self, instance, value))
            self.course = value
    
    class Student2:
        stu1 = Student1()
    
        def __init__(self):
            print('Student2.__init__')
            self.y = Student1()   # 调用了__get__方法
    
    print(Student2.stu1.course)
    
    stu2 = Student2()
    print(stu2.stu1)
    
    
    • 当非数据描述器是实例的变量时,实例访问非数据描述器不会调用__get__方法,只是访问了描述器类的实例;

    • 当数据描述器是实例的变量时,实例访问数据描述器会调用描述器的__get__方法;

    5.非数据描述器和数据描述器的访问顺序

    • 当存在描述器的时候,一个类实例的查找属性顺序为:先查找类或父类中是否有数据描述器属性,如果有那么,先访问数据描述器,如果没有数据描述器 --> 那么就会查找自己实例的dict属性,如果dict属性里面也没有找到 --> 然后会在类或父类的非数据描述器进行查找;

    6.非数据描述器和数据描述器的访问顺序的本质

    # 示例1:非数据描述器
    class Student1:
        def __init__(self):
            self.course = 'Python'
            print('Student1.__init__')
            
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self
        
    class Student2:
        stu1 = Student1()
        
        def __init__(self):
            print('Student2.__init__')
            self.x = 'Student1'
            
    print(Student2.stu1.course)
    
    stu2 = Student2()
    print(stu2.stu1)
    print(stu2.__dict__)  # 实例的__dict__属性中有 {'stu1': 'Student1'}
    
    # 示例2:数据描述器
    class Student1:
        def __init__(self):
            self.course = 'Python'
            print('XKD1.__init__')
            
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self
        
        def __set__(self, instance, value):
            print('self={} instance={} value={}'.format(self, instance, value))
            self.course = value
            
    class Student2:
        stu1 = Student1()
        
        def __init__(self):
            print('Student2.__init__')
            self.x = 'Student1'
            
    print(Student2.stu1.course)
    
    stu2 = Student2()
    print(stu2.stu1)
    print(stu2.__dict__)  # 实例的__dict__为空
    
    • 事实上,实例属性的查找顺序并没有改变,依然是实例的dict中的属性优先被访问;

    • 只是如果实例有属性是数据描述器的话,属性会被dict字典移除,因此就会访问类的属性,造成了数据描 述器优先访问的假象;

    7.哪些案例是描述器实现的

    • 属性装饰器@property(),是通过数据描述器实现;

    • 类方法装饰器@classmethod和静态方法装饰器@staticmethod都是通过非数据描述器实现;

    8.描述器演练

    (1)@staticmethod的实现

    class StaticMethod:
        def __init__(self, fn):
            self.fn = fn
    
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return self.fn
    
    class Stduent:
    
        @StaticMethod
        def show():   # show = StaticMethod(show)  , 此时的show方法,已经是StaticMethod类的实例
            print('静态方法实现')
    
    Stduent.show()
    Stduent().show()
    
    • 静态方法装饰器@staticmethod装饰的方法被装饰成了StaticMethod类的实例,可以看做一个类变量,因此类和 类的实例都可以访问这个类变量,那么就会调用__get__方法,返回self.fn,也就是原生的方法对象,最后调用这个方法对象;

    (2)@ClassMethod的实现

    from functools import partial
    
    class ClassMethod:
        def __init__(self, fn):
            self.fn = fn
    
        def __get__(self, instance, owner):
            print('self={} instance={} owner={}'.format(self, instance, owner))
            return partial(self.fn, owner)
    
    class Stduent:
        @ClassMethod
        def show(cls):    # show = ClassMethod(show)
            print('类方法实现')
    Stduent.show()
    Stduent().show()
    
    
    • 类方法的实现与静态方法类似,只是需要调用partial偏函数,将类提前作为参数传递;

    9.属性装饰器property实现原理分析

    class Property:
        def __init__(self, fget, fset=None):
            self.fget = fget
            self.fset = fset
    
        def __get__(self, instance, owner):
            if instance is not None:
                return self.fget(instance)
            return self
    
        def __set__(self, instance, value):
            if callable(self.fset):
                self.fset(instance, value)
            else:
                raise AttributeError('{} is not callable'.format(self.fset.__name__))
    
        def setter(self, fn):
            self.fset = fn
            return self
    
    class Stduent:
        def __init__(self, data):
            self.__data = data
            
        @Property      # data = Property(data)
        def data(self):
            return self.__data
    
        @data.setter   # data = data.setter(data)
        def data(self, value):
            self.__data = value
    
    stu= Stduent('Python')
    print(stu.data)
    
    stu.data = 'JAVA'
    print(stu.data)
    
    • Python内置的@property装饰器,把一个方法变成可以像属性那样,做取值用;

    • @data.setter装饰器,把一个方法变成可以像属性那样,作赋值用;

    • @propertydata.setter()同时使用,表示可读可写;

    参考:https://www.9xkd.com/user/plan-view.html?id=2294380826

    相关文章

      网友评论

        本文标题:Python学习打call第三十三天:描述器

        本文链接:https://www.haomeiwen.com/subject/hkzzuqtx.html