假设以下结构:
class SetupTestParam(object):
def setup_method(self, method):
self.foo = bar()
@pytest.fixture
def some_fixture():
self.baz = 'foobar'
我使用SetupTestParam
作为测试类的父类。
class TestSomething(SetupTestParam):
def test_a_lot(self, some_fixture):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
... # not repetative code here
assert spam == 'something cool'
现在,编写测试会重复(使用语句),我想编写一个装饰器来减少代码行数。但是pytest和函数签名存在问题。
我发现library应该会有所帮助,但我无法让它发挥作用。
我在classmethod
课程中提出了SetupTestParam
。
@classmethod
@decorator.decorator
def this_is_decorator(cls, f):
def wrapper(self, *args, **kw):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return f(self, *args)
return wrapper
装饰test_a_lot
方法后,收到错误TypeError: transaction_decorator() takes exactly 1 argument (2 given)
有人可以解释我,我做错了什么? (我假设测试方法中的self
存在问题?)
答案 0 :(得分:2)
链接装饰器并不是最简单的事情。一种解决方案可能是分离两个装饰器。保持classmethod
但移动decorator.decorator
到最后:
@classmethod
def this_is_decorator(cls, f):
def wrapper(self, *args, **kw):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return f(self, *args)
return decorator.decorator(wrapper, f)
也许这适合你。
答案 1 :(得分:0)
这是定义此方法时按时间顺序发生的事情。
this_is_decorator
已创建(未调用)。decorator.decorator(this_is_decorator)
被调用。这将返回一个新函数,该函数变为this_is_decorator
且具有相同的用法。classmethod(this_is_decorator)
被调用,其结果是接受(cls, f)
并返回wrapper
的类方法。this_is_decorator
的调用将返回wrapper
。但考虑到this_is_decorator
是一种类方法,我不清楚这是你想要的。我猜你可能想要更像这样的东西:
from decorator import decorator
@decorator
def mydecorator(f):
def wrapper(cls, *args, **kw):
# ... logging, reporting, caching, whatever
return f(*args, **kw)
return wrapper
class MyClass(object):
@classmethod
@mydecorator
def myclsmethod(a, b, c):
# no cls or self value accepted here; this is a function not a method
# ...
这里你的装饰器是在你的课外定义的,因为它将普通的函数改为classmethod
(因为你可能想在其他地方使用它)。这里的执行顺序是:
mydecorator
已定义,未调用。decorator(mydecorator)
被调用,结果变为 mydecorator
。MyClass
开始。myclsmethod
已创建。这是一个普通的功能,而不是一种方法。 VM中存在差异,因此您无需为方法明确提供cls
或self
个参数。myclsmethod
传递给mydecorator
(之前已经过装饰),结果(wrapper
)仍然是函数而不是方法。< / LI>
mydecorator
的结果传递给classmethod
,后者返回绑定到MyClass.myclsmethod
的实际类方法。MyClass
完成的定义。MyClass.myclsmethod(a, b, c)
时,wrapper
会执行,然后调用原始myclsmethod(a, b, c)
函数(它知道为f
)而不提供cls
参数。由于你还需要精确地保存参数列表,所以即使参数的名称保存在装饰函数中,除了有一个额外的初始参数cls
,你就可以实现{{1这样:
mydecorator
这有点难看,但这是我知道根据数据动态设置函数的参数列表的唯一方法。如果返回from decorator import decorator
from inspect import getargspec
@decorator
def mydecorator(func):
result = [None] # necessary so exec can "return" objects
namespace = {'f': func, 'result': result}
source = []
add = lambda indent, line: source.append(' ' * indent + line) # shorthand
arglist = ', '.join(getargspec(func).args) # this does not cover keyword or default args
add(0, 'def wrapper(cls, %s):' % (arglist,))
add(2, 'return f(%s)' % (arglist,))
add(0, 'result[0] = wrapper') # this is how to "return" something from exec
exec '\n'.join(source) in namespace
return result[0] # this is wrapper
是正常的,则可以使用lambda
而不是eval
,这样就无需使用可写入的数组,但在其他方面也是如此。
答案 2 :(得分:0)
经过一些调整并意识到我需要将参数传递给装饰器后,我选择将其作为类编写:
class ThisIsDecorator(object):
def __init__(self, param):
self.param = param # Parameter may vary with the function being decorated
def __call__(self, fn):
wraps(fn) # [1]
def wrapper(fn, fn_self, *args): # [2] fn_self refers to original self param from function fn (test_a_lot) [2]
with fn_self.baz as fn_self.magic: # I pass magic to fn_self to make magic accesible in function fn (test_a_lot)
with fn_self.magic.fooz as more_magic:
blah = self.param.much_more_magic() # repetative bleh
return fn(fn_self, *args)
return decorator.decorator(wrapper, fn)
[1]我使用wraps
原创fn
__name__
,__module__
和__doc__
。
[2]传递给wrapper
的参数为self = <function test_a_lot at 0x24820c8> args = (<TestSomething object at 0x29c77d0>, None, None, None, None), kw = {}
,因此我将args[0]
作为fn_self
取出。
原始版本(不传递参数):
@classmethod
def this_is_decorator(cls, fn):
@wraps(fn)
def wrapper(fn, fn_self, *args):
with fn_self.baz as fn_self.magic:
with fn_self.magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return fn(fn_self, *args)
return decorator.decorator(wrapper,fn)
感谢Mike Muller指出正确的方向。