关于描述器,python官方文档广义的放在了自定义属性访问小节中介绍,为了更清楚实例属性的访问顺序,所以先对反射相关的魔术方法复习一下。
反射名字有点抽象,就是程序被加载到内存中运行时,动态的给实例或者类增加或者修改属性。区别于类或者实例定义时。
内建函数 | 含义 |
---|---|
getattr(obj, name[,default]) | 通过name返回obj的属性值,当属性不存在,返回default。如果没有default,则跑出AttributeError。name类型必须是str |
setattr(obj, name, value) | 为obj添加属性。属性存在则覆盖,name类型必须是str |
hasattr(obj, name) | 判断对象是否存在name属性, name类型必须是str |
delattr(obj, name) | 删除name属性, 如果obj没有name属性,抛出AttributeError |
class A:
m = 100
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "{} {}".format(self.x, self.y)
a = A(1,2)
getattr(a, "x") # 1
getattr(a, "n", "default") # default
setattr(a, "z", 100)
hasattr(a, "z") # True
delattr(a, "n") # AttributeError
# 动态为类绑定属性, 类属性的第一个参数会默认绑定到实例本身
if not hasattr(A, "get"):
setattr(A, "get", lambda self: self.x)
A.__dict__
# 动态为实例绑定属性, 实例属性的第一个参数,不会绑定实例本身,仅仅是实例的一个属性,而这个属性是一个函数罢了
if not hasattr(a, "set"):
setattr(a, "add", lambda self, other: self.x + other.x)
a.__dict__
这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异?
这种动态增加属性的方式是运行时改变类和实例的访问,但是装饰器或mixin都是自定义时实现的,因此反射具有更大的灵活性。
可以实现下列方法来实现自定义对类的实例属性访问
name为属性名字
当默认属性访问因AttributeError而失败是被调用,此方法应该返回找到的属性值或者引发一个AttributeError异常
class A:
def __init__(self):
self.x = 100
def __getattr__(self, item, default = None):
if default is None:
raise AttributeError
return self.item
a = A()
a.x # 100
getattr(a, "y", "default") # default
a.y # AttributeError
属性会按照正常机制(所谓正常机制就是属性的继承关系)寻找,如果找到就不会调用__getattr__方法,否则就会调用__getattr__方法
属性的查找顺序:
instance.dict => instance.class.dict => 继承的祖先类,一直到object的__dict__ => 调用__getattr__方法
name为属性的名字
此方法会无条件的调用以实现对类实力属性的访问。
class A:
def __init__(self):
self.x = 100
def __getattr__(self, item):
return 123
def __getattribute__(self, name):
# return name
raise AttributeError
a = A()
a.x # 123
所有的实例属性访问,第一个都会调用__getattribute__方法,他阻止了属性的查找
该方法应该返回一个属性值或者抛出一个AttributeError异常。
class A:
def __init__(self):
self.x = 100
def __getattr__(self, name):
return 123
def __getattribute__(self, name):
return object.__getattribute__(self, name)
为了避免此方法中的无限递归,其实现应该总是调用具有相同名称的基类方法来访问他所需要的任何属性,例如object.getattribute(self, name)
name为属性名称,value为要赋给属性的值
此方法在一个实例属性被尝试赋值定义时被调用,包括实例初始化时的属性定义。这个调用会取代正常机制(即将值保存到实例字典)。
class A:
d = {
}
def __init__(self):
self.x = 100
def __getattr__(self, name):
return self.d[name]
def __setattr__(self, name, value):
self.d[name] = value # 实例属性定义不会再装入实例__dict__中,而是装在类属性d中
a = A()
setattr(a, "y", 100)
a.d # {'x': 100, 'y': 100}
可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__
name为属性名
可以阻止通过实现来删除实例属性的操作,与类无关
class A:
def __init__(self):
self.x = 100
def __delattr__(self, name):
print("can not delete!") # 定义一个与删除无关的操作即可实现阻止删除
a = A()
del a.x # can not delete!
描述器是一种创建托管属性的方法。描述器具有诸多优点:保护属性不受修改、属性类型检查和自动更新某个依赖属性的值等。
通俗的讲,如果一个类实现了__get__、__set__、__delete__三个方法的任意一个方法就是描述器,实现了这三个中的某些方法,就只吃了描述器协议。
当一个描述器类的实例出现在一个其他类中的时候才会起作用(或者说描述器类的实例必须在所有者类或者某个上级类的字典中),这个其他类就是owner所有者类。
instance为属主实例
owner为属主类
value为类属性值
class A: # 非数据描述器类
def __init__(self):
self.x = 100
def __get__(self, instance, owner):
return self.x
class B: # 数据描述器类
def __init__(self):
self.x = 1000
def __get__(self, instance, owner):
return self.x
def __set__(self, instance, value):
self.x += 1000
class C:
a = A() # 非数据描述器
b = B() # 数据描述器
def __init__(self, z):
self.a = 1
self.b = 2 # C类的实例初始化,因为给属性赋值定义,所以会调用数据描述器
self.z = z
c = C(3)
C.a # 100 # 实例调用类的属性描述器,调用a非数据描述器的\_\_get__方法
c.a # 1
c.b # 2000
c.b = 3 # 修改描述器同名属性值,会调用数据描述器的\_\_set__和\_\_get__方法
c.b # 3000
c.z # 3 z只是个普通属性,所以会按照正常机制寻找属性值z,结果为3
注意:只有调用owner的类属性才会触发描述器,所以一个实例属性和类属性描述器重名,就触发描述器
使用实例对象访问属性时,都会先调用__getattribute__内建函数,优先级如下:
1.数据描述器
2.实例属性
3.非数据描述器
4.__getattr__
python的方法,包括staticmethod()和classmethod()都实现为非数据描述器,因此实例可以重新定义和覆盖方法
Property()函数实现为一个数据描述器,因此实例不能实现覆盖类属性的行为
一旦了解描述器的原理,结合之前描述器的知识,理解它们非常轻松
1.staticmethod
class StaticMethod: # 非数据描述器
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
return self.fn
class A:
@StaticMethod # f = StaticMethod(f) # 相当于f为StaticMethod的实例,非数据描述器
def f(*args, **kwargs):
print("hello")
a = A()
a.f() # 调用了非数据描述器
from functools import partial
class ClassMethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
newfun = partial(self.fn, owner)
return newfun # 将参数cls固定住
class B:
@ClassMethod # f = ClassMethod(f)
def f(cls, *args, **kwargs): # 经过装饰的f是其实类方法,而且是个非描述器
print(cls, args, kwargs)
# return (args, kwargs)
b = B()
b.f(1,2) # 实例调用类描述器,触发描述器
class Common: # 将普通方法改成调用描述器实现
def __init__(self, fn):
self.fn = fn
def __get__(self, insance, owner):
print("Common")
return partial(self.fn, self = insance)
class A:
def __init__(self):
self.age = 100
@Common # fn = Common(fn) fn是一个非数据描述器
def fn(self):
return self.age
class Property:
def __init__(self, fn, fset=None, fdel=None):
print("init====")
self.fn = fn
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
print("get====")
return self.fn(instance) # 只负责获取实例的属性,没有其他功能
def __set__(self, instance, value):
print("set====")
print(self.fset)
self.fset(instance, value) # 调用原A 的设置属性函数,,将属性更改
print("123123123")
def __delete__(self, instance):
print("delete====")
self.fdel(instance)
def setter(self, fset):
print("setter===")
self.fset = fset
return self # 如果不返回self, x.setter的返回值为None,
# 相当于A的类属性为None,就不是数据描述器了,所以返回值必须为self
def deleter(self, fdel):
self.fdel = fdel
return self #
class A:
def __init__(self, x):
self.__x = x
# self.x = 1000000
@Property
def x(self): # Property的实例 ,也是A的类属性描述器
return self.__x
@x.setter # setter装饰完的了之后,必须还是个装饰器
def x(self, value):
self.__x = value
@x.deleter
def x(self):
del self.__x
y = Property(lambda self: self.__x)
三种思路:
下面分别实现这三种思路:
# 1.写函数,在__init__中先检查,如果不合格,直接抛异常
class A:
def __init__(self, name:str, age:int):
params = ((name,str), (age,int))
if not self.check(params): # 为什么可以被调用
raise TypeError()
self.name = name
self.age = age
def check(self, params):
for param, type in params:
if not isinstance(param, type):
return Fasle
return True
a = A("john",18)
# 2.函数装饰器方式:
import inspect
def decorator(cls):
def wrapper(*args, **kwargs):
sig = inspect.signature(cls)
params = sig.parameters # 原始参数注解
bind_value = sig.bind(*args, **kwargs) # 类实例化形参和实参的绑定关系
for name, value in bind_value.arguments.items():
if not isinstance(value, params[name].annotation):
raise TypeError
print(bind_value.arguments)
return cls(*args, **kwargs) #返回值为实例对象, 被装饰完的类,再也不能使用之前的类的属性了
return wrapper
@decorator
class A:
def __init__(self, name:str, age:int):
self.name = name
self.age = age
def get(self):
return 123123123
a = A("1",2)
a.name
a.get()
最后使用描述器的方式实现参数检查
# 描述器方式:
# 肯定是个数据描述器
# 思路整理:
import inspect
class Check:
def __init__(self, name, typ):
self.typ = typ
self.name = name
def __get__(self, instance, owner):
if instance:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if isinstance(value, self.typ):
instance.__dict__[self.name] = value
class B:
def __call__(self, cls):
sig = inspect.signature(cls)
params = sig.parameters
for name, v in params.items():
cls.name = Check(name, v.annotation)
# __setattr__
# cls.__setattr__()
# setattr(cls, name, Check(name, v.annotation)) # 给cls增加类属性
return cls
@B()
class A:
# name = Check("name", str) # 描述器
# age = Check("age", int) # 描述器 ,硬编码,不好,搞一个类装饰器
def __init__(self, name:str, age:int):
self.name = name # 定义这里的属性会调用上面的描述器
self.age = age # 定义这里的属性会调用上面的描述器
def get(self):
return 123123123
a = A("john", 20)
a.__dict__
a.name, a.age