从装饰器中获取Python函数所属的类

发布于 2024-12-08 17:05:38 字数 282 浏览 1 评论 0原文

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

我的装饰师:

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

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):
    filename = fn.__name__
    directory = fn.__module__
    subdirectory = fn.__class__.__name__ #WHERE DO I GET THIS

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

野心澎湃 2024-12-15 17:05:38

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

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

请注意,这在装饰器中无法工作,因为函数仅在定义类之后才会转换为实例方法(即,如果@specialTest 被用来装饰 bar,它不会工作;即使有可能,此时也必须通过检查调用堆栈或同样不愉快的事情来完成)。

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

別甾虛僞 2024-12-15 17:05:38

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

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__)).

羞稚 2024-12-15 17:05:38

获取类名

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

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

在类定义之后获取类

如果即使未调用装饰方法也需要获取类信息,则可以存储对 的引用包装器上的装饰器(方法#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())

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文