4

我想创建装饰器来显示哪些参数被传递给函数和方法。我已经编写了函数的代码,但是方法让我很头疼。

这是按预期工作的函数装饰器:

from functools import update_wrapper


class _PrintingArguments:
    def __init__(self, function, default_comment, comment_variable):
        self.function = function
        self.comment_variable = comment_variable
        self.default_comment = default_comment
        update_wrapper(wrapped=function, wrapper=self)

    def __call__(self, *args, **kwargs):
        comment = kwargs.pop(self.comment_variable, self.default_comment)
        params_str = [repr(arg) for arg in args] + ["{}={}".format(k, repr(v)) for k, v in kwargs.items()]
        function_call_log = "{}({})".format(self.function.__name__, ", ".join(params_str))
        print("Function execution - '{}'\n\t{}".format(comment, function_call_log))
        function_return = self.function(*args, **kwargs)
        print("\tFunction executed\n")
        return function_return


def function_log(_function=None, default_comment="No comment.", comment_variable="comment"):
    if _function is None:
        def decorator(func):
            return _PrintingArguments(function=func, default_comment=default_comment, comment_variable=comment_variable)
        return decorator
    else:
        return _PrintingArguments(function=_function, default_comment=default_comment, comment_variable=comment_variable)

# example use:
@function_log
def a(*args, **kwargs):
    pass


@function_log(default_comment="Hello World!", comment_variable="comment2")
def b(*args, **kwargs):
    pass


a(0, x=1, y=2)
a(0, x=1, y=2, comment="Custom comment!")

b("a", "b", "c", asd="something")
b("a", "b", "c", asd="something", comment2="Custom comment for b!")

代码执行的输出:

Function execution - 'No comment.'
    a(0, y=2, x=1)
    Function executed

Function execution - 'Custom comment!'
    a(0, y=2, x=1)
    Function executed

Function execution - 'Hello World!'
    b('a', 'b', 'c', asd='something')
    Function executed

Function execution - 'Custom comment for b!'
    b('a', 'b', 'c', asd='something')
    Function executed



我已经为方法尝试了完全相同的装饰器:

class A:
    def __init__(self):
        pass

    @function_log
    def method1(self, *args, **kwargs):
        print("\tself = {}".format(self))

    @function_log(default_comment="Something", comment_variable="comment2")
    def method2(self, *args, **kwargs):
        print("\tself = {}".format(self))

a_obj = A()

a_obj.method1(0, 1, p1="abc", p2="xyz")
a_obj.method1(0, 1, p1="abc", p2="xyz", comment="My comment")

a_obj.method2("a", "b", p1="abc", p2="xyz")
a_obj.method2("a", "b", p1="abc", p2="xyz", comment="My comment 2")

输出是:

Function execution - 'No comment.'
    method1(0, 1, p2='xyz', p1='abc')
    self = 0
    Function executed

Function execution - 'My comment'
    method1(0, 1, p2='xyz', p1='abc')
    self = 0
    Function executed

Function execution - 'Something'
    method2('a', 'b', p2='xyz', p1='abc')
    self = a
    Function executed

Function execution - 'Something'
    method2('a', 'b', comment='My comment 2', p2='xyz', p1='abc')
    self = a
    Function executed

我的装饰器没有将参数“self”传递给该方法。
我想编写第二个装饰器“method_log”,它的工作原理与“function_log”非常相似。对于代码:

class A:
    def __init__(self):
        pass

    @method_log
    def method1(self, *args, **kwargs):
        print("\tself = {}".format(self))

    @fmethod_log(default_comment="Something", comment_variable="comment2")
    def method2(self, *args, **kwargs):
        print("\tself = {}".format(self))

a_obj = A()

a_obj.method1(0, 1, p1="abc", p2="xyz")
a_obj.method1(0, 1, p1="abc", p2="xyz", comment="My comment")

a_obj.method2("a", "b", p1="abc", p2="xyz")
a_obj.method2("a", "b", p1="abc", p2="xyz", comment="My comment 2")

我想要输出:

Method execution - 'No comment.'
    method1(<__main__.A instance at ...>, 0, 1, p2='xyz', p1='abc')
    self = <__main__.A instance at ...> #
    Function executed

Method execution - 'My comment'
    method1(<__main__.A instance at ...>, 0, 1, p2='xyz', p1='abc')
    self = <__main__.A instance at ...>
    Function executed

