【问题标题】:Get Python function's owning class from decorator从装饰器中获取 Python 函数的所属类
【发布时间】:2011-12-02 14:04:42
【问题描述】:

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

我的装饰器:

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

【问题讨论】:

  • 在装饰器中给出一个特殊的参数is_member_func,如果是真的得到第一个参数是self。

标签: python class function decorator inspect


【解决方案1】:

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

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

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

【讨论】:

  • 这就是我害怕的。谢谢。
  • 由于某种原因,直到我回到代码中才注册。这是可能的,我只需将与 FN 交互的代码移动到包装函数中,其中 FN 是一个实例。
  • 这在定义时是不可能的,但是您可以在创建类之后列出装饰函数:要实现这一点,装饰器必须在类上放置一些标记属性
  • @kolypto 你的意思是 包装函数 上的标记属性...因为到目前为止的问题是装饰器无法访问该类?
  • @Anentropic,对:一些代码必须对创建的类上的方法进行后处理,并告诉哪些方法被修饰了——我们需要以某种方式标记它们。例如,使用自定义属性
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    获取类名

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

    如果方法的类是内部类,则 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()
    

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

    类定义后获取类

    如果即使没有调用修饰方法也需要获取类信息,可以存储对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 的意义上,它们仅用于函数参数和返回值)。一个问题是装饰器必须是最后应用的,否则类属性不会存储相关的包装器,而是另一个外部包装器。这可以通过在包装器上存储(并稍后检查)所有装饰器来部分解决:

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

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

    【讨论】:

      猜你喜欢
      • 2018-01-21
      • 2017-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-20
      • 2011-01-19
      • 2019-12-26
      • 2020-12-18
      相关资源
      最近更新 更多