Skip to content Skip to sidebar Skip to footer

Given A Method, How Do I Return The Class It Belongs To In Python 3.3 Onward?

Given x = C.f after: class C: def f(self): pass What do I call on x that will return C? The best I could do is execing a parsed portion of x.__qualname__, which is ugl

Solution 1:

If your aim is to get rid of the exec statement, but are willing to use the __qualname__ attribute, even though you are still required to manually parse it, then at least for simple cases the following seems to work:

x.__globals__[x.__qualname__.rsplit('.', 1)[0]]

or:

getattr(inspect.getmodule(x), x.__qualname__.rsplit('.', 1)[0])

I'm not a Python expert, but I think the second solution is better, considering the following documentation excerpts:

  • from What's new in Python 3.3:

    Functions and class objects have a new __qualname__ attribute representing the “path” from the module top-level to their definition. For global functions and classes, this is the same as __name__. For other functions and classes, it provides better information about where they were actually defined, and how they might be accessible from the global scope.

  • from __qualname__'s description in PEP 3155:

    For nested classed, methods, and nested functions, the __qualname__ attribute contains a dotted path leading to the object from the module top-level.

EDIT:

  1. As noted in the comments by @eryksun, parsing __qualname__ like this goes beyond its intended usage and is extremely fragile considering how __qualname__ reflects closures. A more robust approach needs to exclude closure namespaces of the form name.<locals>. For example:

    >>> classC:
    ...     f = (lambda x: lambda s: x)(1)
    ... >>> x = C.f
    >>> x
    <function C.<lambda>.<locals>.<lambda> at 0x7f13b58df730>
    >>> x.__qualname__
    'C.<lambda>.<locals>.<lambda>'>>> getattr(inspect.getmodule(x), x.__qualname__.rsplit('.', 1)[0])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'module'object has no attribute 'C.<lambda>.<locals>'

    This specific case can be handled in the following manner:

    >>> getattr(inspect.getmodule(x),
    ...         x.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
    <class'__main__.C'>
    

    Nonetheless, it's unclear what other corner cases exist now or may come up in future releases.

  2. As noted in the comment by @MichaelPetch, this answer is relevant only for Python 3.3 onward, as only then the __qualname__ attribute was introduced into the language.

  3. For a complete solution that handles bound methods as well, please refer to this answer.

Solution 2:

I'll contribute one more option that relies on the gc module to follow references backwards.

It relies on implementation details that certainly aren't guaranteed and certainly won't work on all Python implementations. Nevertheless, some applications may find this option preferable to working with __qualname__.

You actually need two hops backwards, because the class hides a dict inside it, which holds the member function:

defclass_holding(fn):
    '''                                                                                                                                                                                            
    >>> class Foo:                                                                                                                                                                                 
    ...     def bar(self):                                                                                                                                                                         
    ...         return 1                                                                                                                                                                           
    >>> class_holding(Foo.bar)                                                                                                                                                                     
    <class Foo>                                                                                                                                                                                    
    '''for possible_dict in gc.get_referrers(fn):
        ifnotisinstance(possible_dict, dict):
            continuefor possible_classin gc.get_referrers(possible_dict):
            ifgetattr(possible_class, fn.__name__, None) is fn:
                return possible_classreturnNone

Post a Comment for "Given A Method, How Do I Return The Class It Belongs To In Python 3.3 Onward?"