Given a method, how do I return the class it belongs to in Python 3.3 onward?
Question:
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 exec
ing a parsed portion of x.__qualname__
, which is ugly:
exec('d = ' + ".".join(x.__qualname__.split('.')[:-1]))
For a use case, imagine that I want a decorator that adds a super
call to any method it’s applied to. How can that decorator, which is only given the function object, get the class to super
(the ???
below)?
def ensure_finished(iterator):
try:
next(iterator)
except StopIteration:
return
else:
raise RuntimeError
def derived_generator(method):
def new_method(self, *args, **kwargs):
x = method(self, *args, **kwargs)
y = getattr(super(???, self), method.__name__)
(*args, **kwargs)
for a, b in zip(x, y):
assert a is None and b is None
yield
ensure_finished(x)
ensure_finished(y)
return new_method
Answers:
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:
-
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:
>>> class C:
... 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.
-
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.
- However, according to @WouterBolsterlee, github.com/wbolster/qualname provides an equivalent for older
Python
versions.
-
For a complete solution that handles bound methods as well, please refer to this answer.
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:
def class_holding(fn):
'''
>>> class Foo:
... def bar(self):
... return 1
>>> class_holding(Foo.bar)
<class Foo>
'''
for possible_dict in gc.get_referrers(fn):
if not isinstance(possible_dict, dict):
continue
for possible_class in gc.get_referrers(possible_dict):
if getattr(possible_class, fn.__name__, None) is fn:
return possible_class
return None
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 exec
ing a parsed portion of x.__qualname__
, which is ugly:
exec('d = ' + ".".join(x.__qualname__.split('.')[:-1]))
For a use case, imagine that I want a decorator that adds a super
call to any method it’s applied to. How can that decorator, which is only given the function object, get the class to super
(the ???
below)?
def ensure_finished(iterator):
try:
next(iterator)
except StopIteration:
return
else:
raise RuntimeError
def derived_generator(method):
def new_method(self, *args, **kwargs):
x = method(self, *args, **kwargs)
y = getattr(super(???, self), method.__name__)
(*args, **kwargs)
for a, b in zip(x, y):
assert a is None and b is None
yield
ensure_finished(x)
ensure_finished(y)
return new_method
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:
-
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 formname.<locals>
. For example:>>> class C: ... 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.
-
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.- However, according to @WouterBolsterlee, github.com/wbolster/qualname provides an equivalent for older
Python
versions.
- However, according to @WouterBolsterlee, github.com/wbolster/qualname provides an equivalent for older
-
For a complete solution that handles bound methods as well, please refer to this answer.
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:
def class_holding(fn):
'''
>>> class Foo:
... def bar(self):
... return 1
>>> class_holding(Foo.bar)
<class Foo>
'''
for possible_dict in gc.get_referrers(fn):
if not isinstance(possible_dict, dict):
continue
for possible_class in gc.get_referrers(possible_dict):
if getattr(possible_class, fn.__name__, None) is fn:
return possible_class
return None