Gibt es eine magische Methode, die den Zuweisungsoperator überladen kann, wie __assign__(self, new_value)
?
Ich möchte eine erneute Bindung für eine Instanz verbieten:
class Protect():
def __assign__(self, value):
raise Exception("This is an ex-parrot")
var = Protect() # once assigned...
var = 1 # this should raise Exception()
Ist es möglich? Ist es verrückt? Soll ich Medizin nehmen?
python
class
methods
assignment-operator
magic-methods
Caruccio
quelle
quelle
exec in d
d als Wörterbuch verwenden. Wenn sich der Code auf Modulebene befindet, sollte jede Zuweisung an das Wörterbuch zurückgesendet werden. Sie können entweder Ihre Werte nach der Ausführung wiederherstellen / prüfen, ob sich die Werte geändert haben, oder die Wörterbuchzuweisung abfangen, dh das Variablenwörterbuch durch ein anderes Objekt ersetzen.ScreenUpdating = False
auf Modulebene zu simulieren__all__
Attribut Ihres Moduls verwenden , um den Export privater Daten zu erschweren. Dies ist ein gängiger Ansatz für die Python Standard LibraryAntworten:
Die Art, wie Sie es beschreiben, ist absolut nicht möglich. Die Zuweisung zu einem Namen ist ein grundlegendes Merkmal von Python, und es wurden keine Hooks bereitgestellt, um sein Verhalten zu ändern.
Die Zuweisung zu einem Mitglied in einer Klasseninstanz kann jedoch durch Überschreiben nach Ihren Wünschen gesteuert werden
.__setattr__()
.class MyClass(object): def __init__(self, x): self.x = x self._locked = True def __setattr__(self, name, value): if self.__dict__.get("_locked", False) and name == "x": raise AttributeError("MyClass does not allow assignment to .x member") self.__dict__[name] = value >>> m = MyClass(3) >>> m.x 3 >>> m.x = 4 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in __setattr__ AttributeError: MyClass does not allow assignment to .x member
Beachten Sie, dass es eine Mitgliedsvariable gibt
_locked
, die steuert, ob die Zuweisung zulässig ist. Sie können es entsperren, um den Wert zu aktualisieren.quelle
@property
mit einem Getter, aber ohne Setter, ähnelt der Pseudo-Überlastungszuweisung.getattr(self, "_locked", None)
stattself.__dict__.get("_locked")
.False
stattdessen verwendetNone
. Wenn nun jemand die_locked
Mitgliedsvariable löscht, löst der.get()
Aufruf keine Ausnahme aus.get
Standardeinstellung istNone
, anders alsgetattr
dies tatsächlich eine Ausnahme auslösen würde.getattr()
eher zu verwenden als.__dict__.get()
. Ich denke, es ist besser zu benutzengetattr()
, dafür ist es.Nein, da die Zuweisung eine Sprache ist, die keinen Modifikations-Hook hat.
quelle
Ich denke nicht, dass es möglich ist. So wie ich es sehe, hat die Zuweisung zu einer Variablen nichts mit dem Objekt zu tun, auf das zuvor Bezug genommen wurde: Es ist nur so, dass die Variable jetzt auf ein anderes Objekt "zeigt".
In [3]: class My(): ...: def __init__(self, id): ...: self.id=id ...: In [4]: a = My(1) In [5]: b = a In [6]: a = 1 In [7]: b Out[7]: <__main__.My instance at 0xb689d14c> In [8]: b.id Out[8]: 1 # the object is unchanged!
Sie können das gewünschte Verhalten jedoch nachahmen, indem Sie ein Wrapper-Objekt mit
__setitem__()
oder__setattr__()
Methoden erstellen , die eine Ausnahme auslösen, und das "unveränderliche" Material darin behalten.quelle
Innerhalb eines Moduls ist dies durch ein bisschen dunkle Magie absolut möglich.
import sys tst = sys.modules['tst'] class Protect(): def __assign__(self, value): raise Exception("This is an ex-parrot") var = Protect() # once assigned... Module = type(tst) class ProtectedModule(Module): def __setattr__(self, attr, val): exists = getattr(self, attr, None) if exists is not None and hasattr(exists, '__assign__'): exists.__assign__(val) super().__setattr__(attr, val) tst.__class__ = ProtectedModule
Beachten Sie, dass Sie auch innerhalb des Moduls nicht in die geschützte Variable schreiben können, sobald die Klassenänderung erfolgt. Im obigen Beispiel wird davon ausgegangen, dass sich der Code in einem Modul mit dem Namen befindet
tst
. Sie können dies in derrepl
durch Änderntst
zu tun__main__
.quelle
Mit dem Namespace der obersten Ebene ist dies nicht möglich. Wenn du rennst
var = 1
Es speichert den Schlüssel
var
und den Wert1
im globalen Wörterbuch. Es ist ungefähr gleichbedeutend mit einem Anrufglobals().__setitem__('var', 1)
. Das Problem ist, dass Sie das globale Wörterbuch in einem laufenden Skript nicht ersetzen können (Sie können es wahrscheinlich tun, indem Sie mit dem Stapel herumspielen, aber das ist keine gute Idee). Sie können jedoch Code in einem sekundären Namespace ausführen und ein benutzerdefiniertes Wörterbuch für dessen Globals bereitstellen.class myglobals(dict): def __setitem__(self, key, value): if key=='val': raise TypeError() dict.__setitem__(self, key, value) myg = myglobals() dict.__setitem__(myg, 'val', 'protected') import code code.InteractiveConsole(locals=myg).interact()
Dadurch wird eine REPL ausgelöst, die fast normal funktioniert, jedoch alle Versuche, die Variable festzulegen, ablehnt
val
. Sie könnten auch verwendenexecfile(filename, myg)
. Beachten Sie, dass dies nicht vor bösartigem Code schützt.quelle
Nein, gibt es nicht
Denken Sie darüber nach, in Ihrem Beispiel binden Sie den Namen var erneut an einen neuen Wert. Sie berühren die Instanz von Protect nicht wirklich.
Wenn der Name, den Sie neu binden möchten, tatsächlich eine Eigenschaft einer anderen Entität ist, z. B. myobj.var, können Sie verhindern, dass der Eigenschaft / dem Attribut der Entität ein Wert zugewiesen wird. Aber ich nehme an, das ist nicht das, was Sie von Ihrem Beispiel wollen.
quelle
__dict__.__setattr__
aber esmodule.__dict__
ist schreibgeschützt. Geben Sie außerdem (mymodule) == <type 'module'> ein, und es ist nicht instanziierbar.Im globalen Namespace ist dies nicht möglich, Sie können jedoch die erweiterte Python-Metaprogrammierung nutzen, um zu verhindern, dass mehrere Instanzen eines
Protect
Objekts erstellt werden. Das Singleton-Muster ist ein gutes Beispiel dafür.Im Fall eines Singleton würden Sie sicherstellen, dass das Objekt nach der Instanziierung auch dann bestehen bleibt, wenn die ursprüngliche Variable, die auf die Instanz verweist, neu zugewiesen wird. Alle nachfolgenden Instanzen würden lediglich einen Verweis auf dasselbe Objekt zurückgeben.
Trotz dieses Musters können Sie niemals verhindern, dass ein globaler Variablenname selbst neu zugewiesen wird.
quelle
var = 1
der Singleton-Mechanismus nicht aufgerufen wird.Protect()
) erstellt werden. Es gibt keine Möglichkeit, den ursprünglich zugewiesenen Namen (zvar
. B. ) zu schützen .Eine hässliche Lösung besteht darin, den Destruktor neu zuzuweisen. Aber es ist keine echte Überlastungszuweisung.
import copy global a class MyClass(): def __init__(self): a = 1000 # ... def __del__(self): a = copy.copy(self) a = MyClass() a = 1
quelle
Ja, es ist möglich, dass Sie
__assign__
über Ändern bearbeiten könnenast
.pip install assign
Test mit:
class T(): def __assign__(self, v): print('called with %s' % v) b = T() c = b
Sie erhalten
>>> import magic >>> import test called with c
Das Projekt ist bei
https://github.com/RyanKung/assign
Und das Einfachere:https://gist.github.com/RyanKung/4830d6c8474e6bcefa4edd13f122b4df
quelle
print('called with %s' % self)
?'c'
imv
Argument für die__assign__
Methode? Was zeigt Ihr Beispiel eigentlich? Das verwirrt mich. 2) Wann wäre das sinnvoll? 3) Wie hängt das mit der Frage zusammen? Müssen Sie nicht schreibenb = c
, damit es dem in der Frage geschriebenen Code entsprichtc = b
?Im Allgemeinen ist der beste Ansatz, den ich gefunden habe, das Überschreiben
__ilshift__
als Setter und__rlshift__
als Getter, der vom Immobiliendekorateur dupliziert wird. Es ist fast der letzte Operator, der nur (| & ^) aufgelöst wird und logisch niedriger ist. Es wird selten verwendet (__lrshift__
ist weniger, kann aber berücksichtigt werden).Bei Verwendung des PyPi-Zuweisungspakets kann nur die Vorwärtszuweisung gesteuert werden, sodass die tatsächliche "Stärke" des Bedieners geringer ist. Beispiel für ein PyPi-Zuweisungspaket:
class Test: def __init__(self, val, name): self._val = val self._name = name self.named = False def __assign__(self, other): if hasattr(other, 'val'): other = other.val self.set(other) return self def __rassign__(self, other): return self.get() def set(self, val): self._val = val def get(self): if self.named: return self._name return self._val @property def val(self): return self._val x = Test(1, 'x') y = Test(2, 'y') print('x.val =', x.val) print('y.val =', y.val) x = y print('x.val =', x.val) z: int = None z = x print('z =', z) x = 3 y = x print('y.val =', y.val) y.val = 4
Ausgabe:
x.val = 1 y.val = 2 x.val = 2 z = <__main__.Test object at 0x0000029209DFD978> Traceback (most recent call last): File "E:\packages\pyksp\pyksp\compiler2\simple_test2.py", line 44, in <module> print('y.val =', y.val) AttributeError: 'int' object has no attribute 'val'
Das gleiche gilt für die Verschiebung:
class Test: def __init__(self, val, name): self._val = val self._name = name self.named = False def __ilshift__(self, other): if hasattr(other, 'val'): other = other.val self.set(other) return self def __rlshift__(self, other): return self.get() def set(self, val): self._val = val def get(self): if self.named: return self._name return self._val @property def val(self): return self._val x = Test(1, 'x') y = Test(2, 'y') print('x.val =', x.val) print('y.val =', y.val) x <<= y print('x.val =', x.val) z: int = None z <<= x print('z =', z) x <<= 3 y <<= x print('y.val =', y.val) y.val = 4
Ausgabe:
x.val = 1 y.val = 2 x.val = 2 z = 2 y.val = 3 Traceback (most recent call last): File "E:\packages\pyksp\pyksp\compiler2\simple_test.py", line 45, in <module> y.val = 4 AttributeError: can't set attribute
Der
<<=
Bediener, der Wert auf eine Immobilie legt, ist die visuell sauberere Lösung und versucht nicht, einige reflektierende Fehler zu machen, wie:var1.val = 1 var2.val = 2 # if we have to check type of input var1.val = var2 # but it could be accendently typed worse, # skipping the type-check: var1.val = var2.val # or much more worse: somevar = var1 + var2 var1 += var2 # sic! var1 = var2
quelle