美文网首页编程基础与实践
读书笔记(3): 编写高质量python代码的59个有效方法

读书笔记(3): 编写高质量python代码的59个有效方法

作者: 阿瑟_TJRS | 来源:发表于2020-09-14 16:04 被阅读0次

    前言

    《编写高质量python代码的59个有效方法》这本书分类逐条地介绍了编写python代码的有效思路和方法,对理解python和提高编程效率有一定的帮助。本笔记简要整理其中的重要方法。

    承接上文https://www.jianshu.com/p/15a6050220e6
    https://www.jianshu.com/p/1f6a2b3b502e

    本篇介绍关于元类及属性的编程方法

    4.元类(MetaClass)及属性

    Python中的元类概念是一种比较模糊的概念,简单来讲就是可以把python中的class语句转译为元类,令其在每次定义具体的类时都提供独特的行为。

    Python还具有一个奇妙的特性:可以动态地定义对属性的访问操作,这种动态属性也有可能带来令人意外的副作用。

    用纯属性取代get和set方法

    # C++等其他语言常用写法
    
    # 在类中明确实现getter和setter
    class OldResistor(object):
        def __init__(self,ohms):
            self._ohms=ohms
        def get_ohms(self):
            return self._ohms
        def set_ohms(self,ohms):
            self._ohms=ohms
    r0=OldResistor(5)
    print(r0.get_ohms())
    r0.set_ohms(10)
    print(r0.get_ohms())
    r0.set_ohms(r0.get_ohms()+5)
    

    这种自定义获取和设置的操作,在其他语言中很常见,但在python中不需要手工实现,直接从public属性开始:

    class Resistor(object):
        def __init__(self,ohms):
            self.ohms=ohms
            self.vol=0
            self.current=0
    r1=Resistor(5)
     
    print(r1.ohms) # 5
    r1.ohms=10
    print(r1.ohms) # 10
    

    此外,还可以通过@property和setter来实现一些特殊操作,如下所示,在给成员变量_vol赋值的同时,还进行了其他变量的修改。

    class VolResistance(Resistor):
        def __init__(self,ohms):
            super().__init__(ohms)
            self._vol=0
        @property
        def vol(self):
            return self._vol
        
        @vol.setter
        def vol(self,vol):
            self._vol=vol
            self.current=self._vol/self.ohms
    r2=VolResistance(5)
    print(r2.current) # 0
    r2.vol=10
    print(r2.current) #2.0
    

    @property和setter合作实现对类成员属性的修改,@property相当于getter操作,获取成员信息,而同时会默认创建相应的xx.setter装饰器,可以动态地对相应的成员属性进行修改。

    用描述符来改写需要复用的@property方法

    内置的@property修饰器存在不便于复用的问题,受它修饰的方法无法为同一类中的其他属性所复用。

    Python提供了描述符(descriptor)来做,对访问操作进行一定转译:描述符类提供getset方法

    class Grade(object):
        def __get__(*args,**kwargs):
            pass
        def __set__(*args,**kwargs):
            pass
    
    class Exam(object):
        math_grade=Grade()
        writing_grade=Grade()
        science_grade=Grade()
    

    在具体使用中,我们可以将每个实例对应的值记录到Grade描述符类中,使用字典保存每个实例的状态

    class Grade(object):
        def __init__(self):
            self._values={}
            
        def __get__(self,instance,instance_type):
            print(self._values)
            if instance is None:
                return self
            return self._values.get(instance,0)
        def __set__(self,instance,value):
            self._values[instance]=value
    
    class Exam(object):
        math_grade=Grade()
        writing_grade=Grade()
        science_grade=Grade()
    
    exam=Exam()
    exam.science_grade=40
    print(exam.science_grade)
    #{<__main__.Exam object at 0x7f1b6281ab90>: 40}
    #40
    #
    

    这种写法存在一个严重的问题:泄露内存;在程序的生命期内,对于传给set的每个Exam实例,_values字典都会保存指向该实例的一份引用,导致该实例的引用计数无法将为0,使得内存无法被回收。 可以使用内置的weakref模块,使用WeakKeyDictionary的特殊字典,替代普通的字典。

    from weakref import WeakKeyDictionary
    class Grade(object):
        def __init__(self):
            self._values=WeakKeyDictionary()
            
        def __get__(self,instance,instance_type):
            print(self._values)
            if instance is None:
                return self
            return self._values.get(instance,0)
        def __set__(self,instance,value):
            self._values[instance]=value
    

    用_getattr_ /_getattribute_ /_setattr_实现需求

    Python提供了一些挂钩(Hook),辅助通用代码的编写,将多个系统粘合起来。

    例如:把数据库的行表示为Python对象,往往操作时优势我们不清楚行的结构,需要些通用的代码结构。

    class DB(object):
        def __init__(self):
            self.exists=5
        def __getattr__(self,name):
            value='Value for %s'%name
            setattr(self,name,value)
            return value
    data=DB()
    print(data.__dict__)  ## {'exists': 5}
    print(data.foo)
    print(data.__dict__) ## {'exists': 5, 'foo': 'Value for foo'}
    

    在访问data缺少的foo属性时,会调用定义的getattr方法,从而修改实例的dict字典;这种行为适合实现无结构数据的按需访问,初次执行getattr把相关的属性加载进来。

    然而有些情况下,我们需要动态地访问对象属性,使用_getattr_会导致属性加载后一直存在属性字典中;因此可以使用另一个方法:_getattribute_,每次访问对象属性时都会触发该方法,即使属性已经存在与属性字典中。

    此外,可以用内置的hashattr函数判断对象是否已经拥有了相关的属性,并用内置的getattr函数来获取属性值。

    在利用获取方法得到具体属性,可以通过_setattr_进行属性的赋值操作。

    此外值得注意的一点是,要避免通过_getattribute_和_setattr_方法中访问实例属性,容易造成无限递归。

    这个时候要采用super()._get__attribute_方法,从实例的属性字典里面直接获取_data属性值,以避免无限递归。

    相关文章

      网友评论

        本文标题:读书笔记(3): 编写高质量python代码的59个有效方法

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