33面向对象8_descriptors
为顺昌等地区用户提供了全套网页设计制作服务,及顺昌网站建设行业解决方案。主营业务为成都做网站、网站建设、顺昌网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
descriptors描述器:
descriptor的表现:
用到3个魔术方法:__get__()、__set__()、__delete__();
object.__get__(self,instance,owner)
object.__set__(self,instance,value)
object.__delete__(self,instance)
self,指代当前实例,调用者;
instance,是owner的实例;
owner,是属性所属的类;
py中,一个类实现了__get__()、__set__()、__delete__()三个方法中的任何一个方法,就是描述器;
如果仅实现了__get__(),就是non-data descriptor非数据描述器;
如果同时实现了__get__()、__set__(),就是data descriptor数据描述器,如@property;
如果一个类的类属性设置为描述器,那么这个类它被称为owner属主,如B类中类属性x = A();
关键记住:类属性;
注:
当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()、__set__()、__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法;如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法;
non-data descriptor和data descriptor:
理解1:
如果一个类的属性是一个数据描述器,对实例属性的操作(该实例属性与类属性名相同时)相当于操作类属性;
理解2:
官方是用优先级定义的;
一个类的类属性是一个数据描述器,对该类的实例属性的操作,该类的实例的__dict__优先级降低(数据描述器的优先级高于实例的__dict__);
如果是非数据描述器,则实例的__dict__高于描述器的优先级;
属性查找顺序:
实例的__dict__优先于non-data descriptor;
data descriptor优先于实例的__dict__;
__delete__有同样的效果,有此方法就是data descriptor;
B.x = 500 #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么
print(B.x)
b = B()
b.x = 600 #虽触发了__set__(),未把类属性覆盖,也写不进__dict__中,被__set__()拦截了,对数据起到一定保护作用
本质:
查看实例的__dict__可知,data descriptor,实例的__dict__都被__set__()拦住,实例的属性名与类属性名相同时,写不进实例的__dict__中;
原来不是什么data descriptor优先级高,而是把实例的属性从__dict__中给去掉了(实例的属性名与类的属性名相同),造成了该属性如果是data descriptor优先访问的假象,说到底,属性访问的顺序从来就没变过;
py的描述器应用非常广泛;
py的所有方法(包括@staticmethod、@classmethod、__init__()),都是non-data descriptor,因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一类的其它实例不同的行为;
@property类实现是一个data descriptor,因此,实例不能覆盖属性的行为;
例:
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) # X,AttributeError: 'int' object has no attribute 'a1'
输出:
A.__init__
a1
B.__init__
例:
class A:
def __init__(self):
print('A.__init__')
self.a1 = 'a1'
def __get__(self, instance, owner): #类A中定义了__get__(),类A就是一个描述器,对类B的属性x读取,成为对类A的实例的访问就会调用__get__()
print('A.__get__',self,instance,owner)
# return self #解决B.x.a1报错NoneType问题,黑魔法,通过属性描述器来操作属主,拿到属主的类,可动态的改所有属性
class B:
x = A() #当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()、__set__()、__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法
def __init__(self):
print('B.__init__')
# self.x = 100
self.x = A() #如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法
print(B.x) #V,要在类属性上访问,才触发__get__(),该例__get__()方法返回None
# print(B.x.a1) #X,AttributeError: 'NoneType' object has no attribute 'a1',解决办法:在类A的__get__()添加返回值return self
b = B()
print(b.x)
print(b.x.a1) #实例属性上访问不会触发__get__()
输出:
A.__init__
A.__get__ <__main__.A object at 0x7f3d53b2bb38> None
None
B.__init__
A.__init__
<__main__.A object at 0x7f3d53b2bba8>
a1
例:
class A:
def __init__(self):
print('A.__init__')
self.a1 = 'a1test'
def __get__(self, instance, owner):
print('A.__get__',self,instance,owner)
return self
def __set__(self, instance, value): #有__set__()后,类B的实例b的__dict__为空,只能向上访问类属性的
print('A.__set__',self,instance,value)
class B:
x = A()
def __init__(self):
print('B.__init__')
# self.x = 100
self.x = A()
print(B.x)
print("*"*20)
print(B.x.a1)
print("#"*20)
b = B() #触发__set__()
print("*"*20)
print(b.x) #数据描述器,对实例属性的操作(该实例属性与类属性的名字相同)相当于操作类属性,查看实例的__dict__(为空)可知(向上找了类属性)
print("*"*20)
print(b.x.a1)
print("#"*20)
print(b.__dict__)
print(B.__dict__)
# B.x = 500 #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么
# print(B.x)
输出:
A.__init__
A.__get__ <__main__.A object at 0x7f63acbcd400> None
<__main__.A object at 0x7f63acbcd400>
********************
A.__get__ <__main__.A object at 0x7f63acbcd400> None
a1test
####################
B.__init__
A.__init__
A.__set__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <__main__.A object at 0x7f63acbcdba8>
********************
A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>
<__main__.A object at 0x7f63acbcd400>
********************
A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>
a1test
####################
{}
{'__module__': '__main__', 'x': <__main__.A object at 0x7f63acbcd400>, '__init__':
例:
class A:
def __init__(self):
print('A.__init__')
self.a1 = 'a1test'
def __get__(self, instance, owner):
print('A.__get__',self,instance,owner)
return self
def __set__(self, instance, value):
print('A.__set__',self,instance,value)
class B:
x = A()
def __init__(self):
print('B.__init__')
# self.x = 100
self.x = A()
b = B()
print("*"*20)
b.x = 600 #虽触发了__set__(),未把类属性覆盖,也写不进实例的__dict__中(查看实例的__dict__可知),被__set__()拦截了,对数据起到一定保护作用
print("*"*20)
print(b.x) #调用__get__()
print("*"*20)
print(b.__dict__)
输出:
A.__init__
B.__init__
A.__init__
A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> <__main__.A object at 0x7f05dd15ebe0>
********************
A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> 600
********************
A.__get__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8>
<__main__.A object at 0x7f05dd15eb70>
********************
{}
习题:
1、实现StaticMethod装饰器,完成staticmethod装饰器的功能;
2、实现ClassMethod装饰器,完成classmethod装饰器的功能;
3、对实例的数据进行校验;
class Person:
def __init__(self,name:str,age:int):
self.name = name
self.age = age
1、
class StaticMethod:
def __init__(self,fn):
# print('__init__',fn)
self.fn = fn
def __get__(self, instance, owner):
# print('__get__',self,instance,owner)
return self.fn
class A:
@StaticMethod
def foo(): #类装饰器装饰完后,原函数消失了,foo=StaticMethod(foo),成为装饰器类的实例了,在类属性上访问另一个类的实例时就会触发__get__()方法
print('test function')
@staticmethod
def foo2():
print('test2 func')
f = A.foo
print(f)
f()
A.foo2()
A().foo()
A().foo2()
输出
test function
test2 func
test function
test2 func
2、
from functools import partial
class ClassMethod:
def __init__(self,fn):
print('__init__',fn)
self.fn = fn
def __get__(self, instance, cls):
print('__get__', self, instance, cls)
# return self.fn(cls) #X,NoneType
return partial(self.fn, cls) #固定下来,返回一个新函数
class A:
@ClassMethod
def bar(cls):
print(cls.__name__)
# print(A.bar)
# print()
A.bar()
print()
A().bar()
print()
print(A.__dict__)
输出:
__init__
__get__ <__main__.ClassMethod object at 0x7f2999039d68> None
A
__get__ <__main__.ClassMethod object at 0x7f2999039d68> <__main__.A object at 0x7f2999039da0>
A
{'__module__': '__main__', 'bar': <__main__.ClassMethod object at 0x7f2999039d68>, '__dict__':
3、
class Typed:
def __init__(self,type):
self.type = type
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
print('T.__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('tom',18)
3、
改进:用装饰器+描述器,py中大量使用;
import inspect
class Typed:
def __init__(self, type):
self.type = type
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
print('set', self, instance, value)
if not isinstance(value, self.type):
raise ValueError(value)
class TypeAssert:
def __init__(self, cls):
self.cls = cls
params = inspect.signature(self.cls).parameters
# print(params)
for name, param in params.items():
print(name, param.annotation)
if param.annotation != param.empty:
setattr(self.cls, name, Typed(param.annotation)) #动态类属性注入
def __call__(self, name, age):
# params = inspect.signature(self.cls).parameters
# print(params)
# for name,param in params.items():
# print(name,param.annotation)
# if param.annotation != param.empty:
# setattr(self.cls,name,Typed(param.annotation))
p = self.cls(name, age)
return p
@TypeAssert
class Person:
# name = Typed(str) #动态类属性注入
# age = Typed(age)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p1 = Person('jerry', 18)
p2 = Person('tom', 16)
print(id(p1))
print(id(p2))
输出:
name
age
set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a121dd8> jerry
set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a121dd8> 18
set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a0af940> tom
set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a0af940> 16
140022008389080
140022007920960
习题:
1、将链表,封装成容器:
要求:
1)提供__getitem__()、__iter__()、__setitem__();
2)使用一个列表,辅助完成上面的方法;
3)进阶:不使用列表,完成上面的方法;
2、实现类property装饰器,类名称为Property;
网站栏目:33面向对象8_descriptors
网页网址:http://scjbc.cn/article/gcdshh.html