我正在尝试复制内置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
的东西"不是一个合适的规范),因此很难提供有效的解决方案,但作为起点,您可能希望修复您的godspeed
func 以按预期运行:
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
return godspeed_class(func, args, kwargs, value)
return wrapper