Les classes internes / imbriquées de Python me déroutent. Y a-t-il quelque chose qui ne peut être accompli sans eux? Si oui, quelle est cette chose?
98
Les classes internes / imbriquées de Python me déroutent. Y a-t-il quelque chose qui ne peut être accompli sans eux? Si oui, quelle est cette chose?
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 uneCacheMiss
exception. L'imbrication de l'exception sous la classe principaleDataLoader.CacheMiss
signifie que vous pouvez importer simplementDataLoader
mais toujours utiliser l'exception.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:
(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.)
la source
self
sans aucun travail supplémentaire requis (utilisez simplement un identifiant différent où vous mettriez généralement les intérieursself
; commeinnerself
), 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 leurowner
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:
est une instruction exécutée au moment de l'exécution comme celle-ci:
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:
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.
la source
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
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 ++
la source
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.
la source
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 deDoublyLinkedList
, 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à! »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
etB
:Ce code conduit à un comportement assez raisonnable et prévisible:
S'il
B
s'agissait d'une classe de premier niveau, vous ne pourriez pas écrireself.B()
dans la méthodemake_B
mais simplement écrireB()
, 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 classeB
. C'est la motivation pour introduire l'parent
attribut en classeB
.Bien entendu, cette liaison dynamique peut être recréée sans classe interne au prix d'une instrumentation fastidieuse et sujette aux erreurs des classes.
la source
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
:Alors nous pouvons:
Bien sûr, nous aurions pu éviter
json.JSONEnocder
complètement d' hériter et simplement remplacer default ()::
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
:Je préfère ce modèle au
@staticmethod
décorateur que vous utiliseriez autrement pour une fonction d'usine.la source
1. Deux méthodes fonctionnellement équivalentes
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")
Méthode 2: Avec la classe interne de niveau module attachée à la classe externe
(= "classe interne référencée")
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."
2. Similitudes
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 avecm1
etm2
3. Différences
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.
3.1 Encapsulation de code
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?
3.2 Lisibilité du code
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 innerclass Meta
;au lieu de "écrire un
class _Meta
, puis écrire unclass Foo(models.Model)
avecMeta = _Meta
";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
3.3 Messages d'erreur légèrement différents
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:
C'est parce que les
type
s des classes internes sontla source
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 :J'obtiens les résultats curieux suivants sous OSX Python 2.7.6:
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.la source
.__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.