【问题标题】:Decorate any python function inside context manager在上下文管理器中装饰任何 python 函数
【发布时间】:2018-02-28 16:48:19
【问题描述】:

我想创建一个 python 上下文管理器,它将允许以下操作(使用 reverse_decorator 应用装饰函数,如果它是字符串,则第一个参数反转):

print('hi')
with MyFunctionDecorator('print', reverse_decorator):
    print('hello')
print('bye')

导致:

hi
olleh
bye

重点不是打印函数本身,而是编写这种上下文管理器,它可以装饰任何函数——本地的、全局的、内置的,来自任何模块。这在python中甚至可能吗?我该怎么做?

编辑:澄清一下,重点是不必更改 with 上下文中的代码。

【问题讨论】:

  • 您可以使用mock 模块中的某种形式的patch。我不知道它是否支持像 print 这样的内置补丁。
  • 好的,你应该可以通过修补 __builtins__.print 来做到这一点。不过,补丁仍然无法处理任何不可导入的内容。
  • 没有不简单的方法可以做到这一点。在某些情况下,根本不可能装饰/替换函数 - 例如,如果函数是在本地范围或闭包中定义的。

标签: python python-decorators


【解决方案1】:

这是我的方法:

from contextlib import contextmanager
from importlib import import_module

@contextmanager
def MyFunctionDecorator(func, decorator):
    if hasattr(func, '__self__'):
        owner = func.__self__
    elif hasattr(func, '__objclass__'):
        owner = func.__objclass__
    else:
        owner = import_module(func.__module__)
        qname = func.__qualname__
        while '.' in qname:
            parent, qname = qname.split('.', 1)
            owner = getattr(owner, parent)
    setattr(owner, func.__name__, decorator(func))
    yield
    setattr(owner, func.__name__, func)

# Example decorator, reverse all str arguments
def reverse_decorator(f):
    def wrapper(*args, **kwargs):
        newargs = []
        for arg in args:
            newargs.append(arg[::-1] if isinstance(arg, str) else arg)
        newkwargs = {}
        for karg, varg in kwargs.values():
            newkwargs[karg] = varg[::-1] if isinstance(varg, str) else varg
        return f(*newargs, **newkwargs)
    return wrapper

# Free functions
print('hi')
with MyFunctionDecorator(print, reverse_decorator):
    print('hello')
print('bye')

# Class for testing methods (does not work with builtins)
class MyClass(object):
    def __init__(self, objId):
        self.objId = objId
    def print(self, arg):
        print('Printing from object', self.objId, arg)

# Class level (only affects instances created within managed context)
# Note for decorator: first argument of decorated function is self here
with MyFunctionDecorator(MyClass.print, reverse_decorator):
    myObj = MyClass(1)
    myObj.print('hello')

# Instance level (only affects one instance)
myObj = MyClass(2)
myObj.print('hi')
with MyFunctionDecorator(myObj.print, reverse_decorator):
    myObj.print('hello')
myObj.print('bye')

输出:

hi
olleh
bye
Printing from object 1 olleh
Printing from object 2 hi
Printing from object 2 olleh
Printing from object 2 bye

这应该跨函数和其他模块等工作,因为它修改了模块或类的属性。类方法很复杂,因为一旦你创建了一个类的实例,它的属性就指向类中定义的函数在创建对象时,所以你必须在修改一个类的行为之间做出选择。特定实例或修改托管上下文中新实例的行为,如示例中所示。此外,尝试装饰 listdict 等内置类的方法也不起作用。

【讨论】:

  • 注意事项:1) 您假设函数的所有者可以通过 func.__name__ 访问该函数,因此您的代码会被 from builtins import print as pprint 之类的东西绊倒(尝试装饰 pprint 即可没有)。 2) 装饰器在整个程序中变得活跃,而不仅仅是在with 块内。 3) 如果修饰函数没有 qualname(即 qualname 包含 <locals> 或类似名称),则不起作用。
  • @Aran-Fey 是的,都是真的,感谢您的指出。我没有想到 1),我想也许您可以通过将函数名称作为字符串传递,然后也(或仅?)替换模块中的名称(__main__ 或它来自的任何地方)来解决它。 . 相当 hacky,虽然:S(我的意思是,甚至更多)。是的,2)在多线程上下文中很重要......我无法想象一个解决方法。我想 3) 对于 lambdas 和装饰函数来说是最引人注目的......再一次,也许可以通过基于字符串的 hacky 解决方案来规避。
  • 顺便说一句,我最初误读了这个问题,认为 OP 希望将装饰器应用于 每个 函数调用,而不仅仅是一个特定函数,我考虑了一个基于 @987654321 的解决方案@。对于单个函数来说,这是一种相当矫枉过正的方法,但我想它也可能是实现它的可能途径......
【解决方案2】:

稍微修改一下就可以了:

print('hi')
with MyFunctionDecorator(print, reverse_decorator) as print:
    print('hello')
print('bye')

以下是适用于本示例的定义*:

def reverse_decorator(func):
    def wrapper(*args, **kwargs):
        if len(args) == 1 and not kwargs and isinstance(args[0], str):
            return func(args[0][::-1])
        return func(*args, **kwargs)
    return wrapper

class MyFunctionDecorator:
    def __init__(self, func, decorator):
        self.func = func
        self.decorator = decorator

    def __enter__(self):
        """Return the decorated function"""
        return self.decorator(self.func)

    def __exit__(self, *args):
        """Reset the function in the global namespace"""
        globals()[self.func.__name__] = self.func

但是按照Python Zen 明确地执行它可能更容易:

print('hi')
print('hello'[::-1])
print('bye')

*此代码在许多情况下都不起作用,正如@AranFeythe comments 中指出的那样:

  • 内部函数
  • 如果你要装饰的函数是用import x from y as z导入的
  • 如果您关心之后您在globals() 中定义了一个print 函数,而不是直接作为内置函数

由于这更像是一个概念验证,是的,可以编写一个在这个例子中工作的装饰器,我不会尝试修复这些缺点。只需使用我上面给出的方式,或者只使用装饰器:

print('hi')
reverse_decorator(print)('hello')
print('bye')

【讨论】:

  • 请注意,这在函数内部不起作用。您将收到 UnboundLocalError: local variable 'print' referenced before assignment 错误。该代码还假设该函数是在全局范围内定义的,这可能不是真的 - print 是在 builtins 中定义的,因此这个上下文管理器具有将 print 添加到全局变量的副作用。最后,假设函数被注册为func.__name__,例如,如果函数被导入为from x import func as func_alias,则情况并非如此。
  • @Aran-Fey 是的,你是对的。不知道如何解决。但是话又说回来,这已经太老套了,无论如何我都不想在生产中使用它。
猜你喜欢
  • 2021-08-24
  • 2012-03-02
  • 1970-01-01
  • 2019-05-09
  • 1970-01-01
  • 2018-11-22
  • 2015-08-01
  • 1970-01-01
  • 2016-09-27
相关资源
最近更新 更多