Effective python(四):元类及属性
1,使用纯属性代替get和set方法
- 使用@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__实现按需生成属性
- 当系统在对象实例中查询不到属性时,才会调用__getattr__,可以在其内部设置初始化属性
- 系统每次访问对象属性的时候,都会调用__getattribute__这个挂钩方法,可以在其中记录调用时间或其它的操作
- 只要对实例的属性赋值,不管怎样赋值,都会触发__setattr__方法,可以用来拦截和检查赋值
- 注意:当在__getattribute__或__setattr__中访问或者修改其属性,那么会导致再次调用自身形成无限递归,所以需要使用
super().__getattribute__('_data')
,不管当前类是否继承父类,因为super
确保其只执行一次
6, 使用元类来验证子类定义是否正确
- 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)
- 编写验证语句,验证的是子类而不是基类本身
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,使用元类来注册子类
- 序列化与反序列化
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'])
- 类的注册,可以通过字符串去调用,也适用于函数注册
registry={} def register_class(target_class): #注册类的名称为键,类的引用为值, #以后调用的时候可以直接通过字符串名称调用类 registry[target.__name__]=target_class
- 使用元类进行注册,定义子类语句体后,元类可以拦截这个子类,所以元类可以在子类语句体处理后,立刻注册这一子类
class Meta(type): def __new__(meta,name,bases,class_dict): #cls为生成的新的子类 cls=type.__new__(meta,name,bases,class_dict) register_class(cls) return cls
- 注意父类指定元类后子类不需要再指定
- 不使用
__init__
进行子类注册的原因是,需要每个类都定义注册语句,很繁琐
- 这种方案适用于序列化反序列化,ORM对象关系映射,插件系统和系统挂钩
8,借助元类来注解类的属性
- 例如当前有一个Field类,其中有
name
属性和_name
属性,如果通过正常的赋值解析情况,会有多余的重复操作,如first_name=Field('first_name')
,原因是python会以从右向左的顺序解读赋值语句,所以无法提前知道Field将会被赋值给哪个属性
- 通过元类来直接在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
- 总结,借助元类,我们可以在某个类完全定义好之前,率先修改该类的属性
- 描述符和元类能有效的组合,对某种行为进行修饰,并且可以在不适用weakref的前提下避免内存泄漏
可以直接留言交流问题或想法,每天都会看