【问题标题】:How to make new decorators available within a class without explicitly importing them?如何在不显式导入的情况下使新的装饰器在类中可用?
【发布时间】:2018-04-17 04:41:56
【问题描述】:

是否可以修改一个类以使某个方法装饰器可用,而不必显式导入它,也不必为其添加前缀(@something.some_decorator):

class SomeClass:

    @some_decorator
    def some_method(self):
        pass

我认为使用类装饰器是不可能的,因为应用得太晚了。似乎更有希望的选项是使用元类,但我不确定如何,我的猜测是我必须将 some_decorator 引入 SomeClass 的命名空间。

感谢@MartijnPieters 指出staticmethodclassmethod 是内置的。我原以为他们会成为type 机器的一部分。

需要明确的是,我没有任何明确的用例,我只是好奇这是否可能。

附录,现在问题已经得到解答。我不只是在本地导入或定义装饰器的最初原因是,我已经定义了一个装饰器,该装饰器只有在对象上初始化某个容器属性时才起作用,并且我正在寻找一种方法来强制执行它装饰器的可用性。我最终检查了该属性是否存在,以及是否没有在装饰器中对其进行初始化,这很可能是较小的邪恶。

【问题讨论】:

  • 感谢更新,问题现在比以前更清楚了。

标签: python python-3.x python-decorators metaclass


【解决方案1】:

是的,在 Python 3 中,您可以使用 metaclass __prepare__ hook。它应该返回一个映射,它构成了类主体的本地命名空间的基础:

def some_decorator(f):
    print(f'Decorating {f.__name__}')
    return f

class meta(type):
    @classmethod
    def __prepare__(mcls, name, bases, **kw):
        return {'some_decorator': some_decorator}

class SomeClass(metaclass=meta):
    @some_decorator
    def some_method(self):
        pass

运行上述产生

Decorating some_method

但是你不应该使用这个。正如Zen of Python 所说:显式优于隐式,在类中引入魔术名称​​很容易导致混淆和错误。导入元类与导入装饰器没有什么不同,您将一个名称替换为另一个名称。

类装饰器在创建类主体后仍然可以将其他装饰器应用于类上的方法。 @decorator 语法只是 name = decorator(decorated_object) 的语法糖,您以后总是可以使用 name = decorator(name) 或在类上下文中应用装饰器,如 cls.name = decorator(cls.name)。如果您需要选择应该应用哪些方法,您可以选择方法名称、方法上设置的属性或方法的文档字符串等条件。或者直接在方法上使用装饰器。

【讨论】:

  • 我刚刚进行了测试。准备好的命名空间 (prepare) 中的名称实际上在类中可用。所以,元类可以做到这一点。
  • @SamHartman:我也进行了测试,但现在看到我犯了一个错误。将重新测试。
  • 我建议编辑您的答案以关注元类的替代方案,并讨论元类的缺点/复杂性。我认为您提供的 from foo import decorator 等信息很有价值;即使你可以,你几乎从来没有真正想要为此做元类
  • 感谢你们!我真的很惊讶这实际上是可能的。我将对为什么有人可能想要这样做的问题添加一个解释,尽管我并不是说这值得权衡。
【解决方案2】:

尽我所能告诉元类可以做到这一点。您可能需要通过导入以某种方式获取元类可用的装饰器,然后您可以将它们包含在准备好的命名空间中:

class includewraps(type):
    def prepare(*args):
        from functools import wraps
        return {'wraps': wraps}


class haswraps (metaclass = includewraps):
    # wraps is available in this scope

【讨论】:

    【解决方案3】:

    编写一个装饰器,它接受一个字符串列表并在第一次调用函数之前导入它们。这样可以避免显式导入,直到需要它们之前的最后一刻。就像这里的所有其他答案一样,这可能表明您的代码应该被重组。

    from functools import wraps
    from importlib import import_module
    
    def deferred(*names):
        def decorator(f):
            # this will hold the fully decorated function
            final_f = None
    
            @wraps(f)
            def wrapper(*args, **kwargs):
                nonlocal final_f
    
                if final_f is None:
                    # start with the initial function
                    final_f = f
    
                    for name in names:
                        # assume the last . is the object to import
                        # import the module then get the object
                        mod, obj = name.rsplit('.', 1)
                        d = getattr(import_module(mod), obj)
                        # decorate the function and keep going
                        final_f = d(final_f)
    
                return final_f(*args, **kwargs)
    
            return wrapper
    
        return decorator
    
    # for demonstration purposes, decorate with a function defined after this
    # assumes this file is called "example.py"
    @deferred('example.double')
    def add(x, y):
        return x + y
    
    def double(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            return 2 * f(*args, **kwargs)
    
        return wrapper
    
    if __name__ == '__main__':
        print(add(3, 6))  # 18
    

    deferred 的参数应该是 'path.to.module.decorator 形式的字符串。导入path.to.module,然后从模块中检索decorator。每个装饰器都用于包装函数。该函数存储在nonlocal 中,因此这种导入和装饰只需要在第一次调用该函数时进行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-08
      • 2019-03-25
      • 1970-01-01
      • 2015-05-20
      • 1970-01-01
      • 1970-01-01
      • 2021-12-05
      • 2019-12-04
      相关资源
      最近更新 更多