当前位置:首页 > 技术分析 > 正文内容

Python将装饰器定义为类

问题

你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例。你需要让你的装饰器可以同时工作在类定义的内部和外部。

解决方案

为了将装饰器定义成一个实例,你需要确保它实现了 __call__() 和 __get__() 方 法。例如,下面的代码定义了一个类,它在其他函数上放置一个简单的记录层:

import types
from functools import wraps


class Profiled:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0
    
    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:

@Profiled
def add(x, y):
    return x + y

class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)

在交互环境中的使用示例:

>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls
2
>>> s = Spam()
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3

讨论

将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。 首先,使用 functools.wraps() 函数的作用跟之前还是一样,将被包装函数的元 信息复制到可调用实例中去。 其次,通常很容易会忽视上面的 __get__() 方法。如果你忽略它,保持其他代码 不变再次运行,你会发现当你去调用被装饰实例方法时出现很奇怪的问题。例如:

>>> s = Spam()
>>> s.bar(3)
Traceback (most recent call last):
...
TypeError: bar() missing 1 required positional argument: 'x'

出错原因是当方法函数在一个类中被查找时,它们的 __get__() 方法依据描述器协议被调用。在这里,__get__() 的目的是创建 一个绑定方法对象 (最终会给这个方法传递 self 参数)。下面是一个例子来演示底层原 理:

>>> s = Spam()
>>> def grok(self, x):
...     pass
...
>>> grok.__get__(s, Spam)
<bound method Spam.grok of <__main__.Spam object at 0x100671e90>>
>>>

__get__() 方法是为了确保绑定方法对象能被正确的创建。type.MethodType() 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。如果这个 方法是在类上面来访问,那么 __get__() 中的 instance 参数会被设置成 None 并直接 返回 Profiled 实例本身。这样的话我们就可以提取它的 ncalls 属性了。 如果你想避免一些混乱,也可以考虑另外一个使用闭包和 nonlocal 变量实现的装 饰器。例如:

import types
from functools import wraps

def profiled(func):
    ncalls = 0
    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal ncalls
        ncalls += 1
        return func(*args, **kwargs)
    wrapper.ncalls = lambda: ncalls
    return wrapper

# Example
@profiled
def add(x, y):
    return x + y

这个方式跟之前的效果几乎一样,除了对于 ncalls 的访问现在是通过一个被绑定 为属性的函数来实现,例如:

>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls()
2
>>>

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4715.html

标签: kwargs.get
分享给朋友:

“Python将装饰器定义为类” 的相关文章

泛微预算管理平台:费用精细化管控,告别预算超支

随着企业成本的不断攀升,利润空间越来越小,费用管控变得越来越重要。OA系统预算管理作为帮助协调和控制一定时期内资源的获得、配置和使用的方式,早已成为财务、管理工作中不可或缺的一部分。那么,如何才能避免大量款项的不翼而飞?如何才能细化费用管控避免预算超支?针对这一问题,OA系统通过对错综复杂费用的智能...

Git 分支管理策略汇总

最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码?我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的流程。所以查了一些资料,总结出下面这篇文章,一共包含四种常见的分支管理策略,分享给大家。Git flow在这种模式下,主要维护了两类分支...

gitlab简单搭建与应用

一、gitlab1、简介GitLab是利用Ruby on Rails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。与Github类似,GitLab能够浏览源代码,管理缺陷和注释。可以管理团队对仓库的访问,它非常易于浏览提交过的版本并提供一个文件历...

深度解析!AI智能体在To B领域应用,汽车售后服务落地全攻略

在汽车售后服务领域,AI智能体的应用正带来一场效率和专业度的革命。本文深度解析了一个AI智能体在To B领域的实际应用案例,介绍了AI智能体如何通过提升服务顾问和维修技师的专业度及维修效率,优化汽车售后服务流程。上周我分享了AI智能体+AI小程序To C的AI应用场景《1000%增长!我仅用一个小时...

再来一波黑科技工具,低调使用

静读天下静读天下是一个特别优秀的电子书阅读器。它上面有多个在线书库,像古登堡计划,很多种优秀的书杂志,都可以下载来阅读。它还能智能识别章节功能,还支持外置的语音阅读功能。它支持多种文本格式,比如说txt,pdf,epub,mobi等等。为了便于阅读它还有10 种配色方式,还有夜间模式。不过免费版有广...

uni-app基于vue开发小程序与标准vue开发新增点

1、路由跳转传参uni.navigateTo({ url: `/pages/transition/spreadTextAction?t=${this.options.t}&rt=${this.options.rt}&l=${this.options.l}`});uni.navigateBack({...