python: determine if a class is nested
-
11-07-2019 - |
Question
Suppose you have a python method that gets a type as parameter; is it possible to determine if the given type is a nested class?
E.g. in this example:
def show_type_info(t):
print t.__name__
# print outer class name (if any) ...
class SomeClass:
pass
class OuterClass:
class InnerClass:
pass
show_type_info(SomeClass)
show_type_info(OuterClass.InnerClass)
I would like the call to show_type_info(OuterClass.InnerClass)
to show also that InnerClass is defined inside OuterClass.
Solution
AFAIK, given a class and no other information, you can't tell whether or not it's a nested class. However, see here for how you might use a decorator to determine this.
The problem is that a nested class is simply a normal class that's an attribute of its outer class. Other solutions that you might expect to work probably won't -- inspect.getmro
, for example, only gives you base classes, not outer classes.
Also, nested classes are rarely needed. I would strongly reconsider whether that's a good approach in each particular case where you feel tempted to use one.
OTHER TIPS
An inner class offers no particular special features in Python. It's only a property of the class object, no different from an integer or string property. Your OuterClass/InnerClass example can be rewritten exactly as:
class OuterClass(): pass
class InnerClass(): pass
OuterClass.InnerClass= InnerClass
InnerClass can't know whether it was declared inside another class, because that's just a plain variable binding. The magic that makes bound methods know about their owner ‘self’ doesn't apply here.
The innerclass decorator magic in the link John posted is an interesting approach but I would not use it as-is. It doesn't cache the classes it creates for each outer object, so you get a new InnerClass every time you call outerinstance.InnerClass:
>>> o= OuterClass()
>>> i= o.InnerClass()
>>> isinstance(i, o.InnerClass)
False # huh?
>>> o.InnerClass is o.InnerClass
False # oh, whoops...
Also the way it tries to replicate the Java behaviour of making outer class variables available on the inner class with getattr/setattr is very dodgy, and unnecessary really (since the more Pythonic way would be to call i.__outer__.attr explicitly).
Really a nested class is no different from any other class - it just happens to be defined somewhere else than the top-level namespace (inside another class instead). If we modify the description from "nested" to "non-top-level", then you may be able to come close enough to what you need.
eg:
import inspect
def not_toplevel(cls):
m = inspect.getmodule(cls)
return not (getattr(m, cls.__name__, []) is cls)
This will work for common cases, but it may not do what you want in situations where classes are renamed or otherwise manipulated after definition. For example:
class C: # not_toplevel(C) = False
class B: pass # not_toplevel(C.B) = True
B=C.B # not_toplevel(B) = True
D=C # D is defined at the top, but...
del C # not_toplevel(D) = True
def getclass(): # not_toplevel(getclass()) = True
class C: pass
Thank you all for your answers.
I've found this possible solution using metaclasses; I've done it more for obstination than real need, and it's done in a way that will not be applicable to python 3.
I want to share this solution anyway, so I'm posting it here.
#!/usr/bin/env python
class ScopeInfo(type): # stores scope information
__outers={} # outer classes
def __init__(cls, name, bases, dict):
super(ScopeInfo, cls).__init__(name, bases, dict)
ScopeInfo.__outers[cls] = None
for v in dict.values(): # iterate objects in the class's dictionary
for t in ScopeInfo.__outers:
if (v == t): # is the object an already registered type?
ScopeInfo.__outers[t] = cls
break;
def FullyQualifiedName(cls):
c = ScopeInfo.__outers[cls]
if c is None:
return "%s::%s" % (cls.__module__,cls.__name__)
else:
return "%s.%s" % (c.FullyQualifiedName(),cls.__name__)
__metaclass__ = ScopeInfo
class Outer:
class Inner:
class EvenMoreInner:
pass
print Outer.FullyQualifiedName()
print Outer.Inner.FullyQualifiedName()
print Outer.Inner.EvenMoreInner.FullyQualifiedName()
X = Outer.Inner
del Outer.Inner
print X.FullyQualifiedName()
If you do not set it yourself, I do not believe that there is any way to determine if the class is nested. As anyway a Python class cannot be used as a namespace (or at least not easily), I would say that the best thing to do is simply use different files.
Beginning with Python 3.3 there is a new attribute __qualname__
, which provides not only the class name, but also the names of the outer classes:
In your sample this would result in:
assert SomeClass.__qualname__ == 'SomeClass'
assert OuterClass.InnerClass.__qualname__ == 'OuterClass.InnerClass'
If __qualname__
of a class does not contains a '.' it is an outer class!