在 Python 中的方法修饰期间传递 'self' 参数
Passing 'self' parameter during methods decorating in Python
我想创建装饰器来显示传递给函数和方法的参数。函数的代码我已经写好了,但是方法让我很头疼
这是按预期工作的函数装饰器:
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
它不适用于您当前的设计,因为 classes 在 Python 中的工作方式。
当 class 被实例化时,其上的函数将绑定到该实例 -
它们成为绑定方法,因此 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
神奇地从一个
function
变成 bound method
.
您的装饰器替换了 method1
- 而不是真正的函数,
它现在是 _PrintingArguments
的一个实例。魔法
将函数转换为绑定方法的方法不适用于随机
对象,即使它们定义了 __call__
以便它们表现得像一个函数。 (但是如果你的 class 实现了 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>
没有魔法。 A 实例上的 method1
不是绑定方法,
它只是一个带有 __call__
方法的随机对象,不会有
self
自动通过。
如果你想装饰方法你必须替换装饰函数
对于另一个实函数,具有 __call__
的任意对象将不会执行。
您可以将当前代码改编为 return 一个真正的函数:
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)
如果你想让_PrintingArguments
像普通函数一样绑定,这实际上是可行的,你只需要实现the descriptor protocol yourself to match the way built-in functions behave. Conveniently, Python provides types.MethodType
,它可以用来从[创建一个绑定方法=32=]any 可调用,给定一个要绑定的实例,因此我们使用它来实现描述符的 __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 (Try it online!) 上的预期一样有效。在Python2上就更简单了(因为存在未绑定的方法,所以可以无条件调用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)
通过从 types.MethodType
自身中创建一个未绑定的方法,将 __get__
的实现移至 C 层,从而从每次调用中消除字节码解释器开销。
我想创建装饰器来显示传递给函数和方法的参数。函数的代码我已经写好了,但是方法让我很头疼
这是按预期工作的函数装饰器:
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
它不适用于您当前的设计,因为 classes 在 Python 中的工作方式。
当 class 被实例化时,其上的函数将绑定到该实例 -
它们成为绑定方法,因此 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
神奇地从一个
function
变成 bound method
.
您的装饰器替换了 method1
- 而不是真正的函数,
它现在是 _PrintingArguments
的一个实例。魔法
将函数转换为绑定方法的方法不适用于随机
对象,即使它们定义了 __call__
以便它们表现得像一个函数。 (但是如果你的 class 实现了 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>
没有魔法。 A 实例上的 method1
不是绑定方法,
它只是一个带有 __call__
方法的随机对象,不会有
self
自动通过。
如果你想装饰方法你必须替换装饰函数
对于另一个实函数,具有 __call__
的任意对象将不会执行。
您可以将当前代码改编为 return 一个真正的函数:
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)
如果你想让_PrintingArguments
像普通函数一样绑定,这实际上是可行的,你只需要实现the descriptor protocol yourself to match the way built-in functions behave. Conveniently, Python provides types.MethodType
,它可以用来从[创建一个绑定方法=32=]any 可调用,给定一个要绑定的实例,因此我们使用它来实现描述符的 __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 (Try it online!) 上的预期一样有效。在Python2上就更简单了(因为存在未绑定的方法,所以可以无条件调用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)
通过从 types.MethodType
自身中创建一个未绑定的方法,将 __get__
的实现移至 C 层,从而从每次调用中消除字节码解释器开销。