Получение имени класса
Если вам нужно только имя класса (а не сам класс), оно доступно как часть функции (частично) полный атрибут имени (__qualname__
).
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):
pass
class B(object):
@decorator
def method(self):
pass
Если класс метода является внутренним классом, qualname будет включать внешние классы. Приведенный выше код обрабатывает это, заменяя все разделители точек локальным разделителем пути.
Ничто в классе, кроме его имени, недоступно при вызове декоратора, поскольку сам класс еще не определен.
Получение класса при вызове метода
Если необходим сам класс и доступ может быть отложен до тех пор, пока не будет (сначала) вызван декорированный метод, декоратор может обернуть функцию, как обычно, а затем обертка может получить доступ к экземпляру и классу. Оболочка также может удалить себя и удалить метод, если он должен быть вызван только один раз.
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):
pass
a = A()
a.method()
a.method()
Обратите внимание, что это будет работать только в том случае, если метод вызывается.
Получение класса после определения класса
Если вам нужно получить информацию о классе, даже если декорированный метод не вызывается, вы можете сохранить ссылку на декоратор в оболочке (метод #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):
pass
@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)
# 3
Другая проблема заключается в том, что сканирование всех классов неэффективно. Вы можете сделать это более эффективным, используя дополнительный декоратор класса, чтобы зарегистрировать все классы для проверки декоратора метода. Однако этот подход ненадежен; если вы забудете украсить класс, он не будет записан в реестре.
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):
pass
def func(self):
pass
@decorator
def decorated(self):
pass
@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()
list(decoratedRegistry.getDecoratedMethodsOf(A.B))
list(decoratedRegistry.getDecoratedMethodsOf(A))
list(decoratedRegistry.getAllDecoratedMethods())
Мониторинг нескольких декораторов и применение нескольких реестров декораторов, оставленных в качестве упражнений.
person
outis
schedule
17.06.2021
is_member_func
, если это правда, получите первый аргумент, который является self. - person bhathiya-perera   schedule 11.03.2015