装饰器 class 并且缺少必需的位置参数
Decorator class and missing required positional arguments
我在使用包装器时遇到问题 class,并且无法弄清楚我做错了什么。
我如何才能让包装器与带有 'self' 参数的任何 class 函数一起工作?
这是针对 Python 3.7.3 的。
问题是我记得包装器以前工作过,但似乎发生了一些变化......也许我现在做错了我以前没有做的事情。
class SomeWrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# this fails because self is not passed
# ERROR: __init__() missing 1 required positional argument: 'self'
func_ret = self.func(*args, **kwargs)
# this is also wrong, because that's the wrong "self"
# ERROR: 'SomeWrapper' object has no attribute 'some_func'
# func_ret = self.func(self, *args, **kwargs)
return func_ret
class SomeClass:
SOME_VAL = False
def __init__(self):
self.some_func()
print("Success")
@SomeWrapper
def some_func(self):
self.SOME_VAL = True
def print_val(self):
print(self.SOME_VAL)
SomeClass().print_val()
所以,在 python 3 中,当方法声明作为方法工作时,当它们只是在 class 体内定义为函数时,发生的事情是语言使用“描述符协议”。
简单来说,一个普通的方法只是一个函数,直到它从一个实例中检索出来:因为函数有一个__get__
方法,所以它们被识别为描述符,__get__
方法负责 return 一个“部分函数”,即“绑定方法”,并将在调用时插入 self
参数。如果没有 __get__
方法,SomeWrapper
的实例在从实例中检索时没有关于该实例的信息。
总之,如果要对方法使用class-based装饰器,不仅要写__call__
,还要写__get__
方法。这应该足够了:
from copy import copy
class SomeWrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
func_ret = self.func(self.instance, *args, **kwargs)
return func_ret
def __get__(self, instance, owner):
# self here is the instance of "somewrapper"
# and "instance" is the instance of the class where
# the decorated method is.
if instance is None:
return self
bound_callable = copy(self)
bound_callable.instance = instance
return self
除了复制装饰器实例,这也行得通:
from functools import partial
class SomeWrapper:
...
def __call__(self, instance, *args, **kw):
...
func_ret = self.func(instance, *args, **kw)
...
return func_ret
def __get__(self, instance, owner):
...
return partial(self, instance)
“部分”和 self 的副本都是可调用的,它们“知道”它们来自哪些实例“__got__
”。
只需在装饰器实例中设置 self.instance
属性并 returning self
也可以,但仅限于一次使用该方法的单个实例。在具有某种程度的并行性的程序中,或者即使代码会检索一个方法来延迟调用它(例如将它用于回调),它也会以一种引人注目且难以调试的方式失败,因为该方法会收到 另一个实例在它的“self”参数中。
我在使用包装器时遇到问题 class,并且无法弄清楚我做错了什么。 我如何才能让包装器与带有 'self' 参数的任何 class 函数一起工作?
这是针对 Python 3.7.3 的。 问题是我记得包装器以前工作过,但似乎发生了一些变化......也许我现在做错了我以前没有做的事情。
class SomeWrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# this fails because self is not passed
# ERROR: __init__() missing 1 required positional argument: 'self'
func_ret = self.func(*args, **kwargs)
# this is also wrong, because that's the wrong "self"
# ERROR: 'SomeWrapper' object has no attribute 'some_func'
# func_ret = self.func(self, *args, **kwargs)
return func_ret
class SomeClass:
SOME_VAL = False
def __init__(self):
self.some_func()
print("Success")
@SomeWrapper
def some_func(self):
self.SOME_VAL = True
def print_val(self):
print(self.SOME_VAL)
SomeClass().print_val()
所以,在 python 3 中,当方法声明作为方法工作时,当它们只是在 class 体内定义为函数时,发生的事情是语言使用“描述符协议”。
简单来说,一个普通的方法只是一个函数,直到它从一个实例中检索出来:因为函数有一个__get__
方法,所以它们被识别为描述符,__get__
方法负责 return 一个“部分函数”,即“绑定方法”,并将在调用时插入 self
参数。如果没有 __get__
方法,SomeWrapper
的实例在从实例中检索时没有关于该实例的信息。
总之,如果要对方法使用class-based装饰器,不仅要写__call__
,还要写__get__
方法。这应该足够了:
from copy import copy
class SomeWrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
func_ret = self.func(self.instance, *args, **kwargs)
return func_ret
def __get__(self, instance, owner):
# self here is the instance of "somewrapper"
# and "instance" is the instance of the class where
# the decorated method is.
if instance is None:
return self
bound_callable = copy(self)
bound_callable.instance = instance
return self
除了复制装饰器实例,这也行得通:
from functools import partial
class SomeWrapper:
...
def __call__(self, instance, *args, **kw):
...
func_ret = self.func(instance, *args, **kw)
...
return func_ret
def __get__(self, instance, owner):
...
return partial(self, instance)
“部分”和 self 的副本都是可调用的,它们“知道”它们来自哪些实例“__got__
”。
只需在装饰器实例中设置 self.instance
属性并 returning self
也可以,但仅限于一次使用该方法的单个实例。在具有某种程度的并行性的程序中,或者即使代码会检索一个方法来延迟调用它(例如将它用于回调),它也会以一种引人注目且难以调试的方式失败,因为该方法会收到 另一个实例在它的“self”参数中。