I have a decorator in PY. It is a method and takes the function as a parameter. I want to create a directory structure based based on the passed function. I am using the module name for the parent directory but would like to use the classname for a subdirectory. I can't figure out how to get the name of the class that owns the fn object.
My Decorator:
def specialTest(fn):
f开发者_运维问答ilename = fn.__name__
directory = fn.__module__
subdirectory = fn.__class__.__name__ #WHERE DO I GET THIS
If fn
is an instancemethod
, then you can use fn.im_class
.
>>> class Foo(object): ... def bar(self): ... pass ... >>> Foo.bar.im_class __main__.Foo
Note that this will not work from a decorator, because a function is only transformed into an instance method after the class is defined (ie, if @specialTest
was used to decorate bar
, it would not work; if it's even possible, doing it at that point would have to be done by inspecting the call stack or something equally unhappy).
In Python 2 you can use im_class
attribute on the method object. In Python 3, it'll be __self__.__class__
(or type(method.__self__)
).
Getting the Class Name
If all you want is the class name (and not the class itself), it's available as part of the function's (partially) qualified name attribute (__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
If the method's class is an inner class, the qualname will include the outer classes. The above code handles this by replacing all dot separators with the local path separator.
Nothing of the class beyond its name is accessible when the decorator is called as the class itself is not yet defined.
Getting the Class At Method Call
If the class itself is needed and access can be delayed until the decorated method is (first) called, the decorator can wrap the function, as per usual, and the wrapper can then access the instance and class. The wrapper can also remove itself and undecorate the method if it should only be invoked once.
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()
Note that this will only work if the method is called.
Getting the Class After Class Definition
If you need to get the class info even if the decorated method is not called, you can store a reference to the decorator on the wrapper (method #3), then scan the methods of all classes (after the classes of interest are defined) for those that refer to the decorator:
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
This basically makes the decorator an annotation in the general programming sense (and not the sense of function annotations in Python, which are only for function arguments and the return value). One issue is the decorator must be the last applied, otherwise the class attribute won't store the relevant wrapper but another, outer wrapper. This can be dealt with in part by storing (and later checking) all decorators on the wrapper:
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
Additionally, any decorators you don't control can be made to cooperate by decorating them so they will store themselves on their wrappers, just as decorator
does:
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
Another problem is scanning all classes is inefficient. You can make this more efficient by using an additional class decorator to register all classes to check for the method decorator. However, this approach is brittle; if you forget to decorate the class, it won't be recorded in the registry.
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
Usage:
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())
Monitoring multiple decorators and applying multiple decorator registries left as exercises.
精彩评论