描述器用到的方法
用到3个魔术方法: __get__()、__set__()、__delete__() 方法使用格式: obj.__get__(self, instance, owner) obj.__set__(self, instance, value) obj.__delete__(self, instance) self: 指当前类的实例本身 instance: 指owner的实例 owner: 指当前实例作为属性的所属类
代码一
以下代码执行过程:
定义B类时,执行A()赋值操作,进行A类的初始化,再打印B类调用类属性x的a1属性
紧接着执行B类的初始化,通过b实例调用类属性的x的a1属性
class A: def __init__(self): self.a1 = 'a1' print('A.init') class B: x = A() def __init__(self): print('B.init') print('-' * 20) print(B.x.a1) print('*' * 20) b = B() print(b.x.a1)
描述器定义
Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法, 那么这个类就是描述器. 如果仅实现了__get__,就是非数据描述符 non-data descriptor 同时实现了除__get__以外的__set__或__delete__方法,就是数据描述符 data descriptor 如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主 描述器方法何时被触发: 当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发 当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发
代码二
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print('A.__get__ {} {} {}'.format(self,instance,owner)) class B: x = A() def __init__(self): print('B.init') print('-' * 20) print(B.x) # print(B.x.a1) # AttributeError B.x为None,None没有a1属性 print('*' * 20) b = B() # print(b.x.a1) # AttributeError B.x为None,None没有a1属性 调用B类的类属性,被A类__get__方法拦截,并返回值None
代码三
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print('A.__get__ {} {} {}'.format(self,instance,owner)) return self class B: x = A() def __init__(self): print('B.init') print('-' * 20) print(B.x) print(B.x.a1) print('*' * 20) b = B() print(b.x) print(b.x.a1) 解决上例中的返回值为None,将A类的实例返回,可成功调用A实例的a1属性
代码四
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print('A.__get__ {} {} {}'.format(self,instance,owner)) return self class B: x = A() print(x) def __init__(self): print('B.init') # self.x = 100 # 实例调用x属性时,直接查实例自己的__dict__ self.x = A() # 实例调用x属性时,不进入A类的__get__方法 print(self.x) print('-' * 20) print(B.x) # __get__ print(B.x.a1) # __get__ print('*' * 20) b = B() print(b.x) print(b.x.a1) 总结: 不论是实例还是类,只要是访问了是描述器的类属性, 都会被描述器的__get__方法拦截
属性的访问顺序(本质)
代码五
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print('A.__get__ {} {} {}'.format(self,instance,owner)) return self def __set__(self,instance,value): print('A.__set__ {} {} {}'.format(self,instance,value)) class B: x = A() print(x) def __init__(self): print('B.init') self.x = 100 # self.x = A() # 同上面100结果类似 print(self.x) # print('-' * 20) # print(B.x) # print(B.x.a1) # print('*' * 20) b = B() # print(b.x) # print(b.x.a1) print(b.__dict__) print(B.__dict__) 屏蔽A类的__set__方法,实例的__dict__为{'x': 100} 不屏蔽A类的__set__方法,实例的__dict__为{} __set__方法本质将实例的__dict__的属性名清空,从而达到数据描述器优先于查实例字典的假象
Python中的描述器
描述器在Python中应用非常广泛Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器 .因此,实例可以通过'.'号进行生成属性.property()函数实现为一个数据描述器. 则实例不能使用'.'号进行赋值属性.
示例
class A: @classmethod def foo(cls): pass @staticmethod def bar(): pass @property def z(self): return 5 def __init__(self): # 非数据描述器 self.foo = 100 self.bar = 200 # self.z = 300 # z属性不能使用实例覆盖 a = A() print(a.__dict__) print(A.__dict__)
练习
实现StaticMethod装饰器,完成staticmethod装饰器的功能
class StaticMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,owner): print(self,instance,owner) return self._fn class A: @StaticMethod # stmd = StaticMehtod(stmd) def stmd(): print('stmd') print(A.__dict__) A.stmd() # 类调用stmd属性
实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial class ClassMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,owner): print(self,instance,owner) return partial(self._fn,owner) # 使用partial函数将类给作为默认参数 class A: @ClassMethod # clsmd = ClassMethod(clsmd) def clsmd(cls): print('cls',cls.__name__) print(A.__dict__) A.clsmd()
类初始化的参数检查
import inspect class Typed: def __init__(self,tp): self._tp = tp def __get__(self,instance,owner): pass def __set__(self,instance,value): if not isinstance(value,self._tp): raise ValueError(value) setattr(instance.__class__,self._name,value) def pcheck(cls): def wrapper(*args): sig = inspect.signature(cls) params = sig.parameters for i,(name,param) in enumerate(params.items()): if param.empty != param.annotation: # if not isinstance(args[i],param.annotation): # raise ValueError(args[i]) setattr(cls,name,Typed(param.annotation)) return cls(*args) return wrapper @pcheck class A: # a = Typed(str) # b = Typed(int) def __init__(self,a:str,b:int): self.a = a self.b = b A('1',2)
描述器结合装饰实现
import inspect class Typed: def __init__(self,name,tp): self._name = name self._tp = tp def __get__(self,instance,owner): print('get',self,instance,owner) return instance.__dict__[self._name] def __set__(self,instance,value): print('set',self,instance,value) if not isinstance(value,self._tp): raise ValueError(value) instance.__dict__[self._name] = value class A: a = Typed('a',str) b = Typed('b',int) def __init__(self,a:str,b:int): self.a = a self.b = b a = A('1',2) print(a.__dict__) # print(type(a.a),type(a.b)) print(a.a)
描述器实现
import inspect def pcheck(cls): def wrapper(*args): sig = inspect.signature(cls) params = sig.parameters for i,(_,param) in enumerate(params.items()): if param.empty != param.annotation: if not isinstance(args[i],param.annotation): raise ValueError(args[i]) return cls(*args) return wrapper @pcheck # A = pcheck(A) class A: def __init__(self,a:str,b:int): self.a = a self.b = b A('1','2')
装饰器版本
class A:
def __init__(self,a:str,b:int):
if not (isinstance(a,str) and isinstance(b,int)):
raise ValueError(a,b)
else:
self.a = a
self.b = b
A('1',2)
直接参数检查
思路:
实现参数检查的本质是判断传入的参数是否符合形参定义的类型,也就是用isinstance进行判断.
因此参数检查的不同实现的区别在于在哪些地方拦截传入的参数,来进行检查.
上述实现的拦截地方:
在类初始化时,在对实例属性赋值之前拦截
使用装饰器,和inspect模块,在实例化之前进行参数检查
使用描述器,在初始化时对实例属性设置时,触发描述器的__set__方法,在__set__方法中进行参数检查,再对其实例的类添加类属性
(如果添加在实例上,则会递归调用回到__set__方法)
使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查