44. 用纯属性或修饰器取代旧式的setter和getter
这里说的getter,setter不是特定的函数,而是例如get_attr(),set_attr()这种形式的函数来访问和是这类属性。对于python可以直接使用public类型的属性,可以直接用点(.)访问和修改。如果想执行一些特殊的行为,可以使用property装饰器。
class Test:def __init__(self, x):self._x = x@propertydef x(self):return self._x@x.setterdef x(self, x):self._x = x+1t = Test(1)t.x = 2print(t.x)
45. 考虑用@property实现新的属性访问逻辑,不要着急重构原有代码
46. 用描述符来改写需要复用的@property方法
note:类中属性调用顺序,对象属性>类属性
@property机制的缺陷在于无法复用,当遇到一些逻辑是需要不断复用的情况下时,比如对好几个参数做验证,
- 当然可以在这些参数对应的函数上加上@property,并在函数里实现逻辑,但是每个函数都要写一次逻辑。
- 当然也可以写一个函数,做统一的验证,把所有需要验证的属性验证一次。
- 还可以使用描述符实现,更加清晰,简洁。
什么是描述符:
描述符就是实现了描述符协议的类,他的协议是实现get,set, delete中的一个或多个函数。
① get(self, instance, owner):self,对象,对象对应的类型
② set(self, instance, value):self,对象,值
③ del(self, instance)
实现这种描述符后,会默认通过set设置值,get获取值。需要注意的是,这里的描述符是针对类属性的,也就是说如果在Test类的init方法里面实例化Dis对象,那么并不会有相同的效果。
由于x是类属性,那么不同的类对象之间时共享这个x的,则在不同对象对其赋值时会存在冲突问题。可以采用WeakKeyDictionary的字典以instance为键,存储相应的value。
class Dis:def __init__(self):self._value = 0def __get__(self, instance, instance_type):print('gg')return self._valuedef __set__(self, instance, value):print('xxx')self._value = valueclass Test:x = Dis()t = Test()t.x = 1print(t.x)
47. 针对惰性属性使用getattr,setattr,getattribute
所谓惰性,就是按自己的方式,按需构建。
如果一个类里面定义了getattr,则当访问这个类的对象的属性,而这个属性又在这个类里面找不到时,就会调用getattr方法,这样就可以动态创建属性了
class Test1:def __init__(self, x):self.x = xdef __getattr__(self, name):setattr(self, name, 1)return 1
getattribute只要访问对象中的属性就会触发这个方法,无论属性存不存在
setattr可以拦截所有赋值操作
在实现getattribute与setattr的过程中,如果要使用本对象的普通属性,那么应该通过super()(也就是object类)来使用,而不要直接使用,以避免无限递归。
48. 用init_subclass验证子类写得是否正确
元类通常用于表示一种体系,一种结构,一种形式。一般继承自type,当实例化一个类时,会调用new,声明一个类的元类后,如例子中MyClass,则在实例化前,元类可以获取到该类的名字name,超类bases,以及字典中的属性class_dict,然后可以对相应的规则进行验证,通常元类适用于验证这个类构造是否符合一些规则。当然也可以进行自定义啦,比如加点属性进去,修改属性的值等
class MyMeta(type):def __new__(meta, name, bases, class_dict):# 得到对应的类,后续操作就是正常类的操作了。kclass = super().__new__(meta, name, bases, class_dict)passclass MyClass(metaclass=MyMeta):...
python3.6之后加入了init_subclass方法,这个方法也可以起到验证或者其他作用,通常定义在超类中,子类继承后,对其相应的属性等进行验证或对类进行相应的修改,例子如下,只能访问到类级别的属性,如果定义带了init里面,则无法访问到。
class BaseValid:class_attr = Nonedef __init_subclass__(cls, **kwargs):super().__init_subclass__()if cls.class_attr < 10:raise ValueError('should > 10')cls.name = 'name la'class MyValid(BaseValid):class_attr = 100def p(self):print(self.name)mv = MyValid()print(mv.name)mv.p()
一个类只能用一个元类,因此如果需要多种规则限制的时候,使用第二种方法可能更方便,更简洁,并且第二种方案使用super调用超类的init_subclass方法,可以是其中的规则有序触发。
49. 用init_subclass记录现有的子类
使用元类或者init_subclass在类构造的时候进行拦截,执行一些操作,比如类注册即先用字典将类名和类存起来,用于序列化和反序列化等。
50. 用set_name给类属性加注解
描述符类中的set_name(self, owner, name)方法可以将描述符对应的对象名传递给name,从而动态获取名字。
51. 优先考虑通过类修饰器来提供可组合的扩充功能,不要使用元类
类修饰器和方法上的修饰器类似
def class_func(kclass):kclass.x = 1return kclass@class_funcclass Test:passt = Test()print(t.x)
