如何在 class 中提供新装饰器而不显式导入它们?
How to make new decorators available within a class without explicitly importing them?
是否可以修改 class 以使某个方法装饰器可用,而不必显式导入它也不必为其添加前缀 (@something.some_decorator
):
class SomeClass:
@some_decorator
def some_method(self):
pass
我认为 class 装饰器不可能做到这一点,因为应用得太晚了。看起来更有希望的选项是使用元 class,但我不确定如何,我的猜测是我必须将 some_decorator
引入到 SomeClass
.[= 的命名空间中18=]
感谢@MartijnPieters 指出 staticmethod
和 classmethod
是内置的。我原以为它们会成为 type
机器的一部分。
明确地说,我没有任何明确的用例,我只是想知道这是否完全可行。
附录,现在问题已得到解答。我不局限于简单地在本地导入或定义装饰器的最初原因是,我定义了一个装饰器,该装饰器只有在对象上初始化了某个容器属性时才起作用,并且我正在寻找一种方法来强制执行此操作装饰器的可用性。我最终检查了该属性是否存在,如果不存在则在装饰器中对其进行初始化,这很可能是较小的问题。
是的,在Python3你可以使用metaclass __prepare__
hook。它应该是 return 一个映射,它构成了 class 主体的本地名称空间的基础:
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 所述:显式优于隐式,并且在您的 classes 中引入魔法名称很容易导致混淆和错误。导入 metaclass 与导入装饰器没有什么不同,你用一个名字替换了另一个名字。
class 装饰器仍然可以在 class 主体创建后将其他装饰器应用于 class 上的方法。 @decorator
语法只是 name = decorator(decorated_object)
的语法糖,您以后总是可以通过使用 name = decorator(name)
或在 class 上下文中应用装饰器,如 cls.name = decorator(cls.name)
.如果你需要挑选和选择这应该适用于哪些方法,你可以选择标准,如方法名称,或方法上设置的属性,或方法的文档字符串等。或者直接在方法上使用装饰器。
据我所知,元类可以做到这一点。您可能需要以某种方式让元类可以使用装饰器,可能是通过导入,然后您可以将它们包含在准备好的命名空间中:
class includewraps(type):
def prepare(*args):
from functools import wraps
return {'wraps': wraps}
class haswraps (metaclass = includewraps):
# wraps is available in this scope
编写一个装饰器,它接受一个字符串列表并在第一次调用该函数之前导入它们。这避免了显式导入,直到需要它们之前的最后一刻。与此处的所有其他答案一样,这可能表明您的代码应该重组。
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
中,因此只需要在第一次调用该函数时进行导入和装饰。
是否可以修改 class 以使某个方法装饰器可用,而不必显式导入它也不必为其添加前缀 (@something.some_decorator
):
class SomeClass:
@some_decorator
def some_method(self):
pass
我认为 class 装饰器不可能做到这一点,因为应用得太晚了。看起来更有希望的选项是使用元 class,但我不确定如何,我的猜测是我必须将 some_decorator
引入到 SomeClass
.[= 的命名空间中18=]
感谢@MartijnPieters 指出 staticmethod
和 classmethod
是内置的。我原以为它们会成为 type
机器的一部分。
明确地说,我没有任何明确的用例,我只是想知道这是否完全可行。
附录,现在问题已得到解答。我不局限于简单地在本地导入或定义装饰器的最初原因是,我定义了一个装饰器,该装饰器只有在对象上初始化了某个容器属性时才起作用,并且我正在寻找一种方法来强制执行此操作装饰器的可用性。我最终检查了该属性是否存在,如果不存在则在装饰器中对其进行初始化,这很可能是较小的问题。
是的,在Python3你可以使用metaclass __prepare__
hook。它应该是 return 一个映射,它构成了 class 主体的本地名称空间的基础:
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 所述:显式优于隐式,并且在您的 classes 中引入魔法名称很容易导致混淆和错误。导入 metaclass 与导入装饰器没有什么不同,你用一个名字替换了另一个名字。
class 装饰器仍然可以在 class 主体创建后将其他装饰器应用于 class 上的方法。 @decorator
语法只是 name = decorator(decorated_object)
的语法糖,您以后总是可以通过使用 name = decorator(name)
或在 class 上下文中应用装饰器,如 cls.name = decorator(cls.name)
.如果你需要挑选和选择这应该适用于哪些方法,你可以选择标准,如方法名称,或方法上设置的属性,或方法的文档字符串等。或者直接在方法上使用装饰器。
据我所知,元类可以做到这一点。您可能需要以某种方式让元类可以使用装饰器,可能是通过导入,然后您可以将它们包含在准备好的命名空间中:
class includewraps(type):
def prepare(*args):
from functools import wraps
return {'wraps': wraps}
class haswraps (metaclass = includewraps):
# wraps is available in this scope
编写一个装饰器,它接受一个字符串列表并在第一次调用该函数之前导入它们。这避免了显式导入,直到需要它们之前的最后一刻。与此处的所有其他答案一样,这可能表明您的代码应该重组。
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
中,因此只需要在第一次调用该函数时进行导入和装饰。