使用 class 同时作为装饰器和装饰器工厂进行操作
Using a class to operate both as decorator and decorator factory
考虑以下装饰器函数,它可以是 returns 装饰函数,也可以是参数化装饰器或 函数:
from functools import wraps, partial, update_wrapper
from inspect import signature
def wrapit(func=None, *, verb='calling'):
if func is None: # return a decoratOR
return partial(wrapit, verb=verb)
else: # return a decoratED
@wraps(func)
def _func(*args, **kwargs):
print(f'{verb} {func.__name__} with {args} and {kwargs}')
return func(*args, **kwargs)
return _func
演示:
>>> f = lambda x, y=1: x + y
>>> ff = wrapit(verb='launching')(f)
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> # but can also use it as a "decorator factory"
>>> @wrapit(verb='calling')
... def f(x, y=1):
... return x + y
...
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
class 表单可能如下所示:
class Wrapit:
def __init__(self, func, verb='calling'):
self.func, self.verb = func, verb
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
但是我们如何让 class 能够在函数形式具有的 "decorator factory" 模式下运行(由 if func is None: return partial...
我们如何将这个技巧集成到装饰器中 class?
正如评论中所建议的那样,您可以使用 __new__
方法执行此操作:
class Wrapit:
def __new__(cls, func=None, *, verb='calling'):
if func is None:
return partial(cls,verb=verb)
self = super().__new__(cls)
self.func, self.verb = func, verb
update_wrapper(self, func)
return self
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
每当您尝试实例化 class 时,都会调用 __new__
方法,并且该方法的 return 值将用作尝试实例化的结果——即使它不是 class!
的实例
我接受了@pppery 的回答,因为...这就是答案。我想在这里扩展答案,展示如何通过在父 class 中编写逻辑来获得更多的重用。这需要将@pppery 的逻辑分成 __new__
和 __init__
方法。
from functools import update_wrapper, partial
class Decorator:
def __new__(cls, func=None, **kwargs):
if func is None:
self = partial(cls, **kwargs)
else:
self = super().__new__(cls)
return update_wrapper(self, func)
def __init__(self, func=None, **kwargs):
self.func = func
for attr_name, attr_val in kwargs.items():
setattr(self, attr_name, attr_val)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class Wrapit(Decorator):
def __new__(cls, func=None, *, verb='calling'):
return super().__new__(cls, func, verb=verb)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return super().__call__(*args, **kwargs)
class AnotherOne(Decorator):
def __new__(cls, func=None, *, postproc=lambda x: x):
return super().__new__(cls, func, postproc=postproc)
def __call__(self, *args, **kwargs):
return self.postproc(super().__call__(*args, **kwargs))
演示:
>>> f = lambda x, y=1: x + y
>>>
>>> ff = Wrapit(f, verb='launching')
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> fff = AnotherOne(postproc=str)(f) # doing it the decorator factory way
>>> assert fff(10) == str(11)
>>> assert signature(fff) == signature(f)
考虑以下装饰器函数,它可以是 returns 装饰函数,也可以是参数化装饰器或 函数:
from functools import wraps, partial, update_wrapper
from inspect import signature
def wrapit(func=None, *, verb='calling'):
if func is None: # return a decoratOR
return partial(wrapit, verb=verb)
else: # return a decoratED
@wraps(func)
def _func(*args, **kwargs):
print(f'{verb} {func.__name__} with {args} and {kwargs}')
return func(*args, **kwargs)
return _func
演示:
>>> f = lambda x, y=1: x + y
>>> ff = wrapit(verb='launching')(f)
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> # but can also use it as a "decorator factory"
>>> @wrapit(verb='calling')
... def f(x, y=1):
... return x + y
...
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
class 表单可能如下所示:
class Wrapit:
def __init__(self, func, verb='calling'):
self.func, self.verb = func, verb
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
但是我们如何让 class 能够在函数形式具有的 "decorator factory" 模式下运行(由 if func is None: return partial...
我们如何将这个技巧集成到装饰器中 class?
正如评论中所建议的那样,您可以使用 __new__
方法执行此操作:
class Wrapit:
def __new__(cls, func=None, *, verb='calling'):
if func is None:
return partial(cls,verb=verb)
self = super().__new__(cls)
self.func, self.verb = func, verb
update_wrapper(self, func)
return self
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
每当您尝试实例化 class 时,都会调用 __new__
方法,并且该方法的 return 值将用作尝试实例化的结果——即使它不是 class!
我接受了@pppery 的回答,因为...这就是答案。我想在这里扩展答案,展示如何通过在父 class 中编写逻辑来获得更多的重用。这需要将@pppery 的逻辑分成 __new__
和 __init__
方法。
from functools import update_wrapper, partial
class Decorator:
def __new__(cls, func=None, **kwargs):
if func is None:
self = partial(cls, **kwargs)
else:
self = super().__new__(cls)
return update_wrapper(self, func)
def __init__(self, func=None, **kwargs):
self.func = func
for attr_name, attr_val in kwargs.items():
setattr(self, attr_name, attr_val)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class Wrapit(Decorator):
def __new__(cls, func=None, *, verb='calling'):
return super().__new__(cls, func, verb=verb)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return super().__call__(*args, **kwargs)
class AnotherOne(Decorator):
def __new__(cls, func=None, *, postproc=lambda x: x):
return super().__new__(cls, func, postproc=postproc)
def __call__(self, *args, **kwargs):
return self.postproc(super().__call__(*args, **kwargs))
演示:
>>> f = lambda x, y=1: x + y
>>>
>>> ff = Wrapit(f, verb='launching')
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> fff = AnotherOne(postproc=str)(f) # doing it the decorator factory way
>>> assert fff(10) == str(11)
>>> assert signature(fff) == signature(f)