import os.path
def decorator(fn):
filename = fn.__name__
directory = fn.__module__
subdirectory = fn.__qualname__.removesuffix('.' + fn.__name__).replace('.', os.path.sep)
return fn
class A(object):
@decorator
def method(self):
class B(object):
@decorator
def method(self):
如果该方法的类是一个内部类,qualname将包括外部类。上面的代码通过用本地路径分隔符替换所有的点分隔符来处理这个问题。
当装饰器被调用时,除了它的名字之外,类的任何东西都无法访问,因为类本身还没有被定义。
Getting the Class At Method Call
如果需要类本身,并且访问可以延迟到被装饰的方法被(第一次)调用时,装饰器可以像往常一样包装函数,然后包装器可以访问实例和类。如果该方法只需被调用一次,包装器也可以删除自己并取消装饰。
import types
def once(fn):
def wrapper(self, *args, **kwargs):
# do something with the class
subdirectory = type(self).__name__
# undecorate the method (i.e. remove the wrapper)
setattr(self, fn.__name__, types.MethodType(fn, self))
# invoke the method
return fn(self, *args, **kwargs)
return wrapper
class A(object):
@once
def method(self):
a = A()
a.method()
a.method()
请注意,这只有在方法被调用时才会起作用。
Getting the Class After Class Definition
如果你需要在装饰方法没有被调用的情况下获得类的信息,你可以存储一个引用到包装器上的装饰器(方法#3)。,然后扫描所有类的方法(在感兴趣的类被定义之后),寻找那些引用装饰器的方法。
def decorator(fn):
def wrapper(self, *args, **kwargs):
return fn(self, *args, **kwargs)
wrapper.__decorator__ = decorator
wrapper.__name__ = 'decorator + ' + fn.__name__
wrapper.__qualname__ = 'decorator + ' + fn.__qualname__
return wrapper
def methodsDecoratedBy(cls, decorator):
for method in cls.__dict__.values():
if hasattr(method, '__decorator__') \
and method.__decorator__ == decorator:
yield method
import sys, inspect
def allMethodsDecoratedBy(decorator)
for name, cls in inspect.getmembers(sys.modules, lambda x: inspect.isclass(x)):
for method in methodsDecoratedBy(cls, decorator):
yield method
这基本上使装饰器成为一般编程意义上的注解(而非Python中的函数注解,这只是针对函数参数和返回值)。一个问题是装饰器必须是最后应用的,否则类属性就不会存储相关的包装器,而是存储另一个外部包装器。这个问题可以通过存储(并随后检查)包装器上的所有装饰器来部分解决。
def decorator(fn):
def wrapper(self, *args, **kwargs):
return fn(self, *args, **kwargs)
wrapper.__decorator__ = decorator
if not hasattr(fn, '__decorators__'):
if hasattr(fn, '__decorator__'):
fn.__decorators__ = [fn.__decorator__]
else:
fn.__decorators__ = []
wrapper.__decorators__ = [decorator] + fn.__decorators__
wrapper.__name__ = 'decorator(' + fn.__name__ + ')'
wrapper.__qualname__ = 'decorator(' + fn.__qualname__ + ')'
return wrapper
def methodsDecoratedBy(cls, decorator):
for method in cls.__dict__.values():
if hasattr(method, '__decorators__') and decorator in method.__decorators__:
yield method
此外,任何你不控制的装饰者都可以通过装饰它们来使其合作,这样它们就会像decorator
那样把自己储存在包装纸上。
def bind(*values, **kwvalues):
def wrap(fn):
def wrapper(self, *args, **kwargs):
nonlocal kwvalues
kwvalues = kwvalues.copy()
kwvalues.update(kwargs)
return fn(self, *values, *args, **kwvalues)
wrapper.__qualname__ = 'bind.wrapper'
return wrapper
wrap.__qualname__ = 'bind.wrap'
return wrap
def registering_decorator(decorator):
def wrap(fn):
decorated = decorator(fn)
decorated.__decorator__ = decorator
if not hasattr(fn, '__decorators__'):
if hasattr(fn, '__decorator__'):
fn.__decorators__ = [fn.__decorator__]
else:
fn.__decorators__ = []
if not hasattr(decorated, '__decorators__'):
decorated.__decorators__ = fn.__decorators__.copy()
decorated.__decorators__.insert(0, decorator)
decorated.__name__ = 'reg_' + decorator.__name__ + '(' + fn.__name__ + ')'
decorated.__qualname__ = decorator.__qualname__ + '(' + fn.__qualname__ + ')'
return decorated
wrap.__qualname__ = 'registering_decorator.wrap'
return wrap
class A(object):
@decorator
def decorated(self):
@bind(1)
def add(self, a, b):
return a + b
@registering_decorator(bind(1))
@decorator
def args(self, *args):
return args
@decorator
@registering_decorator(bind(a=1))
def kwargs(self, **kwargs):
return kwargs
A.args.__decorators__
A.kwargs.__decorators__
assert not hasattr(A.add, '__decorators__')
a = A()
a.add(2)
另一个问题是扫描所有的类是低效的。你可以通过使用一个额外的类装饰器来注册所有的类来检查方法装饰器,从而使其更有效率。然而,这种方法是很脆的;如果你忘记装饰类,它就不会被记录在注册表中。
class ClassRegistry(object):
def __init__(self):
self.registry = {}
def __call__(self, cls):
self.registry[cls] = cls
cls.__decorator__ = self
return cls
def getRegisteredClasses(self):
return self.registry.values()
class DecoratedClassRegistry(ClassRegistry):
def __init__(self, decorator):
self.decorator = decorator
super().__init__()
def isDecorated(self, method):
return ( hasattr(method, '__decorators__') \
and self.decorator in method.__decorators__) \
or ( hasattr(method, '__decorator__') \
and method.__decorator__ == self.decorator)
def getDecoratedMethodsOf(self, cls):
if cls in self.registry:
for method in cls.__dict__.values():
if self.isDecorated(method):
yield method
def getAllDecoratedMethods(self):
for cls in self.getRegisteredClasses():
for method in self.getDecoratedMethodsOf(cls):
yield method
使用方法。
decoratedRegistry = DecoratedClassRegistry(decorator)
@decoratedRegistry
class A(object):
@decoratedRegistry
class B(object):
@decorator
def decorated(self):
def func(self):
@decorator
def decorated(self):
@bind(1)
def add(self, a, b):
return a + b
@registering_decorator(bind(1))
@decorator
def args(self, *args):
return args
@decorator
@registering_decorator(bind(a=1))
def kwargs(self, **kwargs):
return kwargs
decoratedRegistry.getRegisteredClasses()