• 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的前提下避免内存泄漏
  • 相关阅读:
    Ajax实践学习笔记(三) Ajax应用模型
    代码之美
    Git 和Github初次使用 (转) Anny
    VisitsPageViewUnique Visitors: Metrics From GA Anny
    Building and Installing Node.js Anny
    Callback in NodeJS Anny
    Intall Apache & php on Ubuntu Anny
    [转载]Linux系统下超强远程同步备份工具Rsync使用详解 Anny
    Cannot Boot WEBrick: "WARN TCPServer Error: Address already in use " Anny
    chmod(转) Anny
  • 原文地址:https://www.cnblogs.com/shitianfang/p/12581624.html
Copyright © 2020-2023  润新知