类方法修饰器



我正在尝试复制内置property类/装饰器的功能;我想要的一个非常基本的例子是:

# If a condition is met, run the first function, else, the second.
@godspeed()
def test():
print(1, 2, 3, 4)
@test.else_()
def test():
print(5, 6, 7, 8)

这是我到目前为止所拥有的:

import inspect
class godspeed_class():
def __init__(
self,
func,
args,
kwargs,
value,
):
self.func = func
self.args = args
self.kwargs = kwargs
self.value = value
def __call__(self):
if self.value:
self.func(*self.args, **self.kwargs)
else:
self.else_func(*self.else_args, **self.else_kwargs)
def else_(self, *args, **kwargs):
def wrapper(func):
self.else_func = func
self.else_args = args
self.else_kwargs = kwargs
return wrapper
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
_ = godspeed_class(func, args, kwargs, value)
inspect.stack(1)[1][0].f_globals[func.__name__] = _
return wrapper

我已经知道如何实现条件解析,但是我在类中的else_装饰器下存储函数时遇到问题,以便在不满足条件时可以调用它。

此外,尽管将新类直接注入全局命名空间,但当我运行print(test)时,它告诉我它是一个NoneType对象。

注意:代码已更新;但是,它仍然给我"NoneType对象"错误。

您需要更改两个wrapper函数以返回可调用对象,可能是类的实例。 否则,您将None作为方法的值,因为装饰器语法会将返回值分配给修饰函数的名称,这意味着即使您的inspect黑客有效, 它将被覆盖。

我建议:

class godspeed_class():
...                       # __init__ and __call__ can remain the same
def else_(self, *args, **kwargs):
def wrapper(func):
self.else_func = func
self.else_args = args
self.else_kwargs = kwargs
return self                       # add return here
return wrapper
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
return godspeed_class(func, args, kwargs, value) # and here (rather than inspect stuff)
return wrapper

这将使用顶级test函数为您的示例完成工作。如果希望能够修饰方法,还需要向类添加__get__方法以添加绑定行为(否则不会将self参数传递给包装的方法)。

在那里使用wrapper作为名称有点误导,因为内部函数是这里使用的实际装饰器(顶级godspeed函数和else_方法是装饰器工厂)。通常,您使用wrapper作为装饰器返回的函数的名称(但您正在使用您的类)。

我还要指出,您将函数的参数传递给装饰器工厂,而不是__call__接受它传递给相关函数的参数,这有点奇怪。对于留下可调用对象(而不是像property这样的工作方式不同)的装饰器来说,大幅改变函数的调用约定有点不寻常,因为如果函数签名不再具有代表性,调用者可能最终很难知道他们应该传入哪些参数。

装饰器并不神奇。基本上,@decorator语法只是句法糖,所以这个:

@mydecorator
def func(): 
pass

只是一个方便的快捷方式

def func():
pass
func = mydecorator(func)

IOW,一个"装饰器"是一个可调用的对象,它接受一个可调用的输入并返回一个可调用的对象(嗯,它应该至少返回一个可调用的 - 你实际上可以返回任何东西,但随后你会打破每个人的期望)。

大多数情况下,装饰器被编写为一个简单的函数,在装饰函数上返回闭包:

def trace(func):
def wrapper(*args, **kw):
result = func(*args, **kw)
print("{}({}, {}) => {}". format(func, args, kw, result))
return result
return wrapper

@trace
def foo(x):
return 42 * x

但是(因为闭包是穷人的类,而类是穷人的闭包)你也可以把它实现为一个可调用的类,在这种情况下,初始值设定项将接收装饰的 func,而该函数又将被实例替换:

class trace(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kw):
result = self.func(*args, **kw)
print("{}({}, {}) => {}". format(self.func, args, kw, result))
return result

@trace
def foo(x):
return 42 * x

然后你有"参数化"的装饰器 - 可以接受参数的装饰器。在这种情况下,您需要两个级别的间接寻址,顶层(用作装饰器的那个)返回实际的装饰器(接收函数的那个),即:

def trace(out):
def really_trace(func):
def wrapper(*args, **kw):
result = func(*args, **kw)
out.write("{}({}, {}) => {}n". format(func, args, kw, result))
return result
return wrapper
return really_trace
@trace(sys.stderr)
def foo(x):
return 42 * x

我将基于类的实现作为练习留给读者;-)

现在在您的情况下,test最终被None的事实很简单,因为您的包装器函数忘记按原样返回godspeed_class实例(而是弄乱函数的f_globals,正如您所注意到的,它没有按预期工作)。

由于您没有清楚地解释您在这里要实现的目标("类似于property的东西"不是一个合适的规范),因此很难提供有效的解决方案,但作为起点,您可能希望修复您的godspeedfunc 以按预期运行:

def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
return godspeed_class(func, args, kwargs, value)
return wrapper

最新更新