IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python 描述器 Descriptors 详解 -> 正文阅读

[Python知识库]Python 描述器 Descriptors 详解

1、概念介绍

  • 描述器 是两个类之间的事

  • 描述器一般会用到4个魔术方法,如下:

    class A:
        def __get__(self, instance, owner):
            pass
        # self      指代当前实例,调用者
        # instance  是owner的实例
        # owner     是属性的所属的类
        def __set__(self, instance, value):    
            pass
        
        def __delete__(self, instance):
            pass
        
        # 3.6以后,新增一个魔术方法
        def __set_name__(self, owner, name):
            pass
    
  • Python 中,一个类实现了__get__ 、 __set__ 、 __delete__三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议

  • 仅实现了__get__,就是 非数据描述器 non-data descriptor

  • 实现了__get____set__,就是 数据描述器 data descriptor

  • 如果一个类的类属性设置为描述器实例,那么它就称为owner属主

  • 当该类的类属性被查找、设置和删除时,就会调用描述器相应的方法

  • 属性查找顺序:数据描述器 优先于 实例的__dict__ 优先于 非数据描述器

  • __delete__ 方法有同样的效果,有了这个方法,也是数据描述器

  • 数据描述器可以阻止实例对属性的修改

2、通过示例验证

2.1 正常的类属性访问

class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

class B:
    x = A()
    def __init__(self):
        print('B init')
        self.a = 'BBB'
        

print(1, B.x)
print(2, B().x)
print(3, B.x.a)
print(4, B().x.a)
b1 = B()
print(5, b1.x)
print(6, b1.x.a)
print(7, b1.a)
print(8, b1.__dict__)
A init
1 <__main__.A object at 0x000001A4A049C580>
B init
2 <__main__.A object at 0x000001A4A049C580>
3 AAA
B init
4 AAA
B init
5 <__main__.A object at 0x000001A4A049C580>
6 AAA
7 BBB
8 {'a': 'BBB'}

2.2 非数据描述器且实例没有同名属性

# 非数据描述器
# self在哪个类中,就是哪个类的实例
# class B实例没有同名类属性
class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner):  # 类A 是非数据描述器
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self

class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'
        self.b = A()
        
print('+' * 55)
print(1, B.x)  # 调用__get__方法
print(2, B.x.a)  # 调用__get__方法
print('*' * 55)
print(3, B().x)  # 调用__get__方法
print(4, B().x.a)  # 调用__get__方法
print('=' * 55)
b1 = B()
print(5, b1.x)  # 调用__get__方法
print(6, b1.x.a)  # 调用__get__方法
print('+' * 55)
print(7, b1.b)  # 实例属性是类属性时,并不会调用 非数据描述器 的__get__方法
print(8, b1.__dict__)
A init
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is None.
owner is <class '__main__.B'>.<=]
1 <__main__.A object at 0x000001A4A23F1550>
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is None.
owner is <class '__main__.B'>.<=]
2 AAA
*******************************************************
B init
A init
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is <__main__.B object at 0x000001A4A202EFA0>.
owner is <class '__main__.B'>.<=]
3 <__main__.A object at 0x000001A4A23F1550>
B init
A init
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is <__main__.B object at 0x000001A4A202E070>.
owner is <class '__main__.B'>.<=]
4 AAA
=======================================================
B init
A init
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is <__main__.B object at 0x000001A4A203C790>.
owner is <class '__main__.B'>.<=]
5 <__main__.A object at 0x000001A4A23F1550>
[=> A.__get__.
self is <__main__.A object at 0x000001A4A23F1550>.
instance is <__main__.B object at 0x000001A4A203C790>.
owner is <class '__main__.B'>.<=]
6 AAA
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 <__main__.A object at 0x000001A4A203CC40>
8 {'a': 'BBB', 'b': <__main__.A object at 0x000001A4A203CC40>}

2.3 非数据描述器且实例有同名属性

# 非数据描述器
# self在哪个类中,就是哪个类的实例
# class B实例有同名类属性
class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner):  # 类A 是非数据描述器
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self

class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'
        self.b = A()
        self.x = 'x in class __init__'  # 增加一个与类属性同名和实例属性
        
print('+' * 55)
# print(1, B.x)  # 调用__get__方法
# print(2, B.x.a)  # 调用__get__方法
print('*' * 55)
print(3, B().x)  # 不会调用__get__方法
# print(4, B().x.a)  # AttributeError: 'str' object has no attribute 'a'
print('=' * 55)
b1 = B()
print(5, b1.x)
# print(6, b1.x.a)  # AttributeError: 'str' object has no attribute 'a'
print('+' * 55)
print(7, b1.b)  # 实例属性是类属性时,并不会调用 非数据描述器 的__get__方法
print(8, b1.__dict__)
A init
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
*******************************************************
B init
A init
3 x in class __init__
=======================================================
B init
A init
5 x in class __init__
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 <__main__.A object at 0x000002041BEFFF10>
8 {'a': 'BBB', 'b': <__main__.A object at 0x000002041BEFFF10>, 'x': 'x in class __init__'}

