Obwohl ich das nie gebraucht habe, ist mir nur aufgefallen, dass das Erstellen eines unveränderlichen Objekts in Python etwas schwierig sein kann. Sie können nicht einfach überschreiben __setattr__
, da Sie dann nicht einmal Attribute in der festlegen können __init__
. Das Unterklassen eines Tupels ist ein Trick, der funktioniert:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Aber dann haben Sie Zugriff auf die a
und b
Variablen durch self[0]
und self[1]
, was ärgerlich ist.
Ist das in Pure Python möglich? Wenn nicht, wie würde ich das mit einer C-Erweiterung machen?
(Antworten, die nur in Python 3 funktionieren, sind akzeptabel).
Aktualisieren:
So Subklassen Tupel ist so , wie es in reinen Python zu tun, die die Daten für den Zugriff durch gut mit Ausnahme der zusätzlichen Möglichkeit funktioniert [0]
, [1]
etc. Also, zu vervollständigen diese Frage alles , was fehlt , ist es Howto zu tun „richtig“ in C, die Ich vermute, es wäre ganz einfach, einfach kein geititem
oder setattribute
usw. zu implementieren . Aber anstatt es selbst zu tun, biete ich dafür ein Kopfgeld an, weil ich faul bin. :) :)
quelle
.a
und.b
? Dafür scheinen die Eigenschaften doch zu existieren.NotImplemented
ist nur als Rückgabewert für umfangreiche Vergleiche gedacht. Ein Rückgabewert für__setatt__()
ist sowieso ziemlich sinnlos, da Sie ihn normalerweise überhaupt nicht sehen. Code wieimmutable.x = 42
wird stillschweigend nichts tun. Sie solltenTypeError
stattdessen eine erhöhen .NotImplementedError
, aberTypeError
was ist ein Tupel, wenn Sie versuchen, es zu ändern.Antworten:
Eine weitere Lösung, an die ich gerade gedacht habe: Der einfachste Weg, das gleiche Verhalten wie bei Ihrem ursprünglichen Code zu erzielen, ist
Es löst nicht das Problem, dass auf Attribute über
[0]
usw. zugegriffen werden kann , aber es ist zumindest erheblich kürzer und bietet den zusätzlichen Vorteil, mitpickle
und kompatibel zu seincopy
.namedtuple
erstellt einen Typ ähnlich dem, was ich in dieser Antwort beschrieben habe , dh abgeleitet vontuple
und unter Verwendung__slots__
. Es ist in Python 2.6 oder höher verfügbar.quelle
verbose
Parametern fürnamedtuple
den Code lässt sich leicht generieren)) besteht darin, dass die einzelne Schnittstelle / Implementierung von anamedtuple
Dutzenden von leicht unterschiedlichen handgeschriebenen Schnittstellen / Implementierungen vorzuziehen ist mach fast das gleiche.namedtuple
s nach Wert kopiert, wenn sie durch Funktionen geleitet werden?Der einfachste Weg, dies zu tun, ist
__slots__
:Instanzen von
A
sind jetzt unveränderlich, da Sie ihnen keine Attribute zuweisen können.Wenn die Klasseninstanzen Daten enthalten sollen, können Sie dies mit folgenden Ergebnissen kombinieren
tuple
:Bearbeiten : Wenn Sie die Indizierung ebenfalls entfernen möchten, können Sie Folgendes überschreiben
__getitem__()
:Beachten Sie, dass Sie
operator.itemgetter
in diesem Fall nicht für die Eigenschaften verwenden können, da diesPoint.__getitem__()
anstelle vontuple.__getitem__()
. Darüber hinaus wird dies die Verwendung von nicht verhinderntuple.__getitem__(p, 0)
, aber ich kann mir kaum vorstellen, wie dies ein Problem darstellen sollte.Ich denke nicht, dass der "richtige" Weg, ein unveränderliches Objekt zu erstellen, darin besteht, eine C-Erweiterung zu schreiben. Python ist normalerweise darauf angewiesen, dass Bibliotheksimplementierer und Bibliotheksbenutzer Erwachsenen zustimmen. Anstatt eine Schnittstelle wirklich durchzusetzen, sollte die Schnittstelle in der Dokumentation klar angegeben werden. Aus diesem Grund denke ich nicht über die Möglichkeit nach, ein Überschreiben
__setattr__()
durch Aufrufenobject.__setattr__()
eines Problems zu umgehen . Wenn jemand dies tut, geschieht dies auf eigenes Risiko.quelle
tuple
hier zu verwenden ?__slots__ = ()
als__slots__ = []
? (Nur zur Klarstellung)__slots__
richtig geändert? Ziel ist es, einmal zu identifizieren, welche Attribute festgelegt werden können.tuple
Scheint a in einem solchen Fall nicht eine ganz natürliche Wahl zu sein?__slots__
kann ich keine Attribute setzen. Und wenn ja,__slots__ = ('a', 'b')
dann sind die Attribute a und b immer noch veränderlich.__setattr__
also ist es eine Verbesserung gegenüber meiner. +1 :)Mit Cython können Sie einen Erweiterungstyp für Python erstellen:
Es funktioniert sowohl mit Python 2.x als auch mit 3.
Tests
Wenn Ihnen die Indizierungsunterstützung nichts ausmacht, ist der
collections.namedtuple
Vorschlag von @Sven Marnach vorzuziehen:quelle
namedtuple
(oder genauer gesagt von dem von der Funktion zurückgegebenen Typnamedtuple()
) sind unveränderlich. Bestimmt.namedtuple
alle Tests (außer Indizierungsunterstützung). Welche Anforderung habe ich vermisst?__weakref__
in Python?Eine andere Idee wäre, den Konstruktor vollständig zu verbieten
__setattr__
und zu verwendenobject.__setattr__
:Natürlich können Sie
object.__setattr__(p, "x", 3)
einePoint
Instanz ändernp
, aber Ihre ursprüngliche Implementierung leidet unter demselben Problem (versuchen Sie estuple.__setattr__(i, "x", 42)
eineImmutable
Instanz aus).Sie können denselben Trick in Ihrer ursprünglichen Implementierung anwenden: Entfernen
__getitem__()
und Verwendentuple.__getitem__()
in Ihren Eigenschaftsfunktionen.quelle
__setattr__
, weil es nicht darum geht, narrensicher zu sein. Es geht darum, klar zu machen, dass es nicht geändert werden sollte, und eine versehentliche Änderung zu verhindern.Sie können einen
@immutable
Dekorator erstellen , der entweder die überschreibt__setattr__
und die__slots__
Liste in eine leere Liste ändert , und dann die dekorieren__init__
Methode damit .Bearbeiten: Wie das OP feststellte,
__slots__
verhindert das Ändern des Attributs nur die Erstellung neuer Attribute , nicht die Änderung.Edit2: Hier ist eine Implementierung:
Edit3: Using unterbricht
__slots__
diesen Code, da if die Erstellung des Objekts stoppt__dict__
. Ich suche eine Alternative.Edit4: Nun, das ist es. Es ist zwar hackisch, funktioniert aber als Übung :-)
quelle
object.__setattr__()
bricht es stackoverflow.com/questions/4828080/…Verwenden einer eingefrorenen Datenklasse
Für Python 3.7+ können Sie verwenden Datenklasse mit einer
frozen=True
Option , die ein sehr pythonic und wartbar Weg ist , zu tun , was Sie wollen.Es würde ungefähr so aussehen:
Da für die Felder der Datenklassen Typhinweise erforderlich sind, habe ich Any aus dem
typing
Modul verwendet .Gründe, ein Namedtuple NICHT zu verwenden
Vor Python 3.7 wurde häufig festgestellt, dass Namedtuples als unveränderliche Objekte verwendet wurden. Es kann in vielerlei Hinsicht schwierig sein. Eine davon ist, dass die
__eq__
Methode zwischen den benannten Tupeln die Klassen der Objekte nicht berücksichtigt. Beispielsweise:Wie Sie sehen, auch wenn die Typen
obj1
undobj2
unterschiedlich sind, auch wenn ihre Felder die Namen unterschiedlich sind,obj1 == obj2
gibt nach wie vorTrue
. Dies liegt daran, dass die verwendete__eq__
Methode die des Tupels ist, bei der nur die Werte der Felder anhand ihrer Position verglichen werden. Dies kann eine große Fehlerquelle sein, insbesondere wenn Sie diese Klassen in Unterklassen unterteilen.quelle
Ich denke nicht, dass es völlig möglich ist, außer wenn entweder ein Tupel oder ein benanntes Tupel verwendet wird. Egal was passiert, wenn Sie überschreiben, kann
__setattr__()
der Benutzer es immer umgehen, indem erobject.__setattr__()
direkt anruft . Jede Lösung, die davon abhängt__setattr__
, funktioniert garantiert nicht.Das Folgende ist ungefähr das nächste, das Sie erhalten können, ohne ein Tupel zu verwenden:
aber es bricht, wenn Sie sich genug anstrengen:
aber Svens Gebrauch von
namedtuple
ist wirklich unveränderlich.Aktualisieren
Da die Frage aktualisiert wurde, um zu fragen, wie es in C richtig gemacht wird, ist hier meine Antwort, wie man es in Cython richtig macht:
Erstens
immutable.pyx
:und a
setup.py
, um es zu kompilieren (mit dem Befehlsetup.py build_ext --inplace
:Dann probieren Sie es aus:
quelle
Ich habe unveränderliche Klassen erstellt, indem ich sie überschrieben
__setattr__
und die Menge zugelassen habe, wenn der Aufrufer__init__
:Dies ist noch nicht genug, da es jedem erlaubt,
___init__
das Objekt zu ändern, aber Sie bekommen die Idee.quelle
object.__setattr__()
bricht es stackoverflow.com/questions/4828080/…__init__
zufriedenstellend ist, ist nicht sehr zufriedenstellend.Zusätzlich zu den hervorragenden anderen Antworten möchte ich eine Methode für Python 3.4 (oder vielleicht 3.3) hinzufügen. Diese Antwort baut auf mehreren vorherigen Antworten auf diese Frage auf.
In Python 3.4 können Sie Eigenschaften ohne Setter verwenden , um Klassenmitglieder zu erstellen, die nicht geändert werden können. (In früheren Versionen war die Zuweisung von Eigenschaften ohne Setter möglich.)
Sie können es so verwenden:
welches drucken wird
"constant"
Aber das Anrufen
instance.a=10
wird verursachen:Erklärung: Eigenschaften ohne Setter sind ein sehr neues Merkmal von Python 3.4 (und ich denke 3.3). Wenn Sie versuchen, einer solchen Eigenschaft zuzuweisen, wird ein Fehler ausgelöst. Mit Slots beschränke ich die Mitgliedsvariablen auf
__A_a
(was ist__a
).Problem: Eine Zuordnung zu
_A__a
ist weiterhin möglich (instance._A__a=2
). Aber wenn Sie einer privaten Variablen zuweisen, ist es Ihre eigene Schuld ...Diese Antwort rät jedoch unter anderem von der Verwendung von ab
__slots__
. Die Verwendung anderer Methoden zur Verhinderung der Attributerstellung ist möglicherweise vorzuziehen.quelle
property
ist auch in Python 2 verfügbar (siehe Code in der Frage selbst). Es wird kein unveränderliches Objekt erstellt, versuchen Sie die Tests aus meiner Antwort, z. B.instance.b = 1
erstellt ein neuesb
Attribut.A().b = "foo"
dh das Festlegen neuer Attribute nicht zulässt.Hier ist eine elegante Lösung:
Erben Sie diese Klasse, initialisieren Sie Ihre Felder im Konstruktor, und schon sind Sie fertig.
quelle
Wenn Sie sich für Objekte mit Verhalten interessieren, ist namedtuple fast Ihre Lösung.
Wie am Ende der Dokumentation zu namedtuple beschrieben , können Sie Ihre eigene Klasse von namedtuple ableiten. Anschließend können Sie das gewünschte Verhalten hinzufügen.
Zum Beispiel (Code direkt aus der Dokumentation entnommen ):
Dies führt zu:
Dieser Ansatz funktioniert sowohl für Python 3 als auch für Python 2.7 (auch auf IronPython getestet).
Der einzige Nachteil ist, dass der Vererbungsbaum etwas seltsam ist. Aber damit spielt man normalerweise nicht.
quelle
class Point(typing.NamedTuple):
Klassen, die von der folgenden
Immutable
Klasse erben , sind ebenso wie ihre Instanzen unveränderlich, nachdem die__init__
Ausführung ihrer Methode abgeschlossen ist. Da es sich, wie andere bereits betont haben, um reines Python handelt, hindert nichts jemanden daran, die mutierenden Spezialmethoden von der Basis aus zu verwenden,object
undtype
dies reicht aus, um zu verhindern, dass jemand versehentlich eine Klasse / Instanz mutiert.Es funktioniert, indem der Klassenerstellungsprozess mit einer Metaklasse entführt wird.
quelle
Ich brauchte das vor einiger Zeit und beschloss, ein Python-Paket dafür zu erstellen. Die erste Version ist jetzt auf PyPI:
Benutzen:
Vollständige Dokumente hier: https://github.com/theengineear/immutable
Ich hoffe, es hilft, es umschließt ein benanntes Tupel, wie es bereits besprochen wurde, macht aber die Instanziierung viel einfacher.
quelle
Dieser Weg hört nicht auf zu
object.__setattr__
arbeiten, aber ich fand ihn trotzdem nützlich:__setitem__
Je nach Anwendungsfall müssen Sie möglicherweise mehr Dinge (wie ) überschreiben .quelle
getattr
, um einen Standardwert für bereitzustellenfrozen
. Das hat die Dinge ein bisschen vereinfacht. stackoverflow.com/a/22545808/5987__new__
Override nicht. Innen__setattr__
ersetzen Sie einfach die Bedingung durchif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
Funktion bereitstellen . Das Objekt wird dann "einmal einfrieren". Schließlichobject.__setattr__
ist es albern , sich Sorgen zu machen , denn "wir sind alle Erwachsene hier".Ab Python 3.7 können Sie den
@dataclass
Dekorator in Ihrer Klasse verwenden und er ist unveränderlich wie eine Struktur! Es kann jedoch eine__hash__()
Methode zu Ihrer Klasse hinzufügen oder nicht . Zitat:Hier das Beispiel aus den oben verlinkten Dokumenten:
quelle
frozen
, dh@dataclass(frozen=True)
, aber es blockiert im Grunde die Verwendung von__setattr__
und__delattr__
wie in den meisten anderen Antworten hier. Dies geschieht nur auf eine Weise, die mit den anderen Optionen von Datenklassen kompatibel ist.Sie können setattr überschreiben und trotzdem init verwenden , um die Variable festzulegen . Sie würden Super Class Setattr verwenden . Hier ist der Code.
quelle
pass
stattraise NotImplementedError
Das Drittanbieter-
attr
Modul bietet diese Funktionalität .Edit: python 3.7 hat diese idee mit in die stdlib übernommen
@dataclass
.attr
Implementiert eingefrorene Klassen durch Überschreiben__setattr__
und hat laut Dokumentation zu jedem Zeitpunkt eine geringfügige Auswirkung auf die Leistung.Wenn Sie die Gewohnheit haben, Klassen als Datentypen zu verwenden,
attr
kann dies besonders nützlich sein, da es sich um das Boilerplate für Sie kümmert (aber keine Magie ausführt). Insbesondere werden neun dunder (__X__) Methoden für Sie geschrieben (es sei denn, Sie deaktivieren eine davon), einschließlich repr, init, hash und aller Vergleichsfunktionen.attr
bietet auch einen Helfer für__slots__
.quelle
Also schreibe ich jeweils von Python 3:
I) mit Hilfe des Datenklassendekorators und setze eingefroren = True. Wir können unveränderliche Objekte in Python erstellen.
Dazu muss die Datenklasse aus der Datenklasse lib importiert und gefroren = True gesetzt werden
Ex.
aus Datenklassen Datenklasse importieren
o / p:
Quelle: https://realpython.com/python-data-classes/
quelle
Ein alternativer Ansatz besteht darin, einen Wrapper zu erstellen, der eine Instanz unveränderlich macht.
Dies ist in Situationen nützlich, in denen nur einige Instanzen unveränderlich sein müssen (wie Standardargumente von Funktionsaufrufen).
Kann auch in unveränderlichen Fabriken eingesetzt werden wie:
Schützt auch vor
object.__setattr__
anderen Tricks, ist aber aufgrund der dynamischen Natur von Python fehlbar.quelle
Ich habe die gleiche Idee wie Alex verwendet: eine Metaklasse und einen "Init-Marker", aber in Kombination mit dem Überschreiben von __setattr__:
Hinweis: Ich rufe die Meta-Klasse direkt auf, damit sie sowohl für Python 2.x als auch für 3.x funktioniert.
Es funktioniert auch mit Slots ...:
... und Mehrfachvererbung:
Beachten Sie jedoch, dass veränderbare Attribute unveränderlich bleiben müssen:
quelle
Eine Sache, die hier nicht wirklich enthalten ist, ist die völlige Unveränderlichkeit ... nicht nur das übergeordnete Objekt, sondern auch alle Kinder. Tupel / Frozensets können beispielsweise unveränderlich sein, die Objekte, zu denen sie gehören, jedoch möglicherweise nicht. Hier ist eine kleine (unvollständige) Version, die die Unveränderlichkeit bis zum Ende anständig erzwingt:
quelle
Sie können setAttr einfach in der endgültigen Anweisung von init überschreiben. Dann können Sie konstruieren, aber nicht ändern. Natürlich können Sie das usint-Objekt immer noch überschreiben. setAttr aber in der Praxis haben die meisten Sprachen irgendeine Form der Reflexion, so dass Unveränderlichkeit immer eine undichte Abstraktion ist. Bei der Unveränderlichkeit geht es eher darum, zu verhindern, dass Kunden versehentlich gegen den Vertrag eines Objekts verstoßen. Ich benutze:
=============================
Die ursprünglich angebotene Lösung war falsch. Diese wurde basierend auf den Kommentaren unter Verwendung der Lösung von hier aktualisiert
Die ursprüngliche Lösung ist auf interessante Weise falsch, daher ist sie unten enthalten.
===============================
Ausgabe :
======================================
Ursprüngliche Implementierung:
In den Kommentaren wurde zu Recht darauf hingewiesen, dass dies tatsächlich nicht funktioniert, da es die Erstellung von mehr als einem Objekt verhindert, wenn Sie die class setattr-Methode überschreiben, was bedeutet, dass eine Sekunde nicht als self.a = will erstellt werden kann Fehler bei der zweiten Initialisierung.
quelle
Die folgende Grundlösung befasst sich mit dem folgenden Szenario:
__init__()
kann wie gewohnt auf die Attribute zugegriffen werden.Die Idee ist, die
__setattr__
Methode zu überschreiben und ihre Implementierung jedes Mal zu ersetzen, wenn der Status des eingefrorenen Objekts geändert wird.Wir brauchen also eine Methode (
_freeze
), die diese beiden Implementierungen speichert und auf Anfrage zwischen ihnen wechselt.Dieser Mechanismus kann innerhalb der Benutzerklasse implementiert oder von einer speziellen
Freezer
Klasse geerbt werden, wie unten gezeigt:quelle
Genau wie ein
dict
Ich habe eine Open-Source-Bibliothek, in der ich Dinge auf funktionale Weise erledige , sodass das Verschieben von Daten in einem unveränderlichen Objekt hilfreich ist. Ich möchte jedoch nicht mein Datenobjekt transformieren müssen, damit der Client mit ihnen interagieren kann. Also habe ich mir das ausgedacht - es gibt dir ein diktartiges Objekt, das unveränderlich ist + einige Hilfsmethoden .
Dank an Sven Marnach in seiner Antwort für die grundlegende Implementierung der Einschränkung der Aktualisierung und Löschung von Immobilien.
Hilfsmethoden
Beispiele
quelle