Dois-je utiliser une classe ou un dictionnaire?

99

J'ai une classe qui ne contient que des champs et aucune méthode, comme ceci:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Cela pourrait facilement être traduit en dict. La classe est plus flexible pour les ajouts futurs et pourrait être rapide avec __slots__. Y aurait-il donc un avantage à utiliser un dict à la place? Un dict serait-il plus rapide qu'une classe? Et plus rapide qu'une classe avec des machines à sous?

démon
la source
2
J'utilise toujours des dictionnaires pour conserver les données, et cela me semble être un cas d'utilisation. dans certains cas, dériver une classe de dictpeut avoir un sens, vers. avantage: lorsque vous déboguez, dites simplement print(request)et vous verrez facilement toutes les informations d'état. avec l'approche plus classique, vous devrez écrire vos __str__méthodes personnalisées , ce qui est nul si vous devez le faire tout le temps.
flux du
Ils sont interchangeables stackoverflow.com/questions/1305532/…
Oleksiy
Si la classe a tout son sens et est claire pour les autres, pourquoi pas? De plus, si vous définissez de nombreuses classes avec des interfaces communes par exemple, pourquoi pas? Mais Python ne prend pas en charge de puissants concepts orientés objet comme C ++.
MasterControlProgram
3
@Ralf quelle POO ne prend pas en charge python?
qwr

Réponses:

32

Pourquoi voudriez-vous en faire un dictionnaire? Quel est l'avantage? Que se passe-t-il si vous souhaitez ajouter plus tard du code? Où irait votre __init__code?

Les classes sont destinées à regrouper les données associées (et généralement le code).

Les dictionnaires servent à stocker les relations clé-valeur, où généralement les clés sont toutes du même type et toutes les valeurs sont également d'un même type. Parfois, ils peuvent être utiles pour regrouper des données lorsque les noms de clé / attribut ne sont pas tous connus à l'avance, mais cela indique souvent que quelque chose ne va pas avec votre conception.

Gardez cela une classe.

adw
la source
Je créerais une sorte de méthode d'usine créant un dict au lieu de la __init__méthode de classe . Mais vous avez raison: je séparerais des choses qui vont ensemble.
deamon
88
ne peut pas être plus en désaccord avec vous: les dictionnaires, les ensembles, les listes et les tuples sont tous là pour regrouper les données associées. en aucun cas il n'y a de supposition que les valeurs d'un dictionnaire doivent ou doivent être du même type de données, bien au contraire. dans de nombreuses listes et ensembles, les valeurs auront le même type, mais c'est surtout parce que c'est ce que nous aimons énumérer ensemble. je pense en fait que l'utilisation généralisée des classes pour conserver des données est un abus de oop; lorsque vous pensez aux problèmes de sérialisation, vous pouvez facilement comprendre pourquoi.
flux du
4
C'est une programmation orientée OBJET et non orientée classe pour une raison: nous avons affaire à des objets. Un objet est caractérisé par 2 (3) propriétés: 1. état (membres) 2. comportement (méthodes) et 3. instance peuvent être décrits en plusieurs mots. Par conséquent, les classes sont destinées à regrouper l'état et le comportement.
friendzis
14
J'ai noté cela parce que vous devriez toujours utiliser par défaut la structure de données la plus simple lorsque cela est possible. Dans ce cas, un dictionnaire suffit pour le but recherché. La question where would your __init__ code go?est préoccupante. Cela pourrait persuader un développeur moins expérimenté que seules les classes doivent être utilisées puisqu'une méthode init n'est pas utilisée dans un dictionnaire. Absurde.
Lloyd Moore
1
@Ralf Une classe Foo est simplement un type, comme int et string. Stockez-vous la valeur dans un type entier ou une variable foo de type entier? Différence sémantique subtile mais importante. Dans des langues comme C, cette distinction n'est pas trop pertinente en dehors des cercles académiques. Bien que la plupart des langages OO prennent en charge les variables de classe, ce qui rend la distinction entre classe et objet / instance extrêmement pertinente - stockez-vous des données en classe (partagées entre toutes les instances) ou dans un objet spécifique? Ne vous faites pas mordre
Friendzis
44

Utilisez un dictionnaire sauf si vous avez besoin du mécanisme supplémentaire d'une classe. Vous pouvez également utiliser a namedtuplepour une approche hybride:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

Les différences de performances ici seront minimes, même si je serais surpris si le dictionnaire n'était pas plus rapide.

