Risposte:
Citato da http://www.geekinterview.com/question_details/64739 :
Vantaggi della classe interna:
- Raggruppamento logico delle classi : se una classe è utile solo per un'altra classe, è logico incorporarla in quella classe e tenerle insieme. La nidificazione di queste "classi di supporto" rende il loro pacchetto più snello.
- Incapsulamento aumentato : si consideri due classi di primo livello A e B in cui B necessita dell'accesso ai membri di A che altrimenti sarebbero dichiarati privati. Nascondendo la classe B all'interno della classe AA i membri possono essere dichiarati privati e B può accedervi. Inoltre B stesso può essere nascosto al mondo esterno.
- Codice più leggibile e gestibile: annidare piccole classi all'interno di classi di primo livello colloca il codice più vicino a dove viene utilizzato.
Il vantaggio principale è l'organizzazione. Tutto ciò che può essere realizzato con le classi interne può essere realizzato senza di loro.
DataLoaderclasse che può generare CacheMissun'eccezione. Annidare l'eccezione sotto la classe principale come DataLoader.CacheMisssignifica che puoi importare solo DataLoaderma usare comunque l'eccezione.
C'è qualcosa che non può essere realizzato senza di loro?
No. Sono assolutamente equivalenti a definire la classe normalmente al livello più alto e quindi copiare un riferimento ad essa nella classe esterna.
Non penso che ci sia alcun motivo speciale per cui le classi annidate sono "consentite", a parte il fatto che non ha alcun senso particolare anche "non consentirle" esplicitamente.
Se stai cercando una classe che esiste all'interno del ciclo di vita dell'oggetto esterno / proprietario e ha sempre un riferimento a un'istanza della classe esterna - classi interne come fa Java - allora le classi nidificate di Python non sono quella cosa. Ma si può incidere qualcosa del genere che cosa:
import weakref, new
class innerclass(object):
"""Descriptor for making inner classes.
Adds a property 'owner' to the inner class, pointing to the outer
owner instance.
"""
# Use a weakref dict to memoise previous results so that
# instance.Inner() always returns the same inner classobj.
#
def __init__(self, inner):
self.inner= inner
self.instances= weakref.WeakKeyDictionary()
# Not thread-safe - consider adding a lock.
#
def __get__(self, instance, _):
if instance is None:
return self.inner
if instance not in self.instances:
self.instances[instance]= new.classobj(
self.inner.__name__, (self.inner,), {'owner': instance}
)
return self.instances[instance]
# Using an inner class
#
class Outer(object):
@innerclass
class Inner(object):
def __repr__(self):
return '<%s.%s inner object of %r>' % (
self.owner.__class__.__name__,
self.__class__.__name__,
self.owner
)
>>> o1= Outer()
>>> o2= Outer()
>>> i1= o1.Inner()
>>> i1
<Outer.Inner inner object of <__main__.Outer object at 0x7fb2cd62de90>>
>>> isinstance(i1, Outer.Inner)
True
>>> isinstance(i1, o1.Inner)
True
>>> isinstance(i1, o2.Inner)
False
(Questo utilizza decoratori di classi, che sono nuovi in Python 2.6 e 3.0. Altrimenti dovresti dire "Inner = innerclass (Inner)" dopo la definizione della classe.)
selfsenza alcun lavoro aggiuntivo richiesto (basta usare un identificatore diverso dove normalmente si inseriresti quello interno self; mi piace innerself) e sarà in grado di accedere all'istanza esterna tramite quello.
WeakKeyDictionaryin questo esempio non consente in realtà la raccolta di dati inutili delle chiavi, perché i valori fanno fortemente riferimento alle rispettive chiavi attraverso il loro ownerattributo.
C'è qualcosa di cui hai bisogno per capire questo. Nella maggior parte dei linguaggi, le definizioni di classe sono direttive per il compilatore. Cioè, la classe viene creata prima che il programma venga mai eseguito. In Python, tutte le istruzioni sono eseguibili. Ciò significa che questa affermazione:
class foo(object):
pass
è un'istruzione che viene eseguita in fase di esecuzione proprio come questa:
x = y + z
Ciò significa che non solo puoi creare classi all'interno di altre classi, ma puoi creare classi ovunque tu voglia. Considera questo codice:
def foo():
class bar(object):
...
z = bar()
Quindi, l'idea di una "classe interna" non è realmente un costrutto del linguaggio; è un costrutto del programmatore. Guido ha un ottimo riassunto di come è successo qui . Ma essenzialmente, l'idea di base è che questo semplifica la grammatica della lingua.
Annidamento delle classi all'interno delle classi:
Le classi annidate gonfiano la definizione della classe rendendo più difficile vedere cosa sta succedendo.
Le classi annidate possono creare accoppiamenti che renderebbero i test più difficili.
In Python puoi mettere più di una classe in un file / modulo, a differenza di Java, quindi la classe rimane ancora vicina alla classe di primo livello e potrebbe anche avere il nome della classe preceduto da un "_" per aiutare a significare che gli altri non dovrebbero essere usandolo.
Il luogo in cui le classi annidate possono rivelarsi utili è all'interno delle funzioni
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
La classe acquisisce i valori dalla funzione consentendo di creare dinamicamente una classe come la metaprogrammazione del modello in C ++
Capisco gli argomenti contro le classi annidate, ma in alcune occasioni è opportuno utilizzarle. Immagina di creare una classe di elenco a doppio collegamento e di dover creare una classe di nodo per mantenere i nodi. Ho due scelte, creare la classe Node all'interno della classe DoublyLinkedList o creare la classe Node all'esterno della classe DoublyLinkedList. Preferisco la prima scelta in questo caso, perché la classe Node è significativa solo all'interno della classe DoublyLinkedList. Sebbene non vi sia alcun vantaggio di nascondere / incapsulare, esiste un vantaggio di raggruppamento di poter dire che la classe Node fa parte della classe DoublyLinkedList.
Nodeclasse non sia utile per altri tipi di classi di elenchi collegati che potresti creare, nel qual caso dovrebbe probabilmente essere solo all'esterno.
Nodeè sotto lo spazio dei nomi di DoublyLinkedList, e ha senso logico che sia così. Questo è Pythonic: "Gli spazi dei nomi sono una grande idea clamorosa - facciamone di più!"
C'è qualcosa che non può essere realizzato senza di loro? Se è così, cos'è quella cosa?
C'è qualcosa di cui non si può fare facilmente a meno : l' ereditarietà delle classi correlate .
Ecco un esempio minimalista con le classi correlate Ae B:
class A(object):
class B(object):
def __init__(self, parent):
self.parent = parent
def make_B(self):
return self.B(self)
class AA(A): # Inheritance
class B(A.B): # Inheritance, same class name
pass
Questo codice porta a un comportamento abbastanza ragionevole e prevedibile:
>>> type(A().make_B())
<class '__main__.A.B'>
>>> type(A().make_B().parent)
<class '__main__.A'>
>>> type(AA().make_B())
<class '__main__.AA.B'>
>>> type(AA().make_B().parent)
<class '__main__.AA'>
Se Bfosse una classe di primo livello, non si potrebbe scrivere self.B()nel metodo make_Bma si scriverebbe semplicemente B(), perdendo così l' associazione dinamica alle classi adeguate.
Nota che in questa costruzione non dovresti mai fare riferimento alla classe Anel corpo della classe B. Questa è la motivazione per introdurre l' parentattributo in classe B.
Naturalmente, questo collegamento dinamico può essere ricreato senza classe interna al costo di una strumentazione noiosa e soggetta a errori delle classi.
Il caso d'uso principale per cui lo uso è prevenire la proliferazione di piccoli moduli e prevenire l'inquinamento dello spazio dei nomi quando non sono necessari moduli separati. Se sto estendendo una classe esistente, ma quella classe esistente deve fare riferimento a un'altra sottoclasse che dovrebbe sempre essere accoppiata ad essa. Ad esempio, potrei avere un utils.pymodulo che contiene molte classi helper, che non sono necessariamente accoppiate insieme, ma voglio rafforzare l'accoppiamento per alcune di quelle classi helper. Ad esempio, quando implemento https://stackoverflow.com/a/8274307/2718295
: utils.py:
import json, decimal
class Helper1(object):
pass
class Helper2(object):
pass
# Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats
class DecimalJSONEncoder(json.JSONEncoder):
class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched
def __init__(self, obj):
self._obj = obj
def __repr__(self):
return '{:f}'.format(self._obj)
def default(self, obj): # override JSONEncoder.default
if isinstance(obj, decimal.Decimal):
return self._repr_decimal(obj)
# else
super(self.__class__, self).default(obj)
# could also have inherited from object and used return json.JSONEncoder.default(self, obj)
Allora possiamo:
>>> from utils import DecimalJSONEncoder
>>> import json, decimal
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234'),
... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder)
{"key2": "key2_value", "key_1": 1.12345678901234}
Ovviamente, avremmo potuto evitare del json.JSONEnocdertutto l' ereditarietà e sovrascrivere semplicemente default ():
:
import decimal, json
class Helper1(object):
pass
def json_encoder_decimal(obj):
class _repr_decimal(float):
...
if isinstance(obj, decimal.Decimal):
return _repr_decimal(obj)
return json.JSONEncoder(obj)
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder)
'{"key1": 1.12345678901234}'
Ma a volte solo per convenzione, vuoi utilsessere composto da classi per l'estensibilità.
Ecco un altro caso d'uso: voglio una fabbrica per mutabili nella mia OuterClass senza dover invocare copy:
class OuterClass(object):
class DTemplate(dict):
def __init__(self):
self.update({'key1': [1,2,3],
'key2': {'subkey': [4,5,6]})
def __init__(self):
self.outerclass_dict = {
'outerkey1': self.DTemplate(),
'outerkey2': self.DTemplate()}
obj = OuterClass()
obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4)
assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6]
Preferisco questo modello al @staticmethoddecoratore che altrimenti useresti per una funzione di fabbrica.
I due modi mostrati prima sono funzionalmente identici. Tuttavia, ci sono alcune sottili differenze e ci sono situazioni in cui vorresti sceglierne una piuttosto che un'altra.
Metodo 1: definizione della classe annidata
(= "Classe annidata")
class MyOuter1:
class Inner:
def show(self, msg):
print(msg)
Metodo 2: con classe interna a livello di modulo collegata alla classe esterna
(= "classe interna referenziata")
class _InnerClass:
def show(self, msg):
print(msg)
class MyOuter2:
Inner = _InnerClass
Il carattere di sottolineatura viene utilizzato per seguire PEP8 "le interfacce interne (pacchetti, moduli, classi, funzioni, attributi o altri nomi) devono essere precedute da un singolo trattino basso iniziale".
Il frammento di codice riportato di seguito mostra le somiglianze funzionali della "Classe nidificata" rispetto alla "Classe interna referenziata"; Si comporterebbero allo stesso modo nel controllo del codice per il tipo di istanza di una classe interna. Inutile dire che m.inner.anymethod()si comporterebbe in modo simile con m1em2
m1 = MyOuter1()
m2 = MyOuter2()
innercls1 = getattr(m1, 'Inner', None)
innercls2 = getattr(m2, 'Inner', None)
isinstance(innercls1(), MyOuter1.Inner)
# True
isinstance(innercls2(), MyOuter2.Inner)
# True
type(innercls1()) == mypackage.outer1.MyOuter1.Inner
# True (when part of mypackage)
type(innercls2()) == mypackage.outer2.MyOuter2.Inner
# True (when part of mypackage)
Le differenze tra "Classe nidificata" e "Classe interna referenziata" sono elencate di seguito. Non sono grandi, ma a volte vorresti sceglierne uno o l'altro in base a questi.
Con "Classi annidate" è possibile incapsulare il codice meglio che con "Classe interna referenziata". Una classe nello spazio dei nomi del modulo è una variabile globale . Lo scopo delle classi annidate è ridurre il disordine nel modulo e inserire la classe interna nella classe esterna.
Anche se nessuno * lo utilizza from packagename import *, una quantità ridotta di variabili a livello di modulo può essere utile, ad esempio, quando si utilizza un IDE con completamento del codice / intellisense.
* Giusto?
La documentazione di Django indica di utilizzare Meta della classe interna per i metadati del modello. È un po 'più chiaro * indicare agli utenti del framework di scrivere un class Foo(models.Model)con inner class Meta;
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
invece di "scrivi a class _Meta, quindi scrivi a class Foo(models.Model)con Meta = _Meta";
class _Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
class Ox(models.Model):
Meta = _Meta
horn_length = models.IntegerField()
Con l'approccio "Classe annidata " il codice può essere letto in un elenco puntato annidato , ma con il metodo "Classe interna referenziata" è necessario scorrere indietro per vedere la definizione di _Metaper vedere i suoi "elementi figlio" (attributi).
Il metodo "Classe interna referenziata" può essere più leggibile se il livello di nidificazione del codice aumenta o se le righe sono lunghe per qualche altro motivo.
* Naturalmente, una questione di gusti
Questo non è un grosso problema, ma solo per completezza: quando si accede ad un attributo inesistente per la classe interna, vediamo eccezioni leggermente diverse. Continuando l'esempio fornito nella sezione 2:
innercls1.foo()
# AttributeError: type object 'Inner' has no attribute 'foo'
innercls2.foo()
# AttributeError: type object '_InnerClass' has no attribute 'foo'
Questo perché le types delle classi interne lo sono
type(innercls1())
#mypackage.outer1.MyOuter1.Inner
type(innercls2())
#mypackage.outer2._InnerClass
Ho usato le classi interne di Python per creare sottoclassi deliberatamente buggate all'interno di funzioni unittest (cioè all'interno def test_something():) al fine di avvicinarmi alla copertura del test del 100% (es. Testare dichiarazioni di registrazione molto raramente attivate sovrascrivendo alcuni metodi).
In retrospettiva è simile alla risposta di Ed https://stackoverflow.com/a/722036/1101109
Tali classi interne dovrebbero uscire dall'ambito ed essere pronte per la garbage collection una volta rimossi tutti i riferimenti ad esse. Ad esempio, prendi il seguente inner.pyfile:
class A(object):
pass
def scope():
class Buggy(A):
"""Do tests or something"""
assert isinstance(Buggy(), A)
Ottengo i seguenti risultati curiosi con OSX Python 2.7.6:
>>> from inner import A, scope
>>> A.__subclasses__()
[]
>>> scope()
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A, scope
>>> from inner import A
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A
>>> import gc
>>> gc.collect()
0
>>> gc.collect() # Yes I needed to call the gc twice, seems reproducible
3
>>> from inner import A
>>> A.__subclasses__()
[]
Suggerimento: non andare avanti e provare a farlo con i modelli Django, che sembravano mantenere altri riferimenti (memorizzati nella cache?) Alle mie classi con errori.
Quindi, in generale, non consiglierei di utilizzare classi interne per questo tipo di scopo a meno che non apprezzi davvero quella copertura del test al 100% e non puoi utilizzare altri metodi. Anche se penso che sia bello essere consapevoli che se usi il __subclasses__(), a volte può essere inquinato da classi interne. Ad ogni modo, se sei arrivato fin qui, penso che siamo abbastanza approfonditi in Python a questo punto, dunderscores privati e tutto il resto.
.__subclasses__()per capire come le classi interne interagiscono con il garbage collector quando le cose escono dallo scopo in Python. Questo sembra visivamente dominare il post, quindi i primi 1-3 paragrafi meritano un po 'più di espansione.