装饰一个实例方法并从装饰器中调用它

时间:2012-12-10 09:31:10

标签: python decorator nose python-decorators

我正在使用nose test generators功能来运行具有不同上下文的相同测试。因为每次测试需要以下锅炉板:

class TestSample(TestBase):

    def test_sample(self):
        for context in contexts:
            yield self.check_sample, context

    def check_sample(self, context):
        """The real test logic is implemented here"""
        pass

我决定编写以下装饰器:

def with_contexts(contexts=None):        
    if contexts is None:
        contexts = ['twitter', 'linkedin', 'facebook']

    def decorator(f):
        @wraps(f)
        def wrapper(self, *args, **kwargs):
            for context in contexts:
                yield f, self, context # The line which causes the error
        return wrapper
    return decorator

装饰器按以下方式使用:

class TestSample(TestBase):  

    @with_contexts()
    def test_sample(self, context):
        """The real test logic is implemented here"""
        var1 = self.some_valid_attribute

执行测试时,会抛出错误,指定正在访问的属性不可用。但是,如果我将调用该方法的行更改为以下方法,则可以正常工作:

yield getattr(self, f.__name__), service

我知道上面的代码片段会创建一个绑定方法,就像第一个 self 手动传递给函数一样。但是,就我的理解而言,第一个片段应该也可以正常工作。如果有人能澄清这个问题,我将不胜感激。

问题的标题通常与装饰器中的实例方法相关,但我保留了特定于我的上下文的描述。

2 个答案:

答案 0 :(得分:3)

您可以使用functools.partial将包装的函数绑定到self,就像方法一样:

from functools import partial

def decorator(f):
    @wraps(f)
    def wrapper(self, *args, **kwargs):        
        for context in contexts:
            yield partial(f, self), context
    return wrapper

现在你正在屈服于偏见,当被称为yieldedvalue(context)时,会调用f(self, context)

答案 1 :(得分:0)

据我所知,有些事情并不合适。首先,你的装饰师就像

def with_contexts(contexts=None):        
    if contexts is None:
        contexts = ['twitter', 'linkedin', 'facebook']

    def decorator(f):
        @wraps(f)
        def wrapper(self, *args, **kwargs):
            for context in contexts:
                yield f, self, context # The line which causes the error
        return wrapper
    return decorator

但你像

一样使用它
@with_contexts
def test_sample(self, context):
    """The real test logic is implemented here"""
    var1 = self.some_valid_attribute

这是错误的:这会调用with_context(test_sample),但您需要with_context()(test_sample)

@with_contexts()
def test_sample(self, context):
    """The real test logic is implemented here"""
    var1 = self.some_valid_attribute

即使您没有提供contexts参数。

其次,你装饰了错误的函数:你的用法表明test函数为每个上下文产生check函数。您要包装的函数执行检查功能的工作,但您必须在测试功能之后命名它。

可以使用selfpartial应用于某个方法,就像Martijn写的那样,但它也可以像Python在幕后所做的那样完成:

method.__get__(self, None)

或者更好

method.__get__(self, type(self))
你可以达到同样的目的。 (也许你的原始版本也可以工作,产生要调用的函数和使用的参数。我不清楚这是它的工作方式。)