Katriel
la source
15
"Les différences de performances ici seront minimes , même si je serais surpris si le dictionnaire n'était pas beaucoup plus rapide." Cela ne calcule pas. :)
mipadi
1
@mipadi: c'est vrai. Maintenant corrigé: p
Katriel
Le dict est 1,5 fois plus rapide que le namedtuple et deux fois plus rapide qu'une classe sans slots. Vérifiez mon message sur cette réponse.
alexpinho98
@ alexpinho98: J'ai essayé de trouver le «message» auquel vous faites référence, mais je ne le trouve pas! pouvez-vous fournir l'URL. Merci!
Dan Oblinger
@DanOblinger Je suppose qu'il veut dire sa réponse ci-dessous.
Adam Lewis
37

Une classe en python est un dict en dessous. Vous obtenez une surcharge avec le comportement de la classe, mais vous ne pourrez pas le remarquer sans un profileur. Dans ce cas, je pense que vous bénéficiez de la classe parce que:

  • Toute votre logique vit dans une seule fonction
  • Il est facile à mettre à jour et reste encapsulé
  • Si vous modifiez quelque chose plus tard, vous pouvez facilement garder l'interface la même
Bruce Armstrong
la source
Toute votre logique ne vit pas dans une seule fonction. Une classe est un tuple d'état partagé et généralement une ou plusieurs méthodes. Si vous changez une classe, vous n'avez aucune garantie sur son interface.
Lloyd Moore
25

Je pense que l'utilisation de chacun est beaucoup trop subjective pour que je puisse entrer là-dessus, alors je vais m'en tenir aux chiffres.

J'ai comparé le temps nécessaire pour créer et changer une variable dans un dict, une classe new_style et une classe new_style avec des slots.

Voici le code que j'ai utilisé pour le tester (c'est un peu brouillon mais ça fait le travail.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

Et voici la sortie ...

Création ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Changer une variable ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Donc, si vous ne faites que stocker des variables, vous avez besoin de vitesse, et cela ne vous obligera pas à faire beaucoup de calculs, je vous recommande d'utiliser un dict (vous pouvez toujours créer une fonction qui ressemble à une méthode). Mais, si vous avez vraiment besoin de classes, rappelez-vous - utilisez toujours __ slots __ .

Remarque:

J'ai testé la «classe» avec les classes new_style et old_style. Il s'avère que les classes old_style sont plus rapides à créer mais plus lentes à modifier (pas de beaucoup mais significatives si vous créez beaucoup de classes dans une boucle serrée (astuce: vous le faites mal)).

Les temps de création et de modification des variables peuvent également différer sur votre ordinateur car le mien est ancien et lent. Assurez-vous de le tester vous-même pour voir les «vrais» résultats.

Éditer:

J'ai testé plus tard le namedtuple: je ne peux pas le modifier mais pour créer les 10000 échantillons (ou quelque chose comme ça), il a fallu 1,4 seconde, donc le dictionnaire est en effet le plus rapide.

Si je change la fonction dict pour inclure les clés et les valeurs et pour renvoyer le dict au lieu de la variable contenant le dict lorsque je le crée, cela me donne 0,65 au lieu de 0,8 seconde.

class Foo(dict):
    pass

La création est comme une classe avec des slots et la modification de la variable est la plus lente (0,17 seconde) donc n'utilisez pas ces classes . optez pour un dict (speed) ou pour la classe dérivée de object ('syntax candy')

alexpinho98
la source
J'aimerais voir les nombres pour une sous-classe de dict(sans méthodes remplacées, je suppose?). Fonctionne-t-elle de la même manière qu'une classe de style nouveau écrite à partir de zéro?
Benjamin Hodgson
12

Je suis d'accord avec @adw. Je ne représenterais jamais un "objet" (au sens OO) avec un dictionnaire. Les dictionnaires regroupent les paires nom / valeur. Les classes représentent des objets. J'ai vu du code où les objets sont représentés avec des dictionnaires et on ne sait pas quelle est la forme réelle de la chose. Que se passe-t-il lorsque certains noms / valeurs ne sont pas présents? Ce qui empêche le client de mettre quoi que ce soit. Ou d'essayer de faire sortir quoi que ce soit. La forme de la chose doit toujours être clairement définie.

Lorsque vous utilisez Python, il est important de construire avec discipline car le langage permet à l'auteur de se tirer une balle dans le pied de plusieurs façons.

Jaydel
la source
9
Les réponses sur SO sont triées par votes, et les réponses avec le même décompte de voix sont classées au hasard. Veuillez donc préciser qui vous entendez par «la dernière affiche».
Mike DeSimone
4
Et comment savez-vous que quelque chose qui n'a que des champs et aucune fonctionnalité est un «objet» au sens OO?
Muhammad Alkarouri
1
Mon test décisif est "la structure de ces données est-elle fixe?". Si oui, utilisez un objet, sinon un dict. S'écarter de cela ne fera que créer de la confusion.
weberc2
Vous devez analyser le contexte du problème que vous résolvez. Les objets doivent être relativement évidents une fois que vous êtes familiarisé avec ce paysage. Personnellement, je pense que retourner un dictionnaire devrait être votre dernier recours, sauf si ce que vous retournez DEVRAIT être un mappage de paires nom / valeur. J'ai vu trop de code bâclé où tout ce qui est passé et évacué des méthodes n'est qu'un dictionnaire. C'est paresseux. J'adore les langages typés dynamiquement, mais j'aime aussi que mon code soit clair et logique. Tout mettre dans un dictionnaire peut être une commodité qui cache le sens.
jaydel
5

