web-dev-qa-db-fra.com

Dois-je utiliser une classe ou un dictionnaire?

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__. Alors, y aurait-il un avantage à utiliser un dict à la place? Un dict serait-il plus rapide qu'une classe? Et plus rapide qu'une classe avec des slots?

82
deamon

Pourquoi voudriez-vous en faire un dictionnaire? Quel est l'avantage? Que se passe-t-il si vous souhaitez ajouter ultérieurement du code? Où serait votre __init__ le code va?

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

Les dictionnaires sont destinés au stockage des 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 dans votre conception.

Gardez cela une classe.

23
adw

Utilisez un dictionnaire, sauf si vous avez besoin du mécanisme supplémentaire d'une classe. Vous pouvez également utiliser un namedtuple pour 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, bien que je serais surpris si le dictionnaire n'était pas plus rapide.

38
Katriel

Une classe dans python is une dictée en dessous. Vous obtenez des frais généraux avec le comportement de la classe, mais vous ne pourrez pas le remarquer sans profileur. Dans dans ce cas, je pense que vous bénéficiez du cours car:

  • Toute votre logique vit dans une seule fonction
  • Il est facile à mettre à jour et reste encapsulé
  • Si vous changez quelque chose plus tard, vous pouvez facilement conserver la même interface
35
Bruce Armstrong

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

J'ai comparé le temps qu'il faut 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 compliqué mais ça fait le boulot.)

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

Modification d'une variable ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Donc, si vous ne stockez que des variables, vous avez besoin de vitesse et cela ne vous obligera pas à faire de nombreux calculs, je 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, n'oubliez pas - utilisez toujours __ slots __.

Remarque:

J'ai testé la 'Classe' avec les deux 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 beaucoup mais significatives si vous créez beaucoup de classes en boucle serrée (astuce: vous vous trompez)).

De plus, les délais de création et de modification des variables peuvent 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.

Modifier:

J'ai ensuite testé le nuple nommé: je ne peux pas le modifier, mais pour créer les 10000 échantillons (ou quelque chose comme ça), cela a pris 1,4 seconde, le dictionnaire est donc le plus rapide.

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

class Foo(dict):
    pass

Créer est comme une classe avec des slots et changer la variable est le plus lent (0,17 seconde) donc n'utilisez pas ces classes. optez pour un dict (vitesse) ou pour la classe dérivée d'un objet ("bonbons de syntaxe")

19
alexpinho98

Je suis d'accord avec @adw. Je ne représenterais jamais un "objet" (dans un sens OO) avec un dictionnaire. Les dictionnaires regroupent des 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 la forme réelle de la chose n'est pas claire. Que se passe-t-il lorsque certains noms/valeurs ne sont pas là? Qu'est-ce qui empêche le client de mettre quoi que ce soit. Ou d'essayer de retirer quoi que ce soit. La forme de la chose devrait toujours être clairement défini.

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 manières.

10
jaydel

Je recommanderais une classe, car ce sont toutes sortes d'informations impliquées dans une demande. Si l'on devait utiliser un dictionnaire, je m'attendrais à ce que les données stockées soient de nature beaucoup plus similaire. Une règle 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.

5
Stigma

Il 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 instance de classe et de dictionnaire. Voir la recette Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss d'ActiveState et les commentaires sur les façons de le faire.

Si vous décidez d'utiliser une classe régulière plutôt qu'une sous-classe, j'ai trouvé la recette Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (par Alex Martelli) very flexible et utile pour le genre de chose 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 davantage ses fonctionnalités en ajoutant des méthodes.

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

Mise à jour

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

3
martineau

Si tout ce que vous voulez faire, c'est des bonbons de syntaxe comme obj.bla = 5 au lieu de obj['bla'] = 5, surtout si vous devez répéter cela souvent, vous voudrez peut-être utiliser une classe de conteneur simple comme dans la suggestion 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 une utilisation de la mémoire. Les dictés 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 conserver 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 avoir avec AttrDict.

3
Michael