Soll ich eine Klasse oder ein Wörterbuch verwenden?

99

Ich habe eine Klasse, die nur Felder und keine Methoden enthält, wie folgt:

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)
        # ...

Dies könnte leicht in ein Diktat übersetzt werden. Die Klasse ist flexibler für zukünftige Ergänzungen und könnte schnell sein __slots__. Wäre es also von Vorteil, stattdessen ein Diktat zu verwenden? Wäre ein Diktat schneller als eine Klasse? Und schneller als eine Klasse mit Slots?

Deamon
quelle
2
Ich benutze immer Wörterbücher, um Daten zu speichern, und das scheint mir ein Anwendungsfall zu sein. In einigen Fällen dictkann es sinnvoll sein, eine Klasse abzuleiten . ordentlicher Vorteil: Wenn Sie debuggen, sagen print(request)Sie einfach und Sie werden sofort alle Statusinformationen sehen. Mit dem klassischeren Ansatz müssen Sie Ihre benutzerdefinierten __str__Methoden schreiben , was scheiße ist, wenn Sie es die ganze Zeit tun müssen.
Flow
Sie sind austauschbar stackoverflow.com/questions/1305532/…
Oleksiy
Wenn die Klasse absolut sinnvoll ist und anderen klar ist, warum nicht? Wenn Sie beispielsweise viele Klassen mit gemeinsamen Schnittstellen definieren, warum nicht? Aber Python unterstützt keine mächtigen objektorientierten Konzepte wie C ++.
MasterControlProgram
3
@Ralf Welche OOP unterstützt Python nicht?
qwr

Antworten:

31

Warum sollten Sie dies zu einem Wörterbuch machen? Was ist der Vorteil? Was passiert, wenn Sie später Code hinzufügen möchten? Wohin würde Ihr __init__Code gehen?

Klassen dienen zum Bündeln verwandter Daten (und normalerweise von Code).

Wörterbücher dienen zum Speichern von Schlüssel-Wert-Beziehungen, wobei normalerweise alle Schlüssel vom gleichen Typ sind und alle Werte auch vom einen Typ sind. Gelegentlich können sie nützlich sein, um Daten zu bündeln, wenn die Schlüssel- / Attributnamen nicht alle im Voraus bekannt sind. Dies ist jedoch häufig ein Zeichen dafür, dass etwas mit Ihrem Design nicht stimmt.

Halten Sie dies eine Klasse.

adw
quelle
Ich würde eine Art Factory-Methode erstellen, die ein Diktat anstelle der Klassenmethode erstellt __init__. Aber Sie haben Recht: Ich würde mich von Dingen trennen, die zusammengehören.
Deamon
88
Ich kann Ihnen nicht mehr widersprechen: Wörterbücher, Mengen, Listen und Tupel sind alle da, um verwandte Daten zu bündeln. In keiner Weise wird angenommen, dass die Werte eines Wörterbuchs vom gleichen Datentyp sein sollten oder müssen, ganz im Gegenteil. In vielen Listen und Mengen haben Werte den gleichen Typ, aber das liegt hauptsächlich daran, dass wir dies gerne zusammen aufzählen. Ich denke tatsächlich, dass die weit verbreitete Verwendung von Klassen zum Speichern von Daten ein Missbrauch von oop ist. Wenn Sie über Serialisierungsprobleme nachdenken, können Sie leicht erkennen, warum.
Flow
4
Es ist OBJEKT-orientierte Programmierung und nicht klassenorientiert aus einem Grund: Wir beschäftigen uns mit Objekten. Ein Objekt zeichnet sich durch 2 (3) Eigenschaften aus: 1. Zustand (Mitglieder) 2. Verhalten (Methoden) und 3. Instanz können in mehreren Worten beschrieben werden. Daher dienen Klassen dazu, Zustand und Verhalten zu bündeln.
friendzis
14
Ich habe dies markiert, da Sie nach Möglichkeit immer standardmäßig die einfachere Datenstruktur verwenden sollten. In diesem Fall reicht ein Wörterbuch für den vorgesehenen Zweck aus. Die Frage where would your __init__ code go?betrifft. Es könnte einen weniger erfahrenen Entwickler davon überzeugen, dass nur Klassen verwendet werden sollen, da in einem Wörterbuch keine init- Methode verwendet wird. Absurd.
Lloyd Moore
1
@Ralf Eine Klasse Foo ist lediglich ein Typ wie int und string. Speichern Sie den Wert in einem Integer-Typ oder einer Variablen foo vom Integer-Typ? Subtiler, aber wichtiger semantischer Unterschied. In Sprachen wie C ist diese Unterscheidung außerhalb akademischer Kreise nicht allzu relevant. Obwohl die meisten OO-Sprachen Klassenvariablen unterstützen, was die Unterscheidung zwischen Klasse und Objekt / Instanz äußerst relevant macht - speichern Sie Daten in der Klasse (gemeinsam für alle Instanzen) oder in einem bestimmten Objekt? Nicht gebissen werden
friendzis
44

