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的前提下避免内存泄漏