Domanda

Supponi di avere un metodo Python che ottiene un tipo come parametro; è possibile determinare se il tipo dato è una classe nidificata?
Per esempio. in questo esempio:

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)

Vorrei che la chiamata a show_type_info (OuterClass.InnerClass) mostrasse anche che InnerClass è definito all'interno di OuterClass.

È stato utile?

Soluzione

AFAIK, data una classe e nessuna altra informazione, non puoi dire se è una classe nidificata. Tuttavia, vedi qui per come utilizzare un decoratore per determinarlo.

Il problema è che una classe nidificata è semplicemente una classe normale che è un attributo della sua classe esterna. Altre soluzioni che potresti aspettarti di funzionare probabilmente non funzioneranno - inspect.getmro , ad esempio, ti dà solo classi di base, non classi esterne.

Inoltre, raramente sono necessarie classi nidificate. Riconsidererei fortemente se si tratta di un buon approccio in ogni caso particolare in cui ti senti tentato di usarne uno.

Altri suggerimenti

Una classe interna non offre particolari funzioni speciali in Python. È solo una proprietà dell'oggetto classe, non diversa da un intero o da una proprietà stringa. Il tuo esempio OuterClass / InnerClass può essere riscritto esattamente come:

class OuterClass(): pass
class InnerClass(): pass
OuterClass.InnerClass= InnerClass

InnerClass non può sapere se è stato dichiarato all'interno di un'altra classe, perché si tratta solo di una semplice associazione di variabili. La magia che fa conoscere i metodi associati al "sé" del loro proprietario non si applica qui.

La magia del decoratore innerclass nel link pubblicato da John è un approccio interessante ma non lo userei così com'è. Non memorizza nella cache le classi che crea per ogni oggetto esterno, quindi ottieni una nuova InnerClass ogni volta che chiami outerinstance.InnerClass:

>>> o= OuterClass()
>>> i= o.InnerClass()
>>> isinstance(i, o.InnerClass)
False # huh?
>>> o.InnerClass is o.InnerClass
False # oh, whoops...

Anche il modo in cui tenta di replicare il comportamento Java di rendere disponibili le variabili di classe esterne sulla classe interna con getattr / setattr è molto complicato, e in realtà non necessario (dal momento che il modo più Pythonic sarebbe chiamare i .__ esterno __. attr esplicitamente ).

In realtà una classe nidificata non è diversa da qualsiasi altra classe - è semplicemente definita da qualche altra parte rispetto allo spazio dei nomi di livello superiore (all'interno di un'altra classe). Se modifichiamo la descrizione da " nidificato " a "non di livello superiore", allora potresti essere in grado di avvicinarti abbastanza a ciò di cui hai bisogno.

es:

import inspect

def not_toplevel(cls):
    m = inspect.getmodule(cls)
    return not (getattr(m, cls.__name__, []) is cls)

Funzionerà nei casi più comuni, ma potrebbe non fare ciò che desideri nelle situazioni in cui le classi vengono rinominate o altrimenti manipolate dopo la definizione. Ad esempio:

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

Grazie a tutti per le risposte.
Ho trovato questa possibile soluzione usando metaclassi; L'ho fatto più per ostinazione che per necessità reali, ed è fatto in un modo che non sarà applicabile a Python 3.
Voglio condividere questa soluzione comunque, quindi la sto pubblicando qui.

#!/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()

Se non lo imposti tu stesso, non credo che ci sia modo di determinare se la classe è nidificata. Dato che comunque una classe Python non può essere usata come spazio dei nomi (o almeno non facilmente), direi che la cosa migliore da fare è semplicemente usare file diversi.

A partire da Python 3.3 c'è un nuovo attributo __qualname__ , che fornisce non solo il nome della classe, ma anche i nomi delle classi esterne:

Nel tuo esempio questo comporterebbe:

assert SomeClass.__qualname__ == 'SomeClass'
assert OuterClass.InnerClass.__qualname__ == 'OuterClass.InnerClass'

Se __qualname__ di una classe non contiene un '.' è una classe esterna!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top