Verwenden Sie ein Wörterbuch, es sei denn, Sie benötigen den zusätzlichen Mechanismus einer Klasse. Sie können auch einen namedtuplefür einen hybriden Ansatz verwenden:

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

Die Leistungsunterschiede hier sind minimal, obwohl ich überrascht wäre, wenn das Wörterbuch nicht schneller wäre.

Katriel
quelle
15
"Die Leistungsunterschiede hier sind minimal , obwohl ich überrascht wäre, wenn das Wörterbuch nicht wesentlich schneller wäre." Dies wird nicht berechnet. :)
Mipadi
1
@mipadi: das ist wahr. Jetzt behoben: p
Katriel
Das Diktat ist 1,5-mal schneller als das Named-Tupel und doppelt so schnell wie eine Klasse ohne Slots. Überprüfen Sie meinen Beitrag zu dieser Antwort.
Alexpinho98
@ alexpinho98: Ich habe mich bemüht, den 'Beitrag' zu finden, auf den Sie sich beziehen, aber ich kann ihn nicht finden! Können Sie die URL angeben? Vielen Dank!
Dan Oblinger
@DanOblinger Ich gehe davon aus, dass er seine Antwort unten meint.
Adam Lewis
37

Eine Klasse in Python ist ein Diktat darunter. Sie bekommen zwar etwas Aufwand mit dem Klassenverhalten, können es aber ohne einen Profiler nicht bemerken. In diesem Fall glaube ich, dass Sie von der Klasse profitieren, weil:

  • Ihre gesamte Logik lebt in einer einzigen Funktion
  • Es ist einfach zu aktualisieren und bleibt gekapselt
  • Wenn Sie später etwas ändern, können Sie die Benutzeroberfläche problemlos beibehalten
Bruce Armstrong
quelle
Ihre gesamte Logik lebt nicht in einer einzigen Funktion. Eine Klasse ist ein Tupel des gemeinsamen Status und normalerweise eine oder mehrere Methoden. Wenn Sie eine Klasse ändern, erhalten Sie keine Garantie für die Schnittstelle.
Lloyd Moore
25

Ich denke, dass die Verwendung jedes einzelnen viel zu subjektiv ist, als dass ich mich darauf einlassen könnte, also bleibe ich einfach bei Zahlen.

Ich habe die Zeit verglichen, die zum Erstellen und Ändern einer Variablen in einem Diktat, einer new_style-Klasse und einer new_style-Klasse mit Slots benötigt wird.

Hier ist der Code, den ich zum Testen verwendet habe (es ist ein bisschen chaotisch, aber es macht den Job.)

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')))

Und hier ist die Ausgabe ...

Erstellen...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Variable ändern ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Wenn Sie also nur Variablen speichern, benötigen Sie Geschwindigkeit und müssen nicht viele Berechnungen durchführen. Ich empfehle die Verwendung eines Diktats (Sie können immer nur eine Funktion erstellen, die wie eine Methode aussieht). Aber wenn Sie wirklich Klassen brauchen, denken Sie daran - verwenden Sie immer __ Slots __ .

Hinweis:

Ich habe die 'Klasse' mit beiden getestet mit den Klassen new_style als auch old_style getestet. Es stellt sich heraus, dass old_style-Klassen schneller zu erstellen, aber langsamer zu ändern sind (nicht viel, aber bedeutsam, wenn Sie viele Klassen in einer engen Schleife erstellen (Tipp: Sie machen es falsch)).

Auch die Zeiten zum Erstellen und Ändern von Variablen können auf Ihrem Computer unterschiedlich sein, da meine alt und langsam sind. Stellen Sie sicher, dass Sie es selbst testen, um die "echten" Ergebnisse zu sehen.

