Réponses:
Extrait de http://www.geekinterview.com/question_details/64739 :
Avantages de la classe interne:
- Regroupement logique des classes : si une classe n'est utile qu'à une seule autre classe, il est logique de l'intégrer dans cette classe et de garder les deux ensemble. L'imbrication de ces "classes d'assistance" rend leur package plus simple.
- Encapsulation accrue : considérez deux classes de niveau supérieur A et B où B a besoin d'accéder aux membres de A qui seraient autrement déclarés privés. En cachant la classe B au sein de la classe AA, les membres peuvent être déclarés privés et B peut y accéder. De plus, B lui-même peut être caché du monde extérieur.
- Code plus lisible et maintenable : l'imbrication de petites classes dans des classes de niveau supérieur rapproche le code de l'endroit où il est utilisé.
Le principal avantage est l'organisation. Tout ce qui peut être accompli avec des classes internes peut être accompli sans elles.
DataLoader
classe qui peut lever une CacheMiss
exception. L'imbrication de l'exception sous la classe principale DataLoader.CacheMiss
signifie que vous pouvez importer simplement DataLoader
mais toujours utiliser l'exception.
Y a-t-il quelque chose qui ne peut être accompli sans eux?
Non. Ils sont absolument équivalents à définir la classe normalement au niveau supérieur, puis à copier une référence à celle-ci dans la classe externe.
Je ne pense pas qu'il y ait une raison particulière pour laquelle les classes imbriquées sont «autorisées», à part cela n'a aucun sens de les «interdire» explicitement non plus.
Si vous recherchez une classe qui existe dans le cycle de vie de l'objet externe / propriétaire et qui a toujours une référence à une instance de la classe externe - les classes internes comme Java le fait - alors les classes imbriquées de Python ne sont pas cette chose. Mais vous pouvez pirater quelque chose comme ça:
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
(Cela utilise des décorateurs de classe, qui sont nouveaux dans Python 2.6 et 3.0. Sinon, vous devrez dire «Inner = innerclass (Inner)» après la définition de la classe.)
self
sans aucun travail supplémentaire requis (utilisez simplement un identifiant différent où vous mettriez généralement les intérieurs self
; comme innerself
), et pourrez accéder à l'instance externe via cela.
WeakKeyDictionary
dans cet exemple n'autorise pas réellement le ramassage des ordures des clés, car les valeurs référencent fortement leurs clés respectives via leur owner
attribut.
Vous devez comprendre quelque chose pour comprendre cela. Dans la plupart des langages, les définitions de classe sont des directives pour le compilateur. Autrement dit, la classe est créée avant l'exécution du programme. En python, toutes les instructions sont exécutables. Cela signifie que cette déclaration:
class foo(object):
pass
est une instruction exécutée au moment de l'exécution comme celle-ci:
x = y + z
Cela signifie que non seulement vous pouvez créer des classes dans d'autres classes, mais vous pouvez également créer des classes où vous le souhaitez. Considérez ce code:
def foo():
class bar(object):
...
z = bar()
Ainsi, l'idée d'une «classe interne» n'est pas vraiment une construction de langage; c'est une construction de programmeur. Guido a un très bon résumé de la façon dont cela s'est produit ici . Mais essentiellement, l'idée de base est que cela simplifie la grammaire de la langue.
Imbrication de classes dans les classes:
Les classes imbriquées gonflent la définition de classe, ce qui rend plus difficile de voir ce qui se passe.
Les classes imbriquées peuvent créer un couplage qui rendrait les tests plus difficiles.
En Python, vous pouvez mettre plus d'une classe dans un fichier / module, contrairement à Java, de sorte que la classe reste toujours proche de la classe de niveau supérieur et pourrait même avoir le nom de classe préfixé avec un "_" pour aider à signifier que les autres ne devraient pas être En l'utilisant.
L'endroit où les classes imbriquées peuvent s'avérer utiles est dans les fonctions
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
La classe capture les valeurs de la fonction vous permettant de créer dynamiquement une classe comme la métaprogrammation de modèle en C ++
Je comprends les arguments contre les classes imbriquées, mais il y a lieu de les utiliser dans certaines occasions. Imaginez que je crée une classe de liste à double lien et que je dois créer une classe de nœuds pour maintenir les nœuds. J'ai deux choix, créer la classe Node dans la classe DoublyLinkedList, ou créer la classe Node en dehors de la classe DoublyLinkedList. Je préfère le premier choix dans ce cas, car la classe Node n'a de sens qu'à l'intérieur de la classe DoublyLinkedList. Bien qu'il n'y ait aucun avantage de masquage / encapsulation, il y a un avantage de regroupement à pouvoir dire que la classe Node fait partie de la classe DoublyLinkedList.
Node
classe n'est pas utile pour d'autres types de classes de liste chaînée que vous pourriez également créer, auquel cas elle devrait probablement être juste à l'extérieur.
Node
est sous l'espace de noms de DoublyLinkedList
, et il est logique de l'être. C'est Pythonic: « Les espaces de noms sont une klaxonnant grande idée - nous allons faire plus de ceux - là! »
Y a-t-il quelque chose qui ne peut être accompli sans eux? Si oui, quelle est cette chose?
Il y a quelque chose qui ne peut pas être facilement fait sans : l' héritage des classes liées .
Voici un exemple minimaliste avec les classes associées A
et 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
Ce code conduit à un comportement assez raisonnable et prévisible:
>>> 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'>
S'il B
s'agissait d'une classe de premier niveau, vous ne pourriez pas écrire self.B()
dans la méthode make_B
mais simplement écrire B()
, et ainsi perdre la liaison dynamique aux classes adéquates.
Notez que dans cette construction, vous ne devez jamais faire référence à une classe A
dans le corps de la classe B
. C'est la motivation pour introduire l' parent
attribut en classe B
.
Bien entendu, cette liaison dynamique peut être recréée sans classe interne au prix d'une instrumentation fastidieuse et sujette aux erreurs des classes.
Le cas d'utilisation principal pour lequel j'utilise ceci est la prévention de la prolifération de petits modules et la prévention de la pollution de l'espace de noms lorsque des modules séparés ne sont pas nécessaires. Si j'étends une classe existante, mais que cette classe existante doit référencer une autre sous-classe qui doit toujours lui être couplée. Par exemple, je peux avoir un utils.py
module qui contient de nombreuses classes d'assistance, qui ne sont pas nécessairement couplées entre elles, mais je veux renforcer le couplage pour certaines de ces classes d'assistance. Par exemple, lorsque j'implémente 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)
Alors nous pouvons:
>>> 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}
Bien sûr, nous aurions pu éviter json.JSONEnocder
complètement d' hériter et simplement remplacer 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}'
Mais parfois, juste pour la convention, vous voulez utils
être composé de classes pour l'extensibilité.
Voici un autre cas d'utilisation: je veux une fabrique pour les mutables dans mon OuterClass sans avoir à appeler 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]
Je préfère ce modèle au @staticmethod
décorateur que vous utiliseriez autrement pour une fonction d'usine.
Les deux méthodes illustrées précédemment sont fonctionnellement identiques. Cependant, il existe des différences subtiles et il y a des situations où vous voudriez choisir l'un plutôt que l'autre.
Méthode 1: définition de classe imbriquée
(= "classe imbriquée")
class MyOuter1:
class Inner:
def show(self, msg):
print(msg)
Méthode 2: Avec la classe interne de niveau module attachée à la classe externe
(= "classe interne référencée")
class _InnerClass:
def show(self, msg):
print(msg)
class MyOuter2:
Inner = _InnerClass
Le soulignement est utilisé pour suivre PEP8 "Les interfaces internes (packages, modules, classes, fonctions, attributs ou autres noms) doivent - être précédées d'un seul trait de soulignement."
L'extrait de code ci-dessous illustre les similitudes fonctionnelles de la «classe imbriquée» et de la «classe interne référencée»; Ils se comporteraient de la même manière dans le code de vérification du type d'une instance de classe interne. Inutile de dire que le m.inner.anymethod()
se comporterait de la même manière avec m1
etm2
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)
Les différences de «Classe imbriquée» et «Classe interne référencée» sont répertoriées ci-dessous. Ils ne sont pas grands, mais parfois vous aimeriez choisir l'un ou l'autre en fonction de ceux-ci.
Avec "Classes imbriquées", il est possible d'encapsuler le code mieux qu'avec "Classe interne référencée". Une classe dans l'espace de noms du module est une variable globale . Le but des classes imbriquées est de réduire l'encombrement dans le module et de placer la classe interne dans la classe externe.
Bien que personne ne l'utilise from packagename import *
, une faible quantité de variables au niveau du module peut être intéressante, par exemple lors de l'utilisation d'un IDE avec complétion de code / intellisense.
* N'est-ce pas?
La documentation de Django indique d'utiliser la classe interne Meta pour les métadonnées du modèle. Il est un peu plus clair * de demander aux utilisateurs du framework d'écrire un class Foo(models.Model)
avec inner class Meta
;
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
au lieu de "écrire un class _Meta
, puis écrire un class Foo(models.Model)
avec Meta = _Meta
";
class _Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
class Ox(models.Model):
Meta = _Meta
horn_length = models.IntegerField()
Avec l'approche "Classe imbriquée", le code peut être lu dans une liste à puces imbriquée , mais avec la méthode "Classe interne référencée", il faut faire défiler vers le haut pour voir la définition de _Meta
pour voir ses "éléments enfants" (attributs).
La méthode "Classe interne référencée" peut être plus lisible si votre niveau d'imbrication de code augmente ou si les lignes sont longues pour une autre raison.
* Bien sûr, une question de goût
Ce n'est pas un gros problème, mais juste pour être complet: lors de l'accès à un attribut inexistant pour la classe interne, nous voyons des exceptions légèrement différentes. Poursuivant l'exemple donné dans la section 2:
innercls1.foo()
# AttributeError: type object 'Inner' has no attribute 'foo'
innercls2.foo()
# AttributeError: type object '_InnerClass' has no attribute 'foo'
C'est parce que les type
s des classes internes sont
type(innercls1())
#mypackage.outer1.MyOuter1.Inner
type(innercls2())
#mypackage.outer2._InnerClass
J'ai utilisé les classes internes de Python pour créer des sous-classes délibérément boguées dans les fonctions unittest (c'est-à-dire à l'intérieur def test_something():
) afin de me rapprocher de la couverture de test à 100% (par exemple, tester très rarement les déclarations de journalisation déclenchées en remplaçant certaines méthodes).
Rétrospectivement, c'est similaire à la réponse d'Ed https://stackoverflow.com/a/722036/1101109
Ces classes internes doivent sortir du champ d'application et être prêtes pour le garbage collection une fois que toutes les références à celles-ci ont été supprimées. Par exemple, prenez le inner.py
fichier suivant :
class A(object):
pass
def scope():
class Buggy(A):
"""Do tests or something"""
assert isinstance(Buggy(), A)
J'obtiens les résultats curieux suivants sous 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__()
[]
Astuce - N'essayez pas de faire cela avec les modèles Django, qui semblaient garder d'autres références (en cache?) À mes classes boguées.
Donc, en général, je ne recommanderais pas d'utiliser des classes internes pour ce genre d'objet, à moins que vous ne valorisiez vraiment cette couverture de test à 100% et que vous ne puissiez pas utiliser d'autres méthodes. Même si je pense qu'il est agréable de savoir que si vous utilisez le __subclasses__()
, il peut parfois être pollué par les classes internes. Quoi qu'il en soit, si vous avez suivi jusqu'ici, je pense que nous sommes assez profondément dans Python à ce stade, les dunderscores privés et tout.
.__subclasses__()
pour comprendre comment les classes internes interagissent avec le ramasse-miettes lorsque les choses sortent du cadre de Python. Cela semble visuellement dominer le message, donc les 1 à 3 premiers paragraphes méritent un peu plus d'élargissement.