zoukankan      html  css  js  c++  java
  • (转)面向对象(深入)|python描述器详解

    原文:https://zhuanlan.zhihu.com/p/32764345

    # 类似函数的形式
    class A:
        def __init__(self, name, score):
            self.name = name # 普通属性
            self.score = score
            
        def getscore(self):
            return self._score
        
        def setscore(self, value):
            print('setting score here')
            if isinstance(value, int):
                self._score = value
            else:
                print('please input an int')
                
        score = property(getscore, setscore)
            
    a = A('Bob',90)
    a.name # 'Bob'
    a.score # 90
    a.score = 'bob' # please input an int

    分析上述调用score的过程

    • 初始化时即开始访问score,发现有两个选项,一个是属性,另一个是property(getscore, setscore)对象,因为后者中定义了__get____set__方法,因此是一个资料描述器,具有比属性更高的优先级,所以这里就访问了描述器
    • 因为初始化时是对属性进行设置,所以自动调用了描述器的__set__方法
    • __set__中对fset属性进行检查,这里即传入的setscore,不是None,所以调用了fsetsetscore方法,这就实现了设置属性时使用自定义函数进行检查的目的
    • __get__也是一样,查询score时,调用__get__方法,触发了getscore方法

    下面是另一种使用property的方法

    # 装饰器形式,即引言中的形式
    class A:
        def __init__(self, name, score):
            self.name = name # 普通属性
            self.score = score
            
        @property
        def score(self):
            print('getting score here')
            return self._score
        
        @score.setter
        def score(self, value):
            print('setting score here')
            if isinstance(value, int):
                self._score = value
            else:
                print('please input an int')
            
    a = A('Bob',90)
    # a.name # 'Bob'
    # a.score # 90
    # a.score = 'bob' # please input an int

    下面进行分析

    • 在第一种使用方法中,是将函数作为传入property中,所以可以想到是否可以用装饰器来封装
    • get部分很简单,访问score时,加上装饰器变成访问property(score)这个描述器,这个score也作为fget参数传入__get__中指定调用时的操作
    • 而set部分就不行了,于是有了setter等方法的定义
    • 使用了propertysetter装饰器的两个方法的命名都还是score,一般同名的方法后面的会覆盖前面的,所以调用时调用的是后面的setter装饰器处理过的score,是以如果两个装饰器定义的位置调换,将无法进行属性赋值操作。
    • 而调用setter装饰器的score时,面临一个问题,装饰器score.setter是什么呢?是scoresetter方法,而score是什么呢,不是下面定义的这个score,因为那个score只相当于参数传入。自动向其他位置寻找有没有现成的score,发现了一个,是property修饰过的score,这是个描述器,根据property的定义,里面确实有一个setter方法,返回的是property类传入fset后的结果,还是一个描述器,这个描述器传入了fgetfset,这就是最新的score了,以后实例只要调用或修改score,使用的都是这个描述器
    • 如果还有del则装饰器中的score找到的是setter处理过的score,最新的score就会是三个函数都传入的score
    • 对最新的score的调用及赋值删除都跟前面一样了

    property的原理就讲到这里,从它的定义我们可以知道它其实就是将我们设置的检查等函数传入get set等方法中,让我们可以自由对属性进行操作。它是一个框架,让我们可以方便传入其他操作,当很多对象都要进行相同操作的话,重复就是难免的。如果想要避免重复,只有自己写一个类似property的框架,这个框架不是传入我们希望的操作了,而是就把这些操作放在框架里面,这个框架因为只能实现一种操作而不具有普适性,但是却能大大减少当前问题代码重复问题

    下面使用描述器定义了Checkint类之后,会发现A类简洁了非常多

    class Checkint:
        
        def __init__(self, name):
            self.name = name
            
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return instance.__dict__[self.name]
            
        def __set__(self, instance, value):
            if isinstance(value, int):
                instance.__dict__[self.name] = value
            else:
                print('please input an integer')
    
    # 类似函数的形式
    class A:
        score = Checkint('score')
        age = Checkint('age')
        
        def __init__(self, name, score, age):
            self.name = name # 普通属性
            self.score = score
            self.age = age
            
    a = A('Bob', 90, 30)
    a.name # 'Bob'
    a.score # 90
    # a.score = 'bob' # please input an int
    # a.age='a' # please input an integer

    描述器的应用

    因为我本人也刚刚学描述器不久,对它的应用还不是非常了解,下面只列举我现在能想到的它有什么用,以后如果想到其他的再补充

    • 首先是上文提到的,它是实例方法、静态方法、类方法、property的实现原理
    • 当访问属性、赋值属性、删除属性,出现冗余操作,或者苦思无法找到答案时,可以求助于描述器
    • 具体使用1:缓存。比如调用一个类的方法要计算比较长的时间,这个结果还会被其他方法反复使用,我们不想每次使用和这个相关的函数都要把这个方法重新运行一遍,于是可以设计出第一次计算后将结果缓存下来,以后调用都使用存下来的结果。只要使用描述器在__get__方法中,在判断语句下,obj.__dict__[self.name] = value。这样每次再调用这个方法都会从这个字典中取得值,而不是重新运行这个方法。(例子来源最后的那个例子)

    参考资料

    参考网页如下

    • 官网的中文翻译,给出了描述器功能的整体框架及一些实例
    • 官网英文
    • 简书文章,主要讲解访问描述器顺序,静态方法、类方法和实例方法下的访问情况
    • 简书文章,对官网的@Property细节解读
    • 一篇译文可以再看看他下面附的参考资料(不要先看这篇,这篇有点深,而且个人认为他有些实现方法舍近求远)
    • 如果想看更多文章,搜索时注意:搜索“描述器”得到的文章高度重复,基本上就是上面几篇了,搜“描述符”会找到更多文章
  • 相关阅读:
    C#’s ~ vs Java’s finalize
    做Java开发这一年
    assertThat, assertEquals, assertTrue
    给Cuke4Duke添加一个AfterAll标签(一):使用Cuke4Duke
    心理问题的根源
    何谓数学
    人生谁看透
    人本主义与自由意志
    哲学的基本问题
    贫穷与教育
  • 原文地址:https://www.cnblogs.com/liujiacai/p/10946768.html
Copyright © 2011-2022 走看看