The __qualname__
attribute is useful to me because it contextualizes functions; however, it's difficult for me to use for my use case because:
__qualname__
returns a string. For my usecase, I need references to the parent object(s).
__qualname__
sometimes returns the super
class instead of the referenced class. For example:
class Parent():
def __init__(self):
pass
class Child(Parent):
pass
print(Child.__init__.__qualname__) # Prints: "Parent.__init__"
The package I am developing needs to be robust, and the edge cases for __qualname__
are not documented as far as I can tell.
Outside of parsing the Python file with ast
, can __qualname__
be reimplemented in Python3 with inspection? How does Python implement __qualname__
? In reimplementing the core functionality, I think I'll be able to adapt it for my use case.
Prior Research:
qualname
implementation: https://github.com/wbolster/qualname/blob/master/qualname.py__qualname__
to get references: Get defining class of unbound method object in Python 3__qualname__
stackoverflow: Reproduce effects of Python 3.3 __qualname__ in earlier PythonsI was unable to find the qualname implementation in the Python source code.
If you can accept an unconventional (or cumbersome) class creation using subclasses from a BaseNode
class for every attribute
class A(BaseNode):
class a(BaseNode):
pass
a = a()
class B(BaseNode):
class a1(A):
pass
a1 = a1()
class a2(A):
pass
a2 = a2()
class C(B):
class c(BaseNode):
pass
c = c()
then every attribute knows his path name in the class hierarchy. (The class creation can be simplified by using __init_subclass__()
or a decorator.)
c = C()
assert c._pathname == 'C'
assert c.c._pathname == 'C.c'
assert c.a1._pathname == 'C.a1'
assert c.a1.a._pathname == 'C.a1.a'
assert c.a2._pathname == 'C.a2'
assert c.a2.a._pathname == 'C.a2.a'
Every attribute instance is created by demand which you can see on the print
outputs
*** <__main__.C object at 0x7fca81558190> creates 'c' from <class '__main__.C.c'>
*** <__main__.C object at 0x7fca81558190> creates 'a1' from <class '__main__.B.a1'>
*** <__main__.B.a1 object at 0x7fca8155b760> creates 'a' from <class '__main__.A.a'>
*** <__main__.C object at 0x7fca81558190> creates 'a2' from <class '__main__.B.a2'>
*** <__main__.B.a2 object at 0x7fca8155b490> creates 'a' from <class '__main__.A.a'>
The BaseNode
class uses self._parent
for tracking the parent object (= instance of the parent class).
class BaseNode:
def __init__(self, parent=None):
self._parent = parent
@property
def _path(self):
path = []
obj = self
while obj:
path.append(obj)
obj = obj._parent
return path[::-1]
@property
def _pathname(self):
return '.'.join(node.__class__.__name__ for node in self._path)
def __get__(self, obj, cls=None):
if obj is None:
return self
name = self.__class__.__name__
try:
inst = obj.__dict__[name]
except KeyError:
print(f'*** {obj} creates {name!r} from {self.__class__}')
inst = self.__class__(obj)
obj.__dict__[name] = inst
return inst
The descriptor method __get__()
dynamically creates the attributes (= BaseNode instances) on demand.
BTW: Any comments or improvements for this approach are welcome :-)