2.4 数据描述器且实例有同名属性

# 数据描述器
# self在哪个类中,就是哪个类的实例
class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner): 
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self
    
    def __set__(self, instance, value):
        print("[=> A.__set__.\nself is {}.\ninstance is {}.\nvalue is {}.<=]".format(self, instance, value))
        

class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'
        self.b = A()
        print('================ before B init x ================')
        self.x = 'x in class __init__'  # 增加一个与类属性同名和实例属性
        print('================ after B init x ================') # 初始化过程中,会调用 __set__方法
        
        
print('=' * 55)
b1 = B()
print(5, b1.x)
print(6, b1.x.a)  
print('+' * 55)
print(7, b1.b)  
print(8, b1.__dict__)  # 字典里面并没有属性x,数据描述器阻止实例同名属性增加
A init
=======================================================
B init
A init
================ before B init x ================
[=> A.__set__.
self is <__main__.A object at 0x000002041BF0DA00>.
instance is <__main__.B object at 0x000002041BEFFF70>.
value is x in class __init__.<=]
================ after B init x ================
[=> A.__get__.
self is <__main__.A object at 0x000002041BF0DA00>.
instance is <__main__.B object at 0x000002041BEFFF70>.
owner is <class '__main__.B'>.<=]
5 <__main__.A object at 0x000002041BF0DA00>
[=> A.__get__.
self is <__main__.A object at 0x000002041BF0DA00>.
instance is <__main__.B object at 0x000002041BEFFF70>.
owner is <class '__main__.B'>.<=]
6 AAA
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 <__main__.A object at 0x000002041BF05E80>
8 {'a': 'BBB', 'b': <__main__.A object at 0x000002041BF05E80>}

2.5 数据描述器且实例有同名属性,验证属性查找顺序,无限递归

# 数据描述器
# self在哪个类中,就是哪个类的实例
# 现在在B实例中字典中,手动添加x属性,验证属性x查找顺序
class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner): 
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self
    
    def __set__(self, instance, value):
        print("[=> A.__set__.\nself is {}.\ninstance is {}.\nvalue is {}.<=]".format(self, instance, value))
        setattr(instance, 'x', value)  # 会一直调用__set__
        # RecursionError: maximum recursion depth exceeded while getting the str of an object

class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'
        self.b = A()
        print('=== before B init x ===')
        self.x = 'x in class __init__'  # 增加一个与类属性同名和实例属性
        print('=== after B init x ===')
        

print('=' * 55)
b1 = B()
print(5, b1.x)
print(6, b1.x.a)  
print('+' * 55)
print(7, b1.b)  
print(8, b1.__dict__)  # 字典里面并没有属性 x

2.6 数据描述器且实例有同名属性,验证属性查找顺序,解决无限递归

# 数据描述器
# self在哪个类中,就是哪个类的实例
# 现在在B实例中字典中,手动添加x属性,验证属性x查找顺序
# 进行优化,避免递归
# 数据描述器 优先于 实例__dict__
# 实例与类有同名属性时,初始化时会调用数据描述器方法

class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner): 
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self
    
    def __set__(self, instance, value):
        print("[=> A.__set__.\nself is {}.\ninstance is {}.\nvalue is {}.<=]".format(self, instance, value))
        # setattr(instance, 'x', value)  # 会一直调用__set__
        # instance.x = value  # 会一直调用__set__
        # RecursionError: maximum recursion depth exceeded while getting the str of an object
        instance.__dict__['x'] = value
        
class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'  # 不同名属性不会调用
        self.b = A()    # 不同名属性不会调用
        print('=== before B init x ===')
        self.x = 'x in class __init__'  # 增加一个与类属性同名和实例属性
        print('=== after B init x ===') # 实例与类有同名属性时,初始化时会调用数据描述器方法
        

b1 = B()
print('=' * 55)
print(5, b1.x)
print(6, b1.x.a)  
print('+' * 55)
print(7, b1.b)  
print(8, b1.__dict__)  # 字典里已经手动添加属性'x'
A init
B init
A init
=== before B init x ===
[=> A.__set__.
self is <__main__.A object at 0x00000125759579A0>.
instance is <__main__.B object at 0x0000012575957A00>.
value is x in class __init__.<=]
=== after B init x ===
=======================================================
[=> A.__get__.
self is <__main__.A object at 0x00000125759579A0>.
instance is <__main__.B object at 0x0000012575957A00>.
owner is <class '__main__.B'>.<=]
5 <__main__.A object at 0x00000125759579A0>
[=> A.__get__.
self is <__main__.A object at 0x00000125759579A0>.
instance is <__main__.B object at 0x0000012575957A00>.
owner is <class '__main__.B'>.<=]
6 AAA
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 <__main__.A object at 0x0000012575957D00>
8 {'a': 'BBB', 'b': <__main__.A object at 0x0000012575957D00>, 'x': 'x in class __init__'}

