如何在不显式导入的情况下使新的装饰器在类中可用?
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 指出staticmethod
和classmethod
是内置的。我原以为他们会成为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
中,因此这种导入和装饰只需要在第一次调用该函数时进行。
【讨论】:
以上是关于如何在不显式导入的情况下使新的装饰器在类中可用?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不显式编译的情况下让 Visual Studio 错误检查我的代码(显示曲线)?