Method execution - 'Something'
    method2(<__main__.A instance at ...>, 'a', 'b', p2='xyz', p1='abc')
    self = <__main__.A instance at ...>
    Function executed

Method execution - 'Something'
    method2(<__main__.A instance at ...>, 'a', 'b', comment='My comment 2', p2='xyz', p1='abc')
    self = <__main__.A instance at ...>
    Function executed
4

2 回答 2

2

如果你想_PrintingArguments以与普通函数相同的方式绑定,这实际上是可能的,你只需要自己实现描述符协议以匹配内置函数的行为方式。方便的是,Python提供了types.MethodType,它可用于从任何可调用创建绑定方法,给定一个要绑定的实例,因此我们使用它来实现我们的描述符__get__

import types

class _PrintingArguments:
    # __init__ and __call__ unchanged

    def __get__(self, instance, owner):
        if instance is None:
            return self  # Accessed from class, return unchanged
        return types.MethodType(self, instance)  # Accessed from instance, bind to instance

这在 Python 3 上可以正常工作(在线试用!)。在 Python 2 上它更简单(因为存在未绑定的方法,所以types.MethodType可以无条件地调用):

import types

class _PrintingArguments(object):  # Explicit inheritance from object needed for new-style class on Py2
    # __init__ and __call__ unchanged

    def __get__(self, instance, owner):
        return types.MethodType(self, instance, owner)  # Also pass owner

在线尝试!

为了获得更好的性能(仅在 Python 2 上),您可以改为:

class _PrintingArguments(object):  # Explicit inheritance from object needed for new-style class on Py2
    # __init__ and __call__ unchanged

# Defined outside class, immediately after dedent
_PrintingArguments.__get__ = types.MethodType(types.MethodType, None, _PrintingArguments)

它将实现转移__get__到 C 层,方法是在types.MethodType自身之外创建一个未绑定的方法,从而消除每次调用的字节码解释器开销。

于 2019-09-05T15:34:19.777 回答
2

由于类在 Python 中的工作方式,它不适用于您当前的设计。

当一个类被实例化时,它上面的函数被绑定到实例上——它们成为绑定的方法,所以它self会被自动传递。

你可以看到它发生:

class A:
    def method1(self):
        pass

>>> A.method1
<function A.method1 at 0x7f303298ef28>
>>> a_instance = A()
>>> a_instance.method1
<bound method A.method1 of <__main__.A object at 0x7f303a36c518>>

当 A 被实例化时,method1它会神奇地从 a function转换为 a bound method

您的装饰器替换了method1- 而不是真正的函数,它现在是_PrintingArguments. 将函数转换为绑定方法的魔法不适用于随机对象,即使它们定义__call__为像函数一样。(但是可以应用这种魔法,如果您的类实现了 Descriptor 协议,请参阅 ShadowRanger 的答案!)。

class Decorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)


class A:
    @Decorator
    def method1(self):
        pass

>>> A.method1
<__main__.Decorator object at 0x7f303a36cbe0>
>>> a_instance = A()
>>> a_instance.method1
<__main__.Decorator object at 0x7f303a36cbe0>

没有魔法。method1在 A 的实例上不是绑定方法,它只是一个带有__call__方法的随机对象,不会 self自动传递。

如果你想装饰方法,你必须用另一个真正的函数替换被装饰的函数,一个任意的对象是__call__不行的。

您可以调整您当前的代码以返回一个真正的函数:

import functools

class _PrintingArguments:
    def __init__(self, default_comment, comment_variable):
        self.comment_variable = comment_variable
        self.default_comment = default_comment

    def __call__(self, function):
        @functools.wraps(function)
        def decorated(*args, **kwargs):
            comment = kwargs.pop(self.comment_variable, self.default_comment)
            params_str = [repr(arg) for arg in args] + ["{}={}".format(k, repr(v)) for k, v in kwargs.items()]
            function_call_log = "{}({})".format(function.__name__, ", ".join(params_str))
            print("Function execution - '{}'\n\t{}".format(comment, function_call_log))
            function_return = function(*args, **kwargs)
            print("\tFunction executed\n")
            return function_return
        return decorated

def function_log(_function=None, default_comment="No comment.", comment_variable="comment"):
    decorator = _PrintingArguments(
        default_comment=default_comment,
        comment_variable=comment_variable,
    )
    if _function is None:
        return decorator
    else:
        return decorator(_function)
于 2019-09-05T14:40:30.483 回答