在上下文管理器中装饰任何python函数

问题描述 投票:3回答:2

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

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

以导致:

hi
olleh
bye

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

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

python python-decorators
2个回答
2
投票

这是我的方法:

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
投票

如果你修改它可以添加一点:

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导入的
  • 如果你关心之后你在print中定义了globals()函数,而不是直接作为内置函数

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

print('hi')
reverse_decorator(print)('hello')
print('bye')
© www.soinside.com 2019 - 2024. All rights reserved.