Je recommanderais une classe, car ce sont toutes sortes d'informations impliquées dans une demande. Si quelqu'un utilisait un dictionnaire, je m'attendrais à ce que les données stockées soient de nature beaucoup plus similaire. Une ligne directrice que j'ai tendance à suivre moi-même est que si je veux faire une boucle sur l'ensemble des paires clé-> valeur et faire quelque chose, j'utilise un dictionnaire. Sinon, les données ont apparemment beaucoup plus de structure qu'un mappage clé-> valeur de base, ce qui signifie qu'une classe serait probablement une meilleure alternative.

Par conséquent, restez avec la classe.

Stigmate
la source
2
Je suis totalement en désaccord. Il n'y a aucune raison de restreindre l'utilisation des dictionnaires aux choses sur lesquelles itérer. Ils servent à maintenir une cartographie.
Katriel
1
Lisez un peu plus attentivement, s'il vous plaît. J'ai dit que je voulais peut-être faire une boucle , pas la volonté . Pour moi, utiliser un dictionnaire implique qu'il existe une grande similitude fonctionnelle entre les clés et les valeurs. Par exemple, des noms avec des âges. Utiliser une classe signifierait que les différentes «clés» ont des valeurs avec des significations très différentes. Par exemple, prenez une classe PErson. Ici, «nom» serait une chaîne et «amis» pourrait être une liste, ou un dictionnaire, ou un autre objet approprié. Vous ne bouclez pas sur tous ces attributs dans le cadre de l'utilisation normale de cette classe.
Stigma
1
Je pense que la distinction entre les classes et les dictionnaires est rendue indistincte en Python par le fait que les premiers sont implémentés en utilisant les seconds (nonobstant les 'slots'). Je sais que cela m'a un peu dérouté quand j'ai appris le langage pour la première fois (avec le fait que les classes étaient des objets et donc des instances de certaines métaclasses mystérieuses).
martineau
4

Si tout ce que vous voulez obtenir est un bonbon de syntaxe obj.bla = 5au lieu de obj['bla'] = 5, surtout si vous devez répéter cela beaucoup, vous voudrez peut-être utiliser une classe de conteneur simple comme dans la suggestion de martineaus. Néanmoins, le code y est assez gonflé et inutilement lent. Vous pouvez rester simple comme ça:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Une autre raison de passer à namedtuples ou à une classe avec __slots__pourrait être l'utilisation de la mémoire. Les dictionnaires nécessitent beaucoup plus de mémoire que les types de liste, donc cela pourrait être un point à considérer.

Quoi qu'il en soit, dans votre cas spécifique, il ne semble pas y avoir de motivation pour abandonner votre implémentation actuelle. Vous ne semblez pas gérer des millions de ces objets, donc aucun type dérivé de liste n'est requis. Et il contient en fait une logique fonctionnelle dans le __init__, donc vous ne devriez pas non plus vous y mettre AttrDict.

Michael
la source
types.SimpleNamespace(disponible depuis Python 3.3) est une alternative au AttrDict personnalisé.
Cristian Ciupitu
4

Il est peut-être possible d'avoir votre gâteau et de le manger aussi. En d'autres termes, vous pouvez créer quelque chose qui fournit les fonctionnalités d'une classe et d'une instance de dictionnaire. Voir la recette Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss d'ActiveState et des commentaires sur les moyens de le faire.

Si vous décidez d'utiliser une classe régulière plutôt qu'une sous-classe, j'ai trouvé que la recette Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (par Alex Martelli) est très flexible et utile pour le genre de chose qu'elle ressemble à ce que vous faites (c'est-à-dire créer un agrégateur d'informations relativement simple). Puisqu'il s'agit d'une classe, vous pouvez facilement étendre ses fonctionnalités en ajoutant des méthodes.

Enfin, il convient de noter que les noms des membres de la classe doivent être des identificateurs Python légaux, mais pas les clés de dictionnaire - un dictionnaire offrirait donc une plus grande liberté à cet égard car les clés peuvent être tout ce qui peut être haché (même quelque chose qui n'est pas une chaîne).

Mettre à jour

Une classe object(qui n'a pas de __dict__) sous-classe nommée SimpleNamespace(qui en a une) a été ajoutée au typesmodule Python 3.3, et est encore une autre alternative.

Martineau
la source