Effective python(四):元类及属性

1,使用纯属性代替get和set方法

  1. 使用@property
    class exp:
        def __init__(self):
            self._x=0
        
        @property
        def x(self):
            return self._x
    
        @x.setter
        def x(self,x):
            self._x=x
            #此处可以进行特殊操作如关联y进行改变
            #也可以进行类型和数值验证
            #还可以防止对属性进行修改,下面是例子
            #在此处不应该执行缓慢的辅助函数,
            #也不应该执行开销较大的数据库查询操作,或者引入模块
            #此处应该执行迅速
            if hasattr(self,'_x'):
                raise AttributeError("can't set attribute")
            pass
    
    #使用方法
    ex=exp()
    ex.x=10
    

2,考虑使用@property重构属性,即扩充功能或修补功能,但若太频繁使用应该考虑彻底重构

3,可以定义一个描述符类,去改写需要复用的property方法

class Grade(object):
   def __get__(self,instance,value):
       pass
   def __set__(self,instance,instance_type):
       pass

class Exam(object):
   def __init__(self):
       self.math=Grade()
       self.English=Grade()

exam1=Exam()
exam2=Exam()
exam1.English=10
exam2.English=15
#python会转译为Exam.__dict__['English'].__set__(exam,40)
#注意set的第一个参数会传入实例,第二个是值

print(exam1.English)
print(exam2.English)
#python会转译为Exam.__dict__['English'].__get__(exam,Exam)
#注意第一个参数是实例,第二个参数是类型

4,当实例引用计数无法降为0的时候,垃圾回收期将不会回收它。可以通过python内置模块weakref中的WeakKeyDictionary特殊字典,如果字典中的引用是最后一份引用,则系统会自动将该实例从字典的键中清除

5,使用__getattr__,__getattribute__和__setattr__实现按需生成属性

  1. 当系统在对象实例中查询不到属性时,才会调用__getattr__,可以在其内部设置初始化属性
  1. 系统每次访问对象属性的时候,都会调用__getattribute__这个挂钩方法,可以在其中记录调用时间或其它的操作
  1. 只要对实例的属性赋值,不管怎样赋值,都会触发__setattr__方法,可以用来拦截和检查赋值
  1. 注意:当在__getattribute__或__setattr__中访问或者修改其属性,那么会导致再次调用自身形成无限递归,所以需要使用super().__getattribute__('_data'),不管当前类是否继承父类,因为super确保其只执行一次

6, 使用元类来验证子类定义是否正确

  1. python会默认把类的class语句体中的相关内容,发送给元类的__new__方法,定义元类要从type继承
    class Meta(type):
        def __new__(meta,name,bases,clas_dict):
            print((meta,name,bases,clas_dict))
            return type.__new__(meta,name,bases,clas_dict)
    
    class Myclass(object,metaclass=Meta):
        stuff=0
        def tmp(self):
            pass
    
    #打印显示
    (<class '__main__.Meta'>,
    'Myclass',
    (<class object>,),
    {'__module__':'__main__',
    '__qualname':'Myclass',
    'tmp':<function Myclass.tmp at 0x102c7dd08>},
    'stuff':0)
    
  1. 编写验证语句,验证的是子类而不是基类本身
    class ValidatePolygo(type):
        def __new__(meta,name,bases,class_dict):
            #不去验证基类,即基类需要继承object
            if bases!=(object,):
                #验证边数属性若小于3,则拒绝这个class
                if class_dict['sides']<3:
                    raise ValueError('应该大于三边')
                return type.__new__(meta,name,bases,class_dict)
    

7,使用元类来注册子类

  1. 序列化与反序列化
    import json
    class Serializable(object):
        def __init__(self,*args):
            self.args=args
        
        def seialize(self):
            return json.dumps({'args':self.args})
    
    class Deserializable(Serializable):
        @classmethod
        def deserialize(cls,json_data):
            params=json.loads(json_data)
            #生成一个对象
            return cls(*params['args'])
    
  1. 类的注册,可以通过字符串去调用,也适用于函数注册
    registry={}
    def register_class(target_class):
        #注册类的名称为键,类的引用为值,
        #以后调用的时候可以直接通过字符串名称调用类
        registry[target.__name__]=target_class
    
  1. 使用元类进行注册,定义子类语句体后,元类可以拦截这个子类,所以元类可以在子类语句体处理后,立刻注册这一子类
    class Meta(type):
        def __new__(meta,name,bases,class_dict):
            #cls为生成的新的子类
            cls=type.__new__(meta,name,bases,class_dict)
            register_class(cls)
            return cls
    
  1. 注意父类指定元类后子类不需要再指定
  1. 不使用__init__进行子类注册的原因是,需要每个类都定义注册语句,很繁琐
  1. 这种方案适用于序列化反序列化,ORM对象关系映射,插件系统和系统挂钩

8,借助元类来注解类的属性

  1. 例如当前有一个Field类,其中有name属性和_name属性,如果通过正常的赋值解析情况,会有多余的重复操作,如first_name=Field('first_name'),原因是python会以从右向左的顺序解读赋值语句,所以无法提前知道Field将会被赋值给哪个属性
  1. 通过元类来直接在class上放置挂钩
    class Meta(type):
        def __new__(meta,name,bases,class_dict):
            for key,value in class_dict.items():
                #判断是否是Field实例
                if isinstance(value,Field):
                    #直接对Field字段内部属性进行改写
                    #如此便不用再重复传参数
                    value.name=key
                    value._name='_'+key
            cls=type.__new__(meta,name,bases,class_dict)
            return cls
    
  1. 总结,借助元类,我们可以在某个类完全定义好之前,率先修改该类的属性
  1. 描述符和元类能有效的组合,对某种行为进行修饰,并且可以在不适用weakref的前提下避免内存泄漏
posted @ 2020-03-27 15:12  石天放  阅读(220)  评论(0编辑  收藏  举报