3

我正在创建一个功能标记系统,以启用或禁用基于标签的功能:

def do_nothing(*args, **kwargs): pass

class Selector(set):
    def tag(self, tag):
        def decorator(func):
            if tag in self:
                return func
            else:
                return do_nothing
        return decorator

selector = Selector(['a'])

@selector.tag('a')
def foo1():
    print "I am called"

@selector.tag('b')
def foo2():
    print "I am not called"

@selector.tag('a')
@selector.tag('b')
def foo3():
    print "I want to be called, but I won't be"

foo1() #Prints "I am called"
foo2() #Does nothing
foo3() #Does nothing, even though it is tagged with 'a'

我的问题是关于最后一个函数 foo3。我明白为什么它没有被调用。我想知道是否有办法让它在选择器中存在任何标签时被调用。理想情况下,该解决方案使得标签只检查一次,而不是每次调用函数时。

附注:我这样做是为了根据unittest单元测试中的环境变量选择要运行的测试。我的实际实现使用unittest.skip.

编辑:添加了装饰器返回。

4

2 回答 2

4

问题是,如果你装饰它两次,一次返回函数,一次什么也不返回。

foo3() -> @selector.tag('a') -> foo3()
foo3() -> @selector.tag('b') -> do_nothing

foo3() -> @selector.tag('b') -> do_nothing
do_nothing -> @selector.tag('a') -> do_nothing

这意味着,无论以何种顺序,您都将一无所获。您需要做的是在每个对象上保留一组标签,并立即检查整个设置。我们可以很好地做到这一点,而不会用函数属性污染命名空间:

class Selector(set):
    def tag(self, *tags):
        tags = set(tags)
        def decorator(func):
            if hasattr(func, "_tags"):
                func._tags.update(tags)
            else:
                func._tags = tags
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs) if self & func._tags else None
            wrapper._tags = func._tags
            return wrapper
        return decorator

这带来了一些好处 - 可以检查函数具有的所有标签,并且可以使用多个装饰器进行标记,或者在单个装饰器中提供多个标签。

@selector.tag('a')
@selector.tag('b')
def foo():
    ...


#Or, equivalently:
@selector.tag('a', 'b')
def foo():
    ...

的使用functools.wraps()还意味着该函数保持其原始“身份”(文档字符串、名称等)。

编辑:如果你想做一些包装消除:

    def decorator(func):
        if hasattr(func, "_tagged_function"):
            func = func._tagged_function
        if hasattr(func, "_tags"):
            func._tags.update(tags)
        else:
            func._tags = tags
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs) if self & func._tags else None
        wrapper._tagged_function = func
        wrapper._tags = func._tags
        return wrapper
于 2013-01-28T18:18:29.477 回答
1

这对你有用吗:

class Selector(set):
    def tag(self, tag_list):
        def decorator(func):
            if set(tag_list) & self:
                return func
            else:
                return do_nothing
        return decorator


@selector.tag(['a','b'])
def foo3():
    print "I want to be called, but I won't be"
于 2013-01-28T18:29:08.733 回答