python高级之描述器

简介:
  • 描述器用到的方法

    用到3个魔术方法: __get__()、__set__()、__delete__()
    方法使用格式:
        obj.__get__(self, instance, owner)
        obj.__set__(self, instance, value)
        obj.__delete__(self, instance)

    self: 指当前类的实例本身
    instance: 指owner的实例
    owner: 指当前实例作为属性的所属类

代码一

以下代码执行过程:
    定义B类时,执行A()赋值操作,进行A类的初始化,再打印B类调用类属性x的a1属性
    紧接着执行B类的初始化,通过b实例调用类属性的x的a1属性

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

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

print('-' * 20)
print(B.x.a1)

print('*' * 20)
b = B()
print(b.x.a1)


  • 描述器定义

Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法,
那么这个类就是描述器.
如果仅实现了__get__,就是非数据描述符 non-data descriptor
同时实现了除__get__以外的__set__或__delete__方法,就是数据描述符 data descriptor

如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主

描述器方法何时被触发:
    当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发
    当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发

代码二

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('-' * 20)
print(B.x)
# print(B.x.a1)   # AttributeError B.x为None,None没有a1属性

print('*' * 20)
b = B()
# print(b.x.a1)  # AttributeError B.x为None,None没有a1属性

调用B类的类属性,被A类__get__方法拦截,并返回值None


代码三

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('-' * 20)
print(B.x)
print(B.x.a1)   

print('*' * 20)
b = B()
print(b.x)
print(b.x.a1)  

解决上例中的返回值为None,将A类的实例返回,可成功调用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()
    print(x)
    def __init__(self):
        print('B.init')
#         self.x = 100    #  实例调用x属性时,直接查实例自己的__dict__
        self.x = A()      # 实例调用x属性时,不进入A类的__get__方法
        print(self.x)      

print('-' * 20)
print(B.x)    # __get__
print(B.x.a1)    # __get__

print('*' * 20)
b = B()
print(b.x)
print(b.x.a1) 

总结: 不论是实例还是类,只要是访问了是描述器的类属性,
都会被描述器的__get__方法拦截


  • 属性的访问顺序(本质)

代码五

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))

class B:
    x  = A()
    print(x)
    def __init__(self):
        print('B.init')
        self.x = 100
#         self.x = A()   # 同上面100结果类似
        print(self.x)

# print('-' * 20)
# print(B.x)
# print(B.x.a1)   

# print('*' * 20)
b = B()
# print(b.x)
# print(b.x.a1)  
print(b.__dict__)
print(B.__dict__)

屏蔽A类的__set__方法,实例的__dict__为{'x': 100}
不屏蔽A类的__set__方法,实例的__dict__为{}
__set__方法本质将实例的__dict__的属性名清空,从而达到数据描述器优先于查实例字典的假象
  • Python中的描述器

描述器在Python中应用非常广泛

Python的方法(包括staticmethod()和classmethod()) 都实现为非数据描述器.
因此,实例可以通过'.'号进行生成属性.
property()函数实现为一个数据描述器.则实例不能使用'.'号进行赋值属性.

示例

class A:
    @classmethod
    def foo(cls):
        pass

    @staticmethod
    def bar():
        pass

    @property
    def z(self):
        return 5

    def __init__(self): # 非数据描述器
        self.foo = 100
        self.bar = 200