Bearbeiten:

Ich habe das Namedtuple später getestet: Ich kann es nicht ändern, aber um die 10000 Samples (oder ähnliches) zu erstellen, hat es 1,4 Sekunden gedauert, sodass das Wörterbuch tatsächlich das schnellste ist.

Wenn ich die Diktatfunktion so ändere , dass sie die Schlüssel und Werte enthält und das Diktat anstelle der Variablen zurückgibt, die das Diktat enthält, wenn ich es erstelle, gibt es mir 0,65 statt 0,8 Sekunden.

class Foo(dict):
    pass

Das Erstellen ist wie eine Klasse mit Slots und das Ändern der Variablen ist am langsamsten (0,17 Sekunden). Verwenden Sie diese Klassen also nicht . Gehen Sie für ein Diktat (Geschwindigkeit) oder für die vom Objekt abgeleitete Klasse ('Syntax Candy')

alexpinho98
quelle
Ich würde gerne die Zahlen für eine Unterklasse von sehen dict(ohne überschriebene Methoden, denke ich?). Funktioniert es genauso wie eine neue Klasse, die von Grund auf neu geschrieben wurde?
Benjamin Hodgson
12

Ich stimme @adw zu. Ich würde niemals ein "Objekt" (im OO-Sinne) mit einem Wörterbuch darstellen. Wörterbücher aggregieren Name / Wert-Paare. Klassen repräsentieren Objekte. Ich habe Code gesehen, in dem die Objekte mit Wörterbüchern dargestellt werden, und es ist unklar, wie die tatsächliche Form der Sache ist. Was passiert, wenn bestimmte Namen / Werte nicht vorhanden sind? Was den Kunden daran hindert, überhaupt etwas einzubringen. Oder zu versuchen, überhaupt etwas herauszuholen. Die Form der Sache sollte immer klar definiert sein.

Bei der Verwendung von Python ist es wichtig, diszipliniert zu bauen, da die Sprache dem Autor viele Möglichkeiten bietet, sich selbst in den Fuß zu schießen.

Jaydel
quelle
9
Antworten auf SO werden nach Stimmen sortiert und Antworten mit der gleichen Stimmenzahl werden nach dem Zufallsprinzip sortiert. Bitte klären Sie, wen Sie mit "dem letzten Poster" meinen.
Mike DeSimone
4
Und woher weißt du, dass etwas, das nur Felder und keine Funktionalität hat, ein "Objekt" im Sinne von OO ist?
Muhammad Alkarouri
1
Mein Lackmustest lautet "Ist die Struktur dieser Daten festgelegt?". Wenn ja, dann verwenden Sie ein Objekt, andernfalls ein Diktat. Wenn Sie davon abweichen, wird dies nur Verwirrung stiften.
weberc2
Sie müssen den Kontext des Problems analysieren, das Sie lösen. Objekte sollten relativ offensichtlich sein, sobald Sie mit dieser Landschaft vertraut sind. Persönlich denke ich, dass die Rückgabe eines Wörterbuchs Ihr letzter Ausweg sein sollte, es sei denn, Sie geben eine Zuordnung von Name / Wert-Paaren zurück. Ich habe zu viel schlampigen Code gesehen, bei dem alles, was in Methoden ein- und ausgeht, nur ein Wörterbuch ist. Es ist faul. Ich liebe dynamisch getippte Sprachen, aber ich mag auch, dass mein Code klar und logisch ist. Alles in ein Wörterbuch zu verschieben, kann eine Annehmlichkeit sein, die die Bedeutung verbirgt.
Jaydel
5

Ich würde eine Klasse empfehlen, da es sich um alle Arten von Informationen handelt, die mit einer Anfrage verbunden sind. Wenn man ein Wörterbuch verwenden würde, würde ich erwarten, dass die gespeicherten Daten weitaus ähnlicher sind. Eine Richtlinie, der ich mich selbst folge, ist, dass ich ein Wörterbuch verwende, wenn ich den gesamten Satz von Schlüssel-> Wertepaaren durchlaufen und etwas tun möchte. Andernfalls sind die Daten anscheinend weitaus strukturierter als eine grundlegende Schlüssel-> Wertzuordnung, was bedeutet, dass eine Klasse wahrscheinlich eine bessere Alternative wäre.

