使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器
描述器的定义
一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器。
如果只实现了__get__,就叫非数据描述器non-data descriptor
如果同时实现了__get__,__set__,那就叫数据描述器data descriptor。
如果一个类的类属性的值设置为了描述器,那这个类就称为owner(属主)。
回顾下就会发现我们曾用过@property(property是一个类)来将一个方法转为属性,其实property就是一个描述器。
class A: def __init__(self): self.a1 = 'a1' class B: x = A() def __init__(self): pass print(B.x.a1) ------运行结果----- a1
看下执行流程
class A: def __init__(self): print('A.init') self.a1 = 'a1' class B: x = A() def __init__(self): print('B.init') pass print(B.x.a1) -----运行结果——— A.init a1 # B根本就没有实例化
一步一步加,慢慢观察实例的初始化顺序
class A: def __init__(self): print('A.init') self.a1 = 'a1' class B: x = A() def __init__(self): print('B.init') self.x = 100 print(B.x.a1) b = B() print(B.x.a1) print(b.x.a1) # 抛出下面的AttributeError异常,因为按属性的mro搜索顺序来说,b.x也就是实例的x=100,100没有a1属性 -----运行结果----- A.init a1 B.init a1 Traceback (most recent call last): File "C:/python/1117/description_test1.py", line 16, in <module> print(b.x.a1) AttributeError: 'int' object has no attribute 'a1'
实例中可以覆盖非数据描述器
class A: def __init__(self): print('A.init') self.a1 = 'a1' def __get__(self, instance, owner): print(self,instance,owner) class B: x = A() def __init__(self): print('B.init') # self.x = 100 # print(B.x.a1) print(B.x) # 返回的是NOne b = B() print(B.x) print(b.x) #描述器跟类属性有关系 ———运行结果—————————— A.init <__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'> None B.init <__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'> None <__main__.A object at 0x00000271CBDA1668> <__main__.B object at 0x00000271CBDA18D0> <class '__main__.B'> None
class A: def __init__(self): print('A.init') self.a1 = 'a1' def __get__(self, instance, owner): print(self,instance,owner) class B: x = A() def __init__(self): print('B.init') # self.x = 100 self.x= A() # print(B.x.a1) # print(B.x) # 返回的是NOne b = B() print(B.x) print(b.x) #描述器跟类属性有关系 print(b.x.a1) ————运行结果————— A.init B.init A.init <__main__.A object at 0x000001A6D1031668> None <class '__main__.B'> None <__main__.A object at 0x000001A6D1031860> a1
第一种情况:
A类的类属性的值等于B类的实例,且B类实现了__get__,__set__,__delete__三种方法中任意一个时,也就是说B类是一个描述器,就会触发定义的方法(如__get__)
第二种情况:
如果A类的实例的值等于B类的实例,即使B类是一个描述器,也不会触发其中的三种方法
__set__
class A: def __init__(self): print('A.init') self.a1 = 'a1' def __get__(self, instance, owner): print(self,instance,owner) return self def __set__(self, instance, value): print(self,instance,value) class B: x = A() def __init__(self): print('B.init') self.x = 100 #实例的属性 # self.x= A() b = B() print(B.x) print(b.x.a1) print(b.__dict__) print(B.__dict__) ——————运行结果—————— A.init B.init <__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> 100 <__main__.A object at 0x00000193299127F0> None <class '__main__.B'> <__main__.A object at 0x00000193299127F0> <__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> <class '__main__.B'> a1 {} #添加了__set__方法之后,b实例的字典没有任何属性 {'__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>, '__init__': <function B.__init__ at 0x0000019329902E18>, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x00000193299127F0>}
添加了一个__set__方法后结果就发生了变化。
结论:
如果一个类的类属性是一个数据描述器的话,你对它的同名属性的操作,相当于操作类属性。
官方的说明是说:
如果一个类的类属性是一个数据描述器的话,在实例中定义了同名的属性时,类属性的查找顺序优先级优于实例属性。
然而,我们在上例中打印的__dict__字典中可以看到,根本不是什么优先级关系,实例的字典中根本就不存在同名的属性,同名的属性只存在于类的__dict__字典中。
所以实例的属性名与类属性名同名时,其实就是在操作类属性。
在实例中定义一个不同名的属性举例测试下:
class A: def __init__(self): print('A.init') self.a1 = 'a1' def __get__(self, instance, owner): print(self,instance,owner) return self def __set__(self, instance, value): print(self,instance,value) class B: x = A() def __init__(self): print('B.init') self.y = 100 #不与类属性同名的属性 # self.x= A() b = B() print(B.x) print(b.x.a1) print(b.y) print(b.__dict__) print(B.__dict__) ————运行结果————— A.init B.init <__main__.A object at 0x0000012BEE4C17F0> None <class '__main__.B'> <__main__.A object at 0x0000012BEE4C17F0> <__main__.A object at 0x0000012BEE4C17F0> <__main__.B object at 0x0000012BEE4C18D0> <class '__main__.B'> a1 100 {'y': 100} #不与类属性同名时,不会改变属性搜索顺序 {'__init__': <function B.__init__ at 0x0000012BEE4B2E18>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, 'x': <__main__.A object at 0x0000012BEE4C17F0>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'B' objects>}
练习:
1.实现StaticMethod装饰器,完成staticmethod装饰器的功能
2.实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial class StaticMethod: #防止冲突改名 def __init__(self,fn): self.fn = fn def __get__(self, instance, owner): print(self,instance,owner) return self.fn class ClassMethod: def __init__(self,fn): print(fn) self.fn = fn def __get__(self, instance, owner): print(self,instance,owner) # return self.fn(owner) return partial(self.fn,owner) #partail偏函数固定某个参数,就返回了一个newfunction新函数 class A: @StaticMethod def foo(): #foo = StaticMethod(foo) print('foo') @ClassMethod def bar(cls): #bar=ClassMethod(bar) print(cls.__name__) f = A.foo print(f) f() print('~~~~~~~~~~~~~') b = A.bar print(b) b() #需要调用必须是函数 -----------运行结果--------- <function A.bar at 0x000001956B9D4840> <__main__.StaticMethod object at 0x000001956B8728D0> None <class '__main__.A'> <function A.foo at 0x000001956B9D47B8> foo ~~~~~~~~~~~~~ <__main__.ClassMethod object at 0x000001956B872940> None <class '__main__.A'> functools.partial(<function A.bar at 0x000001956B9D4840>, <class '__main__.A'>) A
与对象绑定的叫方法
与function绑定的就叫函数
普通函数就用@staticmethod
def stcmth()
不想传参就用静态方法
类的方法就用@classmethod
def clsmtd(cls)
类或实例的类(解释器隐含a.__class__)传入cls
传入的是类或实例的类
类方法即使不实例化,通过类也可以直接调用。
实例化之后通过实例也可以调用。
传参时对参数类型进行检查:
第一种方法:
传统方法,在__init__时做条件判断
class Person: def __init__(self,name:str,age:int): if not self.checkdata(((name,str),(age,int))): raise TypeError() self.name = name self.age = age def checkdata(self,params): print(params) for dt,tp in params: # print(dt,tp) if not isinstance(dt,tp): #第一种 return False else: return True # if type(dt) == tp: #第二种 # continue # else: # return False # else: # return True p1 = Person('zhangsan',18) #当传入类型不符时抛出TypeError异常,比如传入'18'字符串格式的age就会触发异常 print(p1.__dict__) print(p1.name) print(p1.age) ————运行结果———————— (('zhangsan', <class 'str'>), (18, <class 'int'>)) {'name': 'zhangsan', 'age': 18} zhangsan 18
第二种:
使用数据描述器
class Typed: def __init__(self): pass def __get__(self, instance, owner): pass def __set__(self, instance, value): print('Typed.__set__:',self,instance,value) class Person: name = Typed() age = Typed() def __init__(self,name:str,age:int): self.name = name self.age = age p1 = Person('jerry',18) ————运行结果—————— Typed.__set__: <__main__.Typed object at 0x000002852A70FA58> <__main__.Person object at 0x000002852A7217B8> jerry Typed.__set__: <__main__.Typed object at 0x000002852A721710> <__main__.Person object at 0x000002852A7217B8> 18
将类型作为参数传入Typed类,在__set__方法中做检查:
class Typed: def __init__(self,type): self.type = type pass def __get__(self, instance, owner): pass def __set__(self, instance, value): print('Typed.__set__:',self,instance,value) if not isinstance(value,self.type): raise ValueError(value) class Person: name = Typed(str) age = Typed(int) def __init__(self,name:str,age:int): self.name = name self.age = age p1 = Person('jerry',18) #运行正常,说明传入的参数类型正确 ------运行结果------- Typed.__set__: <__main__.Typed object at 0x00000218FDBEFA58> <__main__.Person object at 0x00000218FDC01668> jerry Typed.__set__: <__main__.Typed object at 0x00000218FDC01390> <__main__.Person object at 0x00000218FDC01668> 18
当传入的参数不正确时就抛出ValueError异常:
p1 = Person('jerry','18') ———运行结果————— Typed.__set__: <__main__.Typed object at 0x00000261222116A0> <__main__.Person object at 0x0000026122211710> jerry Traceback (most recent call last): Typed.__set__: <__main__.Typed object at 0x0000026122211668> <__main__.Person object at 0x0000026122211710> 18 File "C:/python/1117/params_check_test3.py", line 23, in <module> p1 = Person('jerry','18') File "C:/python/1117/params_check_test3.py", line 21, in __init__ self.age = age File "C:/python/1117/params_check_test3.py", line 13, in __set__ raise ValueError(value) ValueError: 18
在设置值时,Typed类__set__方法拦截到之后判断传入的value和type类型是否一致。
第三种:
装饰器加数据描述器
class Typed: def __init__(self,name,type): self.name = name self.type = type def __get__(self, instance, owner): if instance is not None: return instance.__dict__[self.name] return self def __set__(self, instance, value): print(self,instance,value) if not isinstance(value,self.type): raise ValueError(value) instance.__dict__[self.name] = value # return self import inspect def typeassert(cls): params = inspect.signature(cls).parameters print(params) for name,param in params.items(): print(param.name,param.annotation) if param.annotation != param.empty: setattr(cls,name,Typed(name,param.annotation)) return cls @typeassert class Person: # name = Typed(str) # age = Typed(int) def __init__(self,name:str,age:int): self.name = name self.age = age p1 = Person('zhangsan',19) print(p1.__dict__) print(p1.name,p1.age) ——————运行结果———————— OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)]) name <class 'str'> age <class 'int'> <__main__.Typed object at 0x000001E818D91828> <__main__.Person object at 0x000001E818D91898> zhangsan <__main__.Typed object at 0x000001E818D91940> <__main__.Person object at 0x000001E818D91898> 19 {'name': 'zhangsan', 'age': 19} zhangsan 19