#         self.z = 300    # z属性不能使用实例覆盖
a = A()
print(a.__dict__)
print(A.__dict__)


  • 练习


    • 实现StaticMethod装饰器,完成staticmethod装饰器的功能

  • class StaticMethod:
        def __init__(self,fn):
            self._fn = fn
    
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return self._fn
    
    class A:
        @StaticMethod      # stmd = StaticMehtod(stmd)
        def stmd():
            print('stmd')
    
    print(A.__dict__)
    A.stmd()    # 类调用stmd属性

    • 实现ClassMethod装饰器,完成classmethod装饰器的功能

  • from functools import partial
    
    
    class ClassMethod:
        def __init__(self,fn):
            self._fn = fn
    
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return partial(self._fn,owner)      
            # 使用partial函数将类给作为默认参数
    
    class A:
        @ClassMethod       # clsmd = ClassMethod(clsmd)
        def clsmd(cls):
            print('cls',cls.__name__)
    
    print(A.__dict__)
    A.clsmd()

    • 类初始化的参数检查

  • import inspect
    
    class Typed:
    
        def __init__(self,tp):
            self._tp = tp
    
        def __get__(self,instance,owner):
            pass
    
        def __set__(self,instance,value):
            if not isinstance(value,self._tp):
                raise ValueError(value)
            setattr(instance.__class__,self._name,value)
    
    def pcheck(cls):
        def wrapper(*args):
            sig = inspect.signature(cls)
            params = sig.parameters
            for i,(name,param) in enumerate(params.items()):
                if param.empty != param.annotation:
    #                 if not isinstance(args[i],param.annotation):
    #                     raise ValueError(args[i])
                    setattr(cls,name,Typed(param.annotation))
            return cls(*args)
        return wrapper
    
    @pcheck
    class A:
    #     a = Typed(str)
    #     b = Typed(int)
        def __init__(self,a:str,b:int):
            self.a = a
            self.b = b
    
    A('1',2)

    描述器结合装饰实现

    import inspect
    
    
    class Typed:
        def __init__(self,name,tp):
            self._name = name
            self._tp = tp
    
        def __get__(self,instance,owner):
            print('get',self,instance,owner)
            return instance.__dict__[self._name]
    
        def __set__(self,instance,value):
            print('set',self,instance,value)
            if not isinstance(value,self._tp):
                raise ValueError(value)
            instance.__dict__[self._name] = value
    
    class A:
        a = Typed('a',str)
        b = Typed('b',int)
        def __init__(self,a:str,b:int):
            self.a = a
            self.b = b
    
    a = A('1',2)
    print(a.__dict__)
    # print(type(a.a),type(a.b))
    print(a.a)

    描述器实现

    import inspect
    
    def pcheck(cls):
        def wrapper(*args):
            sig = inspect.signature(cls)
            params = sig.parameters
            for i,(_,param) in enumerate(params.items()):
                if param.empty != param.annotation:
                    if not isinstance(args[i],param.annotation):
                        raise ValueError(args[i])
            return cls(*args)
        return wrapper
    
    @pcheck    # A = pcheck(A)
    class A:
        def __init__(self,a:str,b:int):
            self.a = a
            self.b = b
    
    A('1','2')

    装饰器版本

    class A:
        def __init__(self,a:str,b:int):
            if not (isinstance(a,str) and isinstance(b,int)):
                raise ValueError(a,b)
            else:
                self.a = a
                self.b = b
    A('1',2)

    直接参数检查

    思路:
        实现参数检查的本质是判断传入的参数是否符合形参定义的类型,也就是用isinstance进行判断.
        因此参数检查的不同实现的区别在于在哪些地方拦截传入的参数,来进行检查.
        上述实现的拦截地方:
            在类初始化时,在对实例属性赋值之前拦截
            使用装饰器,和inspect模块,在实例化之前进行参数检查
            使用描述器,在初始化时对实例属性设置时,触发描述器的__set__方法,在__set__方法中进行参数检查,再对其实例的类添加类属性
                (如果添加在实例上,则会递归调用回到__set__方法)
            使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查                                                                     本文转自 撒旦搞时间 51CTO博客,原文链接:http://blog.51cto.com/12074120/1984216,如需转载请自行联系原作者

