python: determina se una classe è nidificata
-
11-07-2019 - |
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.
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!