44. 用纯属性或修饰器取代旧式的setter和getter
这里说的getter,setter不是特定的函数,而是例如get_attr(),set_attr()这种形式的函数来访问和是这类属性。对于python可以直接使用public类型的属性,可以直接用点(.)访问和修改。如果想执行一些特殊的行为,可以使用property装饰器。
class Test:
def __init__(self, x):
self._x = x
@property
def x(self):
return self._x
@x.setter
def x(self, x):
self._x = x+1
t = Test(1)
t.x = 2
print(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 = 0
def __get__(self, instance, instance_type):
print('gg')
return self._value
def __set__(self, instance, value):
print('xxx')
self._value = value
class Test:
x = Dis()
t = Test()
t.x = 1
print(t.x)
47. 针对惰性属性使用getattr,setattr,getattribute
所谓惰性,就是按自己的方式,按需构建。
如果一个类里面定义了getattr,则当访问这个类的对象的属性,而这个属性又在这个类里面找不到时,就会调用getattr方法,这样就可以动态创建属性了
class Test1:
def __init__(self, x):
self.x = x
def __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)
pass
class MyClass(metaclass=MyMeta):
...
python3.6之后加入了init_subclass方法,这个方法也可以起到验证或者其他作用,通常定义在超类中,子类继承后,对其相应的属性等进行验证或对类进行相应的修改,例子如下,只能访问到类级别的属性,如果定义带了init里面,则无法访问到。
class BaseValid:
class_attr = None
def __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 = 100
def 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 = 1
return kclass
@class_func
class Test:
pass
t = Test()
print(t.x)