The real answer is that there is no general way to do this. Python classes are normal objects, but they are created a bit differently. A class does not exist until well after its entire body has been executed. Once a class is created, it can be bound to many different names. The only reference it has to where it was created are the __module__ and __qualname__ attributes, but both of these are mutable.
In practice, it is possible to write your example like this:
class Sub:
def __init__(self):
pass
class Base:
Sub = Sub
Sub.__qualname__ = 'Base.Sub'
class Sub(Sub):
pass
class Extra(Base):
Sub = Sub
Sub.__qualname__ = 'Extra.Sub'
del Sub # Unlink from global namespace
Barring the capitalization, this behaves exactly as your original example. Hopefully this clarifies which code has access to what, and shows that the most robust way to determine the enclosing scope of a class is to explicitly assign it somewhere. You can do this in any number of ways. The trivial way is just to assign it. Going back to your original notation:
class Base:
class Sub:
def __init__(self):
print(self.enclosing)
Base.Sub.enclosing = Base
class Extra(Base):
class Sub(Base.Sub):
pass
Extra.Sub.enclosing = Extra
Notice that since Base does not exist when it body is being executed, the assignment has to happen after the classes are both created. You can bypass this by using a metaclass or a decorator. That will allow you to mess with the namespace before the class object is assigned to a name, making the change more transparent.
class NestedMeta(type):
def __init__(cls, name, bases, namespace):
for name, obj in namespace.items():
if isinstance(obj, type):
obj.enclosing = cls
class Base(metaclass=NestedMeta):
class Sub:
def __init__(self):
print(self.enclosing)
class Extra(Base):
class Sub(Base.Sub):
pass
But this is again somewhat unreliable because not all metaclasses are an instance of type, which takes us back to the first statement in this answer.
subinto another class gives you nothing.extrahas anything to do withbase.sub. Please explain your assumptions better, and post an actual MCVE.selfinbase.sub.__init__MAY be of typebase.suborextra.sub. The code above is actually runnable, and if runselfwill be typeextra.subin thebase.subconstructor. Getting typeextrafromextra.subis what this comes down to.extradoes not have anything to do withbase.subis a bit too strong,extrais related tobaseby inheritance, andbaseis related tobase.subby namespace. One would have to get into definition of "related" and start a discussion on relationship theory to take this much further though, but that won't help or interest me. The problem at hand is following the namespace relationship fromextra.subtoextra, it's clearly semantically possible, and often done even in various contexts. Writingfrom .. import xwe traverse the module namespace back for example.