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
Functions and methods can return objects. This is actually nothing new since everything in Python is an object and we have been returning values for quite some time.
To access the method of a class, we need to instantiate a class into an object. Then we can access the method as an instance method of the class as shown in the program below. Here through the self parameter, instance methods can access attributes and other methods on the same object.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With