Parfois, il est logique de regrouper les données liées. J'ai tendance à le faire avec un dicton, par exemple,
self.group = dict(a=1, b=2, c=3)
print self.group['a']
Un de mes collègues préfère créer une classe
class groupClass(object):
def __init__(a, b, c):
self.a = a
self.b = b
self.c = c
self.group = groupClass(1, 2, 3)
print self.group.a
Notez que nous ne définissons aucune méthode de classe.
J'aime utiliser un dict car j'aime minimiser le nombre de lignes de code. Mon collègue pense que le code est plus lisible si vous utilisez une classe et qu'il sera plus facile d'ajouter des méthodes à la classe à l'avenir.
Lequel tu préfères, et pourquoi?
Si vous ne définissez vraiment aucune méthode de classe, un dict ou un namedtuple a beaucoup plus de sens, à mon avis. Simple + intégré c'est bien! À chacun, cependant.
Contexte
R. Hettinger a présenté un résumé des conteneurs de données alternatifs basés sur des attributs lors de la réunion de vacances 2017 de SF Python. Voir son Tweet et son jeu de diapositives . Il a également donné un talk à PyCon 2018 sur les dataclasses.
D'autres types de conteneurs de données sont mentionnés dans ce article et principalement dans Python 3 (voir les liens ci-dessous).
Voici une discussion sur la liste de diffusion python-ideas sur l'ajout de recordclass
à la bibliothèque standard.
Options
Alternatives dans la bibliothèque standard
collections.namedtuple
: Tuple avec attributs (voir séminal recette )typing.NamedTuple
: Tuple sous-classable (voir ceci post le comparer avec namedtuple
)types.SimpleNamespace
: classe simple avec déclaration de classe facultativetypes.MappingProxy
: dict en lecture seuleenum.Enum
: collection contrainte de constantes connexes (se comporte comme une classe)dataclasses.dataclass
: nomable mutable avec classes par défaut/sans passe-partoutOptions externes
SimpleNamedspace
)Lequel?
Le choix de l'option à utiliser dépend de la situation (voir les exemples ci-dessous). Habituellement, un dictionnaire mutable à l'ancienne ou un tuple nommé immuable est assez bon. Les classes de données sont le plus récent ajout (Python 3.7a) offrant à la fois la mutabilité et immuabilité en option , avec la promesse d'une réduction du nombre de points d'inspiration inspirée par le projet attrs .
Exemples
import typing as typ
import collections as ct
import dataclasses as dc
# Problem: You want a simple container to hold personal data.
# Solution: Try a NamedTuple.
>>> class Person(typ.NamedTuple):
... name: str
... age: int
>>> a = Person("bob", 30)
>>> a
Person(name='bob', age=30)
# Problem: You need to change age each year, but namedtuples are immutable.
# Solution: Use assignable attributes of a traditional class.
>>> class Person:
... def __init__(self, name, age):
... self.name = name
... self.age = age
>>> b = Person("bob", 30)
>>> b.age = 31
>>> b
<__main__.Person at 0x4e27128>
# Problem: You lost the pretty repr and want to add comparison features.
# Solution: Use included repr and eq features from the new dataclasses.
>>> @dc.dataclass(eq=True)
... class Person:
... name: str
... age: int
>>> c = Person("bob", 30)
>>> c.age = 31
>>> c
Person(name='bob', age=31)
>>> d = Person("dan", 31)
>>> c != d
True
Je préfère suivre YAGNI et utiliser un dict.
Il y a une nouvelle proposition qui vise à implémenter exactement ce que vous cherchez, appelée classes de données . Jetez un coup d'oeil.
Utiliser une classe plutôt qu'un dict est une question de préférence. Personnellement, je préfère utiliser un dict lorsque les clés ne sont pas connues a priori. (En tant que conteneur de mappage).
L'utilisation d'une classe pour stocker des données signifie que vous pouvez fournir de la documentation sur les attributs de classe.
Personnellement, la principale raison pour laquelle j'utilise une classe est peut-être d'utiliser la fonction de saisie automatique des IDE! (techniquement une raison boiteuse, mais très utile dans la pratique)
Soit dit en passant, je pense que Python 3.7 implémenté @ dataclass est le moyen le plus simple et le plus efficace pour implémenter des classes en tant que conteneurs de données.
@dataclass
class Data:
a: list
b: str #default variables go after non default variables
c: bool = False
def func():
return A(a="hello")
print(func())
La sortie serait: hello
Elle est trop similaire à Scala comme classe de cas et la façon la plus simple d'utiliser une classe comme conteneur.
Votre chemin est meilleur. N'essayez pas d'anticiper trop l'avenir car vous ne réussirez probablement pas.
Cependant, il peut parfois être judicieux d'utiliser quelque chose comme une structure C , par exemple si vous souhaitez identifier différents types plutôt que d'utiliser des dict pour tout.
Vous pouvez combiner ensemble les avantages de dict et class, en utilisant une classe wrapper héritée de dict. Vous n'avez pas besoin d'écrire du code passe-partout, et en même temps, vous pouvez utiliser la notation par points.
class ObjDict(dict):
def __getattr__(self,attr):
return self[attr]
def __setattr__(self,attr,value):
self[attr]=value
self.group = ObjDict(a=1, b=2, c=3)
print self.group.a
Je ne suis pas d'accord que le code est plus lisible en utilisant une classe sans méthodes. Vous attendez généralement des fonctionnalités d'une classe, pas seulement des données.
Donc, j'irais pour un dict jusqu'à ce que le besoin de fonctionnalité se fasse sentir, puis le constructeur de la classe pourrait recevoir un dict :-)
Qu'en est-il de Prodict :
group = Prodict(a=1, b=2, c=3)
group.d = 4
Et si vous voulez que la conversion de type automatique et le code automatique soient complets (intelli-sense):
class Person(Prodict):
name: str
email: str
rate: int
john = Person(name='John', email='[email protected]')
john.rate = 7
john.age = 35 # dynamic
Dans une langue qui le prend en charge, j'utiliserais un struct
. Un dictionnaire serait le plus proche d'une structure en Python, du moins pour autant que je le vois.
Sans oublier, vous pouvez quand même ajouter une méthode à un dictionnaire si vous vraiment le vouliez;)
Un dicton est évidemment approprié pour cette situation. Il a été conçu spécifiquement pour ce cas d'utilisation. À moins que vous n'utilisiez réellement la classe en tant que classe, il ne sert à rien de réinventer la roue et de surcharger/gaspiller l'espace supplémentaire d'une classe qui agit comme un mauvais dictionnaire (pas de fonctionnalités de dictionnaire).
Si l'on ne se soucie pas de l'empreinte mémoire, alors dict , namedtuple , dataclass ou juste une classe avec __slots__
Sont de bons choix.
Mais si l'on doit créer des millions d'objets avec quelques attributs, il existe une solution basée sur la bibliothèque recordclass :
from recordclass import make_dataclass
C = make_dataclass("C", ('a', 'b', 'c'))
c = C(1, 2, 3)
Même chose avec une définition de classe:
from recordclass import dataobject
class C(dataobject):
a:int
b:int
c:int
c = C(1, 2, 3)
Il a une empreinte mémoire minimale = sizeof(PyObject_HEAD) + 3*sizeof(PyObject*)
octets.
Pour comparaison, les variantes basées sur __slots__
Nécessitent sizeof(PyGC_Head) + sizeof(PyObject_HEAD) + 3*sizeof(PyObject*)
octets.