8

我在 PY 有一个装饰师。它是一种方法,并将函数作为参数。我想根据传递的函数创建一个目录结构。我将模块名称用于父目录,但想将类名用于子目录。我不知道如何获取拥有 fn 对象的类的名称。

我的装饰器:

def specialTest(fn):
    filename = fn.__name__
    directory = fn.__module__
    subdirectory = fn.__class__.__name__ #WHERE DO I GET THIS
4

3 回答 3

8

如果fninstancemethod,那么您可以使用fn.im_class.

>>> 类 Foo(对象):
...定义栏(自我):
... 经过
...
>>> Foo.bar.im_class
__main__.Foo

请注意,这在装饰器中不起作用,因为函数仅在定义类之后才转换为实例方法(即,如果@specialTest用于 decorate bar,它将不起作用;如果甚至可能,那么在那时执行必须通过检查调用堆栈或同样不愉快的事情来完成)。

于 2011-10-06T21:08:53.703 回答
6

在 Python 2 中,您可以im_class在方法对象上使用属性。在 Python 3 中,它将是__self__.__class__(或type(method.__self__))。

于 2011-10-06T21:11:48.567 回答
0

获取类名

如果您想要的只是类名(而不是类本身),它可以作为函数(部分)限定名属性( __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.

于 2021-06-17T06:45:33.420 回答