从装饰器获取Python函数的拥有类

时间:2011-10-06 21:06:10

标签: python class function decorator inspect

我在PY有一个装饰师。它是一种方法,并将该函数作为参数。我想基于传递的函数创建一个基于目录的结构。我使用父目录的模块名称,但希望使用类名作为子目录。我无法弄清楚如何获得拥有fn对象的类的名称。

我的装饰者:

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

3 个答案:

答案 0 :(得分:8)

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

>>> class Foo(object):
...     def bar(self):
...         pass
...
>>> Foo.bar.im_class
__main__.Foo

请注意,这将 not 从装饰器中起作用,因为在定义类之后,函数只会转换为实例方法(即,如果{{1} }用于装饰@specialTest,它不起作用;如果它甚至可能,那么在那一点上执行它必须通过检查调用堆栈或同样不满意的事情来完成。

答案 1 :(得分:6)

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

答案 2 :(得分:0)

获取类名

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

如果方法的类是内部类,则限定名将包括外部类。上面的代码通过用本地路径分隔符替换所有点分隔符来处理这个问题。

当装饰器被调用时,除了其名称之外的任何类都无法访问,因为类本身尚未定义。

在方法调用处获取类

如果需要类本身并且访问可以延迟到(首先)调用装饰方法之前,装饰器可以像往常一样包装函数,然后包装器可以访问实例和类。如果只应调用一次,包装器也可以移除自身并取消修饰该方法。

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()

请注意,这仅在调用该方法时才有效。

在类定义之后获取类

如果即使没有调用装饰方法也需要获取类信息,可以存储对decorator on the wrapper (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

这基本上使装饰器成为一般编程意义上的注解(而不是 function annotations in Python 意义上的注解,后者仅用于函数参数和返回值)。一个问题是装饰器必须是最后应用的,否则 class 属性将不会存储相关的包装器,而是另一个外部包装器。这可以通过在包装器上存储(并稍后检查)所有装饰器来部分处理:

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())

监控多个装饰器并应用多个装饰器注册表作为练习。