如何在不显式导入的情况下使新的装饰器在类中可用?

Posted

技术标签:

【中文标题】如何在不显式导入的情况下使新的装饰器在类中可用?【英文标题】: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 机器的一部分。

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

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

【问题讨论】:

感谢更新,问题现在比以前更清楚了。 【参考方案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 中,因此这种导入和装饰只需要在第一次调用该函数时进行。

【讨论】:

以上是关于如何在不显式导入的情况下使新的装饰器在类中可用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不传递数组的情况下使数组可访问到类中?

Qt - 如何在不显式实现线程的情况下同时运行两个插槽

如何在不显式编译的情况下让 Visual Studio 错误检查我的代码(显示曲线)?

python装饰器在类中的实现

数组适配器中的 get count() 如何在不显式返回数组大小的情况下返回值?

使用 Excel 是不是可以在不显式生成表格数据但仅使用公式的情况下绘制图表?