PYTHON进阶

9.描述器

2021-01-11  本文已影响0人  Stone_説

目录:
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 0x000002C955CAFFD0> None <class '__main__.B'>
None
----------------
B.init
A.__get__ <__main__.A object at 0x000002C955CAFFD0> <__main__.B object at 0x000002C955CB6048> <class '__main__.B'>
None

因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用__get__方法。

例3:
如何解决例2中报错问题,因为__get__方法,self,instance,owner三个参数?

__get__(self, instance, owner)
B.x调用返回<__main__.A object at 0x000002C955CAFFD0> None <class '__main__.B'>
b.x调用返回<__main__.A object at 0x000002C955CAFFD0> <__main__.B object at 0x000002C955CB6048> <class '__main__.B'>
1.self对应都是A的实例
2.owner对应都是B类
3.instance
  -None表示不是B类的实例,对应调用B.x
  -<__main__.B object at 0x000002C955CB6048>表示是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 0x000002730F1DFFD0> None <class '__main__.B'>
<__main__.A object at 0x000002730F1DFFD0>
A.__get__ <__main__.A object at 0x000002730F1DFFD0> None <class '__main__.B'>
a1
----------------
B.init
A.__get__ <__main__.A object at 0x000002730F1DFFD0> <__main__.B object at 0x000002730F1E6048> <class '__main__.B'>
<__main__.A object at 0x000002730F1DFFD0>
A.__get__ <__main__.A object at 0x000002730F1DFFD0> <__main__.B object at 0x000002730F1E6048> <class '__main__.B'>
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 0x0000028EA1F17048> None <class '__main__.B'>
<__main__.A object at 0x0000028EA1F17048>
A.__get__ <__main__.A object at 0x0000028EA1F17048> None <class '__main__.B'>
a1
----------------
B.init
A.init
A.__get__ <__main__.A object at 0x0000028EA1F17048> <__main__.B object at 0x0000028EA1F17080> <class '__main__.B'>
<__main__.A object at 0x0000028EA1F17048>
A.__get__ <__main__.A object at 0x0000028EA1F17048> <__main__.B object at 0x0000028EA1F17080> <class '__main__.B'>
a1
<__main__.A object at 0x0000028EA1F170B8>

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 <module>
    print(b.x.a1)
AttributeError: 'str' object has no attribute 'a1'
A.init
****************
A.__get__ <__main__.A object at 0x0000029F75A15080> None <class '__main__.B'>
<__main__.A object at 0x0000029F75A15080>
A.__get__ <__main__.A object at 0x0000029F75A15080> None <class '__main__.B'>
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 0x0000026A4E6F6080> None <class '__main__.B'>
<__main__.A object at 0x0000026A4E6F6080>
A.__get__ <__main__.A object at 0x0000026A4E6F6080> None <class '__main__.B'>
a1
----------------
B.init
A.__set__ <__main__.A object at 0x0000026A4E6F6080> <__main__.B object at 0x0000026A4E6F60B8> b.x
A.__get__ <__main__.A object at 0x0000026A4E6F6080> <__main__.B object at 0x0000026A4E6F60B8> <class '__main__.B'>
<__main__.A object at 0x0000026A4E6F6080>
A.__get__ <__main__.A object at 0x0000026A4E6F6080> <__main__.B object at 0x0000026A4E6F60B8> <class '__main__.B'>
a1
A.__get__ <__main__.A object at 0x0000026A4E6F6080> <__main__.B object at 0x0000026A4E6F60B8> <class '__main__.B'>
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': <classmethod object at 0x00000144A9A38128>, 
'bar': <staticmethod object at 0x00000144A9A38160>, 
'z': <property object at 0x00000144A99375E8>, 
'getfoo': <function A.getfoo at 0x00000144A9A30BF8>, 
'__init__': <function A.__init__ at 0x00000144A9A30C80>,
'__dict__': <attribute '__dict__' of 'A' objects>, 
'__weakref__': <attribute '__weakref__' of 'A' objects>, 
'__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 0x00000196A8EDF908> <class '__main__.B'> x
**********
1 <__main__.A object at 0x00000196A8EDF908> <__main__.B object at 0x00000196A8EDFF98> <class '__main__.B'>
<__main__.A object at 0x00000196A8EDF908>
上一篇 下一篇

猜你喜欢

热点阅读