views:

704

answers:

5

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.

+10  A: 

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.

John Feminella
+1 This is good advice.
Andrew Hare
Thanks, accepted your answer: the point is the fact that a nested class is an attribute of the outer.P.S: I'm sure there are always alternatives to nested classes, but I cannot see why they should be a bad idea either.
Paolo Tedesco
+3  A: 

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).

bobince
A: 

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.

Nikwin
+2  A: 

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
Brian
A: 

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()
Paolo Tedesco