Bleib also bei der Klasse.

Stigma
quelle
2
Ich bin völlig anderer Meinung. Es gibt keinen Grund, die Verwendung von Wörterbüchern auf Dinge zu beschränken, die wiederholt werden müssen. Sie dienen zur Pflege eines Mappings.
Katriel
1
Lesen Sie bitte etwas genauer. Ich sagte, ich möchte vielleicht eine Schleife machen , nicht will . Für mich bedeutet die Verwendung eines Wörterbuchs, dass es eine große funktionale Ähnlichkeit zwischen den Schlüsseln und Werten gibt. Zum Beispiel Namen mit Alter. Die Verwendung einer Klasse würde bedeuten, dass die verschiedenen 'Schlüssel' Werte mit sehr unterschiedlichen Bedeutungen haben. Nehmen Sie zum Beispiel eine Klasse PErson. Hier wäre "Name" eine Zeichenfolge, und "Freunde" kann eine Liste, ein Wörterbuch oder ein anderes geeignetes Objekt sein. Sie würden nicht alle diese Attribute als Teil der normalen Verwendung dieser Klasse durchlaufen.
Stigma
1
Ich denke, die Unterscheidung zwischen Klassen und Wörterbüchern wird in Python durch die Tatsache undeutlich gemacht, dass die ersteren mit den letzteren implementiert werden ('Slots' nicht standhalten). Ich weiß, das hat mich ein wenig verwirrt, als ich die Sprache zum ersten Mal lernte (zusammen mit der Tatsache, dass Klassen Objekte und daher Instanzen einiger mysteriöser Metaklassen waren).
Martineau
4

Wenn alles, was Sie erreichen möchten, Syntax-Bonbons wie obj.bla = 5anstelle von ist obj['bla'] = 5, insbesondere wenn Sie dies häufig wiederholen müssen, möchten Sie möglicherweise eine einfache Container-Klasse wie in Martineaus Vorschlag verwenden. Trotzdem ist der Code dort ziemlich aufgebläht und unnötig langsam. Sie können es so einfach halten:

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

Ein weiterer Grund für den Wechsel zu namedtuples oder einer Klasse mit __slots__könnte die Speichernutzung sein. Dikte benötigen deutlich mehr Speicher als Listentypen, daher sollte dies ein Punkt sein, über den man nachdenken sollte.

In Ihrem speziellen Fall scheint es jedoch keine Motivation zu geben, von Ihrer aktuellen Implementierung abzuweichen. Sie scheinen nicht Millionen dieser Objekte zu verwalten, daher sind keine von Listen abgeleiteten Typen erforderlich. Und es enthält tatsächlich eine funktionale Logik innerhalb der __init__, so dass Sie auch nicht mit bekommen sollten AttrDict.

Michael
quelle
types.SimpleNamespace(verfügbar seit Python 3.3) ist eine Alternative zum benutzerdefinierten AttrDict.
Cristian Ciupitu
4

Möglicherweise können Sie Ihren Kuchen auch essen. Mit anderen Worten, Sie können etwas erstellen, das die Funktionalität einer Klassen- und einer Wörterbuchinstanz bietet. Siehe die ActiveState'sLesen Sie das Rezept und kommentieren Sie, wie Sie dies tun können.

Wenn Sie eine reguläre Klasse zu verwenden entscheiden , anstatt eine Unterklasse, habe ich das gefunden Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ „ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ“ ᴄʟᴀss Rezept (von Alex Martelli) zu sehr flexibel und nützlich für die Art von Sache , die es sieht so aus, als ob Sie es tun (dh einen relativ einfachen Aggregator von Informationen erstellen). Da es sich um eine Klasse handelt, können Sie ihre Funktionalität problemlos durch Hinzufügen von Methoden erweitern.

Abschließend sollte angemerkt werden, dass die Namen von Klassenmitgliedern legale Python-Bezeichner sein müssen, Wörterbuchschlüssel jedoch nicht. Ein Wörterbuch würde diesbezüglich mehr Freiheit bieten, da Schlüssel alles sein können, was hashbar ist (sogar etwas, das keine Zeichenfolge ist).

Aktualisieren

Eine Klasse object(die keine hat __dict__) mit dem Namen SimpleNamespace(die eine hat) wurde dem typesModul Python 3.3 hinzugefügt und ist eine weitere Alternative.

Martineau
quelle