44. 用纯属性或修饰器取代旧式的setter和getter

这里说的getter,setter不是特定的函数,而是例如get_attr(),set_attr()这种形式的函数来访问和是这类属性。对于python可以直接使用public类型的属性,可以直接用点(.)访问和修改。如果想执行一些特殊的行为,可以使用property装饰器。

  1. class Test:
  2. def __init__(self, x):
  3. self._x = x
  4. @property
  5. def x(self):
  6. return self._x
  7. @x.setter
  8. def x(self, x):
  9. self._x = x+1
  10. t = Test(1)
  11. t.x = 2
  12. print(t.x)

45. 考虑用@property实现新的属性访问逻辑,不要着急重构原有代码

46. 用描述符来改写需要复用的@property方法

note:类中属性调用顺序,对象属性>类属性
@property机制的缺陷在于无法复用,当遇到一些逻辑是需要不断复用的情况下时,比如对好几个参数做验证,

  • 当然可以在这些参数对应的函数上加上@property,并在函数里实现逻辑,但是每个函数都要写一次逻辑。
  • 当然也可以写一个函数,做统一的验证,把所有需要验证的属性验证一次。
  • 还可以使用描述符实现,更加清晰,简洁。

什么是描述符:
描述符就是实现了描述符协议的类,他的协议是实现getset, delete中的一个或多个函数。
get(self, instance, owner):self,对象,对象对应的类型
set(self, instance, value):self,对象,值
del(self, instance)
实现这种描述符后,会默认通过set设置值,get获取值。需要注意的是,这里的描述符是针对类属性的,也就是说如果在Test类的init方法里面实例化Dis对象,那么并不会有相同的效果。
由于x是类属性,那么不同的类对象之间时共享这个x的,则在不同对象对其赋值时会存在冲突问题。可以采用WeakKeyDictionary的字典以instance为键,存储相应的value。

  1. class Dis:
  2. def __init__(self):
  3. self._value = 0
  4. def __get__(self, instance, instance_type):
  5. print('gg')
  6. return self._value
  7. def __set__(self, instance, value):
  8. print('xxx')
  9. self._value = value
  10. class Test:
  11. x = Dis()
  12. t = Test()
  13. t.x = 1
  14. print(t.x)

47. 针对惰性属性使用getattrsetattrgetattribute

所谓惰性,就是按自己的方式,按需构建。
如果一个类里面定义了getattr,则当访问这个类的对象的属性,而这个属性又在这个类里面找不到时,就会调用getattr方法,这样就可以动态创建属性了

  1. class Test1:
  2. def __init__(self, x):
  3. self.x = x
  4. def __getattr__(self, name):
  5. setattr(self, name, 1)
  6. return 1

getattribute只要访问对象中的属性就会触发这个方法,无论属性存不存在
setattr可以拦截所有赋值操作
在实现getattributesetattr的过程中,如果要使用本对象的普通属性,那么应该通过super()(也就是object类)来使用,而不要直接使用,以避免无限递归

48. 用init_subclass验证子类写得是否正确

元类通常用于表示一种体系,一种结构,一种形式。一般继承自type,当实例化一个类时,会调用new,声明一个类的元类后,如例子中MyClass,则在实例化前,元类可以获取到该类的名字name,超类bases,以及字典中的属性class_dict,然后可以对相应的规则进行验证,通常元类适用于验证这个类构造是否符合一些规则。当然也可以进行自定义啦,比如加点属性进去,修改属性的值等

  1. class MyMeta(type):
  2. def __new__(meta, name, bases, class_dict):
  3. # 得到对应的类,后续操作就是正常类的操作了。
  4. kclass = super().__new__(meta, name, bases, class_dict)
  5. pass
  6. class MyClass(metaclass=MyMeta):
  7. ...

python3.6之后加入了init_subclass方法,这个方法也可以起到验证或者其他作用,通常定义在超类中,子类继承后,对其相应的属性等进行验证或对类进行相应的修改,例子如下,只能访问到类级别的属性,如果定义带了init里面,则无法访问到。

  1. class BaseValid:
  2. class_attr = None
  3. def __init_subclass__(cls, **kwargs):
  4. super().__init_subclass__()
  5. if cls.class_attr < 10:
  6. raise ValueError('should > 10')
  7. cls.name = 'name la'
  8. class MyValid(BaseValid):
  9. class_attr = 100
  10. def p(self):
  11. print(self.name)
  12. mv = MyValid()
  13. print(mv.name)
  14. mv.p()

一个类只能用一个元类,因此如果需要多种规则限制的时候,使用第二种方法可能更方便,更简洁,并且第二种方案使用super调用超类的init_subclass方法,可以是其中的规则有序触发。

49. 用init_subclass记录现有的子类

使用元类或者init_subclass在类构造的时候进行拦截,执行一些操作,比如类注册即先用字典将类名和类存起来,用于序列化和反序列化等。

50. 用set_name给类属性加注解

描述符类中的set_name(self, owner, name)方法可以将描述符对应的对象名传递给name,从而动态获取名字。

51. 优先考虑通过类修饰器来提供可组合的扩充功能,不要使用元类

类修饰器和方法上的修饰器类似

  1. def class_func(kclass):
  2. kclass.x = 1
  3. return kclass
  4. @class_func
  5. class Test:
  6. pass
  7. t = Test()
  8. print(t.x)