web-dev-qa-db-fra.com

Est-il correct d'avoir plusieurs classes dans le même fichier en Python?

J'arrive tout juste dans le monde Python après des années de Java et PHP. Bien que le langage lui-même soit assez simple, je me bats avec certains " des problèmes mineurs que je ne peux pas comprendre - et auxquels je n'ai pas trouvé de réponses dans les nombreux documents et tutoriels que j'ai lus jusqu'ici.

Pour le praticien expérimenté Python, cette question peut sembler idiote, mais je veux vraiment une réponse pour que je puisse aller plus loin avec le langage:

Dans Java et PHP ( bien que pas strictement requis ), vous devez écrire chaque class sur son propre fichier, avec le nom du fichier est celui de class comme meilleure pratique.

Mais en Python, ou du moins dans les tutoriels que j'ai vérifiés, il est ok d'avoir plusieurs classes dans le même fichier.

Cette règle s'applique-t-elle à la production, au code prêt à être déployé ou est-elle effectuée uniquement par souci de concision dans le code éducatif uniquement?

21
Olivier Malki

Est-il correct d'avoir plusieurs classes dans le même fichier en Python?

Oui. Tant d'un point de vue philosophique que pratique.

En Python, les modules sont un espace de noms qui existe une fois en mémoire.

Supposons que nous ayons la structure de répertoires hypothétique suivante, avec une classe définie par fichier:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

Toutes ces classes sont disponibles dans le module collections et (il y en a en fait 25 au total) définies dans le module de bibliothèque standard dans _collections_abc.py

Il y a quelques problèmes ici qui, je pense, rendent le _collections_abc.py supérieur à la structure de répertoires hypothétique alternative.

  • Ces fichiers sont classés par ordre alphabétique. Vous pouvez les trier d'autres manières, mais je ne connais pas de fonctionnalité qui trie les fichiers par dépendances sémantiques. La source du module _collections_abc est organisée par dépendance.
  • Dans les cas non pathologiques, les modules et les définitions de classe sont des singletons, se produisant une fois chacun en mémoire. Il y aurait un mappage bijectif des modules sur les classes - rendant les modules redondants.
  • Le nombre croissant de fichiers rend la lecture des classes plus simple (sauf si vous avez un IDE qui le rend simple) - le rendant moins accessible aux personnes sans outils.

Êtes-vous empêché de diviser des groupes de classes en différents modules lorsque vous le jugez souhaitable du point de vue de l'espace de noms et de l'organisation?

Non.

Du Zen of Python , qui reflète la philosophie et les principes sous lesquels il a grandi et évolué:

Les espaces de noms sont une excellente idée de klaxon - faisons-en plus!

Mais gardons à l'esprit qu'il dit aussi:

L'appartement est meilleur que l'emboîtement.

Python est incroyablement propre et facile à lire. Il vous encourage à le lire. Placer chaque classe distincte dans un fichier séparé décourage la lecture. Cela va à l'encontre de la philosophie de base de Python. Regardez la structure de la bibliothèque standard , la grande majorité des modules sont des modules à fichier unique, pas des packages. Je vous soumets que le code idiomatique Python est écrit dans le même style que la bibliothèque standard CPython.

Voici le code réel du module de classe de base abstrait . J'aime l'utiliser comme référence pour la dénotation de divers types abstraits dans la langue.

Diriez-vous que chacune de ces classes devrait nécessiter un fichier distinct?

class Hashable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __hash__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Hashable:
            try:
                for B in C.__mro__:
                    if "__hash__" in B.__dict__:
                        if B.__dict__["__hash__"]:
                            return True
                        break
            except AttributeError:
                # Old-style class
                if getattr(C, "__hash__", None):
                    return True
        return NotImplemented


class Iterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            if _hasattr(C, "__iter__"):
                return True
        return NotImplemented

Iterable.register(str)


class Iterator(Iterable):

    @abstractmethod
    def next(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterator:
            if _hasattr(C, "next") and _hasattr(C, "__iter__"):
                return True
        return NotImplemented


class Sized:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if _hasattr(C, "__len__"):
                return True
        return NotImplemented


class Container:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if _hasattr(C, "__contains__"):
                return True
        return NotImplemented


class Callable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __call__(self, *args, **kwds):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Callable:
            if _hasattr(C, "__call__"):
                return True
        return NotImplemented

Devraient-ils donc chacun avoir leur propre dossier?

J'espère que non.

Ces fichiers ne sont pas seulement du code - ils sont de la documentation sur la sémantique de Python.

Ce sont peut-être 10 à 20 lignes en moyenne. Pourquoi devrais-je avoir à aller dans un fichier complètement séparé pour voir 10 autres lignes de code? Ce serait très impraticable. De plus, il y aurait des importations de passe-partout presque identiques sur chaque fichier, ajoutant des lignes de code redondantes.

Je trouve assez utile de savoir qu'il existe un seul module où je peux trouver toutes ces classes de base abstraites, au lieu d'avoir à parcourir une liste de modules. Les visualiser en contexte les uns avec les autres me permet de mieux les comprendre. Quand je vois qu'un Iterator est un Iterable, je peux rapidement revoir en quoi consiste un Iterable en levant les yeux.

Je finis parfois par avoir quelques cours très courts. Ils restent dans le dossier, même s'ils doivent s'agrandir avec le temps. Parfois, les modules matures ont plus de 1000 lignes de code. Mais ctrl-f est facile, et certains IDE facilitent la visualisation des contours du fichier - donc quelle que soit la taille du fichier, vous pouvez rapidement accéder à l'objet ou à la méthode que vous recherchez.

Conclusion

Ma direction, dans le contexte de Python, est de préférer conserver les définitions de classe apparentées et sémantiquement similaires dans le même fichier. Si le fichier devient si volumineux qu'il devient trop lourd, envisagez une réorganisation.

15
Aaron Hall

Lors de la structuration de votre application en Python, vous devez penser en termes de packages et de modules.

Les modules concernent les fichiers dont vous parlez. C'est bien d'avoir un tas de classes dans le même module. L'objectif est que toutes les classes d'un même module servent le même objectif/la même logique. Si le module dure trop longtemps, pensez à le subdiviser en repensant votre logique.

N'oubliez pas de lire de temps en temps sur Index of Python Enhancement Proposals .

4
user132583

La vraie réponse à cela est générale et ne dépend pas du langage utilisé: ce qui doit être dans un fichier ne dépend pas principalement du nombre de classes qu'il définit. Cela dépend de la connectivité logique et de la complexité. Période.

Ainsi, si vous avez quelques très petites classes très interconnectées, elles doivent être regroupées dans le même fichier. Vous devez diviser une classe si elle n'est pas étroitement connectée à une autre classe ou si elle est trop complexe pour être incluse dans une autre classe.

Cela dit, la règle d'une classe par fichier est généralement une bonne heuristique. Cependant, il existe des exceptions importantes: une petite classe d'assistance qui n'est en fait que le détail d'implémentation de sa seule classe d'utilisateurs doit généralement être intégrée dans le fichier de cette classe d'utilisateurs. De même, si vous avez trois classes vector2, vector3, et vector4, il y a peu de raisons de les implémenter dans des fichiers séparés.