目录:
1.描述器的表现
2.描述器定义
3.属性的访问顺序
4.Python中的描述器
5.新增方法
1. 描述器的表现
用到3个魔术方法:
1.__get__(),object.__get__(self,instance,owner)
2.__set__(),object.__set__(self,instance,value)
3.__delete__(),object.__delete__(self,instance)
self指代当前实例,调用者
instance是owner的实例
owner是属性的所属的类
例1:
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
class B:
x = A()
def __init__(self):
print('B.init')
print('****************')
print(B.x.a1)
print('----------------')
b = B()
print(b.x.a1)
# 运行结果
A.init
****************
a1
----------------
B.init
a1
类加载的时候,类变量需要先生成,而类B的x属性是A的实例,所以类A先初始化,所以打印A.init。
然后执行到打印B.x.a1,然后实例化并初始化B的实例b。
打印b.x.a1,会查到类属性b.x,指向A的实例,所以返回A实例的属性a1的值。
例2:
根据例1中的执行流程,在类A中添加get方法
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('****************')
print(B.x)
print('----------------')
b = B()
print(b.x)
# 执行结果
A.init
****************
A.__get__ <__main__.a object at> None
None
----------------
B.init
A.__get__ <__main__.a object at> <__main__.b object at>
None
因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用__get__方法。
例3:
如何解决例2中报错问题,因为__get__方法,self,instance,owner三个参数?
__get__(self, instance, owner)
B.x调用返回<__main__.a object at> None
b.x调用返回<__main__.a object at> <__main__.b object at>
1.self对应都是A的实例
2.owner对应都是B类
3.instance
-None表示不是B类的实例,对应调用B.x
-<__main__.b object at>表示是B的实例,对应调用B().x使用返回值解决。返回self,就是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()
def __init__(self):
print('B.init')
print('****************')
print(B.x)
print(B.x.a1)
print('----------------')
b = B()
print(b.x)
print(b.x.a1)
# 运行结果
A.init
****************
A.__get__ <__main__.a object at> None
A.__get__ <__main__.a object at> None
a1
----------------
B.init
A.__get__ <__main__.a object at> <__main__.b object at>
A.__get__ <__main__.a object at> <__main__.b object at>
a1
例4:
类B的属性也可以这样吗?
只有类属性是类的实例才可以
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 # 解决返回值为None的问题
class B:
x = A()
def __init__(self):
print('B.init')
self.b = A() # 实例属性也指向一个A的实例
print('****************')
print(B.x)
print(B.x.a1)
print('----------------')
b = B()
print(b.x)
print(b.x.a1)
print(b.b) # 并没有触发__get__
# 运行结果
A.init
****************
A.__get__ <__main__.a object at> None
A.__get__ <__main__.a object at> None
a1
----------------
B.init
A.init
A.__get__ <__main__.a object at> <__main__.b object at>
A.__get__ <__main__.a object at> <__main__.b object at>
a1
2. 描述器定义
Python中,一个类实现了__get__,__set__,__delete__三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
如果一个类的类属性设置为描述器实例,那么它被称为owner属主。
当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法。
仅实现了__get__,就是非数据描述符non-data descriptor
实现了__get__、__set__就是数据描述符data descriptor
3. 属性的访问顺序
例1:
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')
self.x = 'b.x' # 增加实例属性x
print('****************')
print(B.x)
print(B.x.a1)
print('----------------')
b = B()
print(b.x)
print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'
# 执行结果
Traceback (most recent call last):
File "C:/Users/dell/PycharmProjects/pythonProject/test2.py", line 23, in
print(b.x.a1)
AttributeError: 'str' object has no attribute 'a1'
A.init
****************
A.__get__ <__main__.a object at> None
A.__get__ <__main__.a object at> None
a1
----------------
B.init
b.x
例2:
上例中,类A只实现了__get__()方法,b.x访问到了实例的属性,而不是描述器。
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))
self.data = value
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x'
print('****************')
print(B.x)
print(B.x.a1)
print('----------------')
b = B()
print(b.x)
print(b.x.a1)
print(b.x.data)
# 运行结果
A.init
****************
A.__get__ <__main__.a object at> None
A.__get__ <__main__.a object at> None
a1
----------------
B.init
A.__set__ <__main__.a object at> <__main__.b object at> b.x
A.__get__ <__main__.a object at> <__main__.b object at>
A.__get__ <__main__.a object at> <__main__.b object at>
a1
A.__get__ <__main__.a object at> <__main__.b object at>
b.x
NOTE:
1.所有b.x就会访问描述器的__get__()方法,代码中返回的self就是描述器实例,它的实例字典中就保存着a1和data属性,可以打印b.x.__dict__就可以查看
2.属性查找顺序:实例的__dict__优先于非数据描述器,数据描述器优先于实例的__dict__
3.__delete__方法有同样的效果,有了这个方法,也是数据描述器
4. Python中的描述器
Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。
property()函数实现为一个数据描述器,因此,实例不能覆盖属性的行为。
例1:
class A:
@classmethod
def foo(cls):
pass
@staticmethod
def bar():
pass
@property
def z(self):
return 5
def getfoo(self):
return self.foo
def __init__(self):
self.foo = 100
self.bar = 200
a = A()
print(a.__dict__)
print(A.__dict__)
# 运行结果,foo,bar都可以实例中覆盖,但z不可以
{'foo': 100, 'bar': 200}
{
'__module__': '__main__',
'foo': ,
'bar': ,
'z': ,
'getfoo': ,
'__init__': ,
'__dict__': ,
'__weakref__': ,
'__doc__': None
}
5. 新增方法
新增描述器方法set_name,它在属主类构建的时候就会调用
class A:
def __init__(self):
print('A init')
def __get__(self, instance, owner):
print(1,self,instance,owner)
return self
def __set_name__(self, owner, name):
print(2,self,owner,name)
self.name = name
class B:
x = A()
print('**********')
print(B().x)
# 运行结果,此方法是为了知道属主和属主类的类属性名
A init
2 <__main__.a object at> x
**********
1 <__main__.a object at> <__main__.b object at>