相关文章
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
111 3
|
机器学习/深度学习 数据采集 算法
Python编程语言进阶学习:深入探索与高级应用
【7月更文挑战第23天】Python的进阶学习是一个不断探索和实践的过程。通过深入学习高级数据结构、面向对象编程、并发编程、性能优化以及在实际项目中的应用,你将能够更加熟练地运用Python解决复杂问题,并在编程道路上走得更远。记住,理论知识只是基础,真正的成长来自于不断的实践和反思。
|
数据采集 Java C语言
Python面向对象的高级动态可解释型脚本语言简介
Python是一种面向对象的高级动态可解释型脚本语言。
148 3
|
机器学习/深度学习 数据采集 人工智能
Python 是一种广泛使用的高级编程语言
【7月更文挑战第17天】Python 是一种广泛使用的高级编程语言
167 2
|
存储 算法 Python
“解锁Python高级数据结构新姿势:图的表示与遍历,让你的算法思维跃升新高度
【7月更文挑战第13天】Python中的图数据结构用于表示复杂关系,通过节点和边连接。常见的表示方法是邻接矩阵(适合稠密图)和邻接表(适合稀疏图)。图遍历包括DFS(深度优先搜索)和BFS(广度优先搜索):DFS深入探索分支,BFS逐层访问邻居。掌握这些技巧对优化算法和解决实际问题至关重要。**
147 1
|
存储 算法 调度
惊呆了!Python高级数据结构堆与优先队列,竟然能这样优化你的程序性能!
【7月更文挑战第10天】Python的heapq模块实现了堆和优先队列,提供heappush和heappop等函数,支持O(log n)时间复杂度的操作。优先队列常用于任务调度和图算法,优化性能。例如,Dijkstra算法利用最小堆加速路径查找。堆通过列表存储,内存效率高。示例展示了添加、弹出和自定义优先级元素。使用堆优化程序,提升效率。
160 2
|
算法 调度 Python
Python高手必备!堆与优先队列的高级应用,掌握它们,技术路上畅通无阻!
【7月更文挑战第9天】Python的heapq模块实现了堆数据结构,提供O(log n)操作如`heappush`和`heappop`。堆是完全二叉树,用于优先队列,保证最大/最小元素快速访问。例如,最小堆弹出最小元素,常用于Dijkstra算法找最短路径、Huffman编码压缩数据及任务调度。通过`heappush`和`heappop`可创建和管理优先队列,如`(优先级, 数据)`元组形式。理解并运用这些概念能优化算法效率,解决复杂问题。
140 2
|
程序员 Python
程序员必看!Python闭包与装饰器的高级应用,让你的代码更优雅、更强大
【7月更文挑战第7天】Python中的闭包和装饰器是高级特性,用于增强代码功能。闭包是内部函数记住外部作用域的变量,常用于动态函数和函数工厂。示例展示了`make_multiplier_of`返回记住n值的`multiplier`闭包。装饰器则是接收函数并返回新函数的函数,用于不修改原函数代码就添加功能。`my_decorator`装饰器通过`@`语法应用到`say_hello`函数上,展示了在调用前后添加额外行为的能力。这两种技术能提升代码的优雅性和效率。
93 3
|
Python
Python黑魔法揭秘:闭包与装饰器的高级玩法,让你代码飞起来
【7月更文挑战第7天】Python的闭包和装饰器是提升代码效率的神器。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建。示例中,`make_multiplier_of`返回一个保留`n`值的闭包。装饰器则是一个接收函数并返回新函数的函数,用于在不修改原函数情况下添加功能,如日志或性能追踪。`@my_decorator`装饰的`say_hello`函数在执行时会自动加上额外操作。掌握这两者,能让Python代码更优雅、强大。**
81 1
|
消息中间件 网络协议 网络安全
解锁Python Socket新姿势,进阶篇带你玩转高级网络通信技巧!
【7月更文挑战第26天】掌握Python Socket后,探索网络通信高级技巧。本指南深化Socket编程理解,包括非阻塞I/O以提升并发性能(示例使用`select`),SSL/TLS加密确保数据安全,以及介绍高级网络协议库如HTTP、WebSocket和ZeroMQ,简化复杂应用开发。持续学习,成为网络通信专家!
108 0

热门文章

最新文章

推荐镜像

更多