2.7 数据描述器且实例有同名属性,禁止实例同名属性覆盖

# 数据描述器
# self在哪个类中,就是哪个类的实例
# 现在在B实例中字典中,手动添加x属性,验证属性x查找顺序
# 进行优化,避免递归
# 数据描述器 优先于 实例__dict__
# 实例与类有同名属性时,初始化时会调用数据描述器方法
# 运用数据描述器,阻止修改同名属性

class A:
    def __init__(self):
        print('A init')
        self.a = 'AAA'

    def __get__(self, instance, owner): 
        print("[=> A.__get__.\nself is {}.\ninstance is {}.\nowner is {}.<=]".format(self, instance, owner))
        return self
    
    def __set__(self, instance, value):
        print("[=> A.__set__.\nself is {}.\ninstance is {}.\nvalue is {}.<=]".format(self, instance, value))
        # setattr(instance, 'x', value)  # 会一直调用__set__
        # instance.x = value  # 会一直调用__set__
        # RecursionError: maximum recursion depth exceeded while getting the str of an object
        # instance.__dict__['x'] = value
        if instance:
            raise AttributeError('can\'t set attribute')
        
class B:
    x = A()  # 对类B或者类B的实例的属性读取,称为对类A的实例访问,就会调用__get__方法
    def __init__(self):
        print('B init')
        self.a = 'BBB'  # 不同名属性不会调用
        self.b = A()    # 不同名属性不会调用
        print('=== before B init x ===')
        self.x = 'x in class __init__'  # 增加一个与类属性同名和实例属性
        print('=== after B init x ===') # 实例与类有同名属性时,初始化时会调用数据描述器方法      
        
print('+' * 55)
b = B()
print('=' * 55)
print(b.x)
A init
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
B init
A init
=== before B init x ===
[=> A.__set__.
self is <__main__.A object at 0x00000204606C4160>.
instance is <__main__.B object at 0x0000020460C51A60>.
value is x in class __init__.<=]
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: can't set attribute
# 联想 property 的只读属性

class A:
    def __init__(self):
        self._x = 5
        
    @property
    def x(self):
        return self._x
    
    
a = A()
print(a)
print(a.x)
a.x = 100  # AttributeError: can't set attribute

2.8 __set_name__

# __set_name__

class A:
    def __init__(self):
        print(1, 'A init')
        
    def __get__(self, instance, owner):
        print(2, '__get__', self, instance, owner)
        return self
    
    def __set_name__(self, owner, name):
        print(3, '__set_name__', self, owner, name)
        self.name = name
        
class B:
    x = A()
    
print('=' * 30)
print(B().x)
print('*' * 30)
print(B().x.__dict__)
1 A init
3 __set_name__ <__main__.A object at 0x0000020460E7EEE0> <class '__main__.B'> x
==============================
2 __get__ <__main__.A object at 0x0000020460E7EEE0> <__main__.B object at 0x00000204606F4160> <class '__main__.B'>
<__main__.A object at 0x0000020460E7EEE0>
******************************
2 __get__ <__main__.A object at 0x0000020460E7EEE0> <__main__.B object at 0x0000020460751B50> <class '__main__.B'>
{'name': 'x'}

3、描述器应用

class A:
    def __init__(self):  # non-data descriptor
        self.clsmtd = 100
        self.stamtd = 200
        self.foo = 300
        # self.ppty = 500  # AttributeError: can't set attribute    
    
    @classmethod
    def clsmtd(cls):  # non-data descriptor
        pass
    
    @staticmethod
    def stamtd():  # non-data descriptor
        pass
    
    @property
    def ppty(self):  # data descriptor
        return 5
    
    def foo(self):  # non-data descriptor
        return self.foo
 
a = A()
print(a.__dict__)
print(A.__dict__)
{'clsmtd': 100, 'stamtd': 200, 'foo': 300}
{'__module__': '__main__', '__init__': <function A.__init__ at 0x0000020460456C10>, 'clsmtd': <classmethod object at 0x0000020460D07FD0>, 'stamtd': <staticmethod object at 0x0000020460D07730>, 'ppty': <property object at 0x000002045F136C70>, 'foo': <function A.foo at 0x000002045F0D2DC0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-04-14 23:50:39  更:2022-04-14 23:51:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 17:23:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码