Is it possible to overload a Python assignment?

Is there a magic method that can overload an assignment operator like __assign__(self, new_value) ?

I would like to prevent re-binding for an instance:

 class Protect(): def __assign__(self, value): raise Exception("This is an ex-parrot") var = Protect() # once assigned... var = 1 # this should raise Exception() 

Is it possible? This is madness? Should I be on medicine?

+52
python methods assignment-operator class magic-methods
Jun 13 '12 at 23:13
source share
9 answers

The way you describe it is absolutely impossible. Naming is a fundamental feature of Python, and no interceptors were provided to change its behavior.

However, assignment to a member in an instance of a class can be controlled as you want by overriding .__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) >>> mx 3 >>> mx = 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 

Note that there is a member variable, _locked , that controls whether assignment is allowed. You can unlock it to update the value.

+45
Jun 13 2018-12-12T00:
source share
β€” -

No, because the purpose of language is intrinsic , which has no modification binding.

+26
Jun 13 '12 at 23:20
source share

I do not think that's possible. The way I see it, assigning a variable does nothing with the object that he mentioned earlier: it's just that the variable "points" to another object.

 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! 

However, you can mimic your desired behavior by creating a wrapper using the __setitem__() or __setattr__() methods that throw an exception and store the "immutable" elements inside.

+8
Jun 13 '12 at 23:20
source share

No no

Think about it, in your example you are rebuilding the name var to a new value. You are not actually touching the Protect instance.

If the name you want to restore is actually a property of some other object ie myobj.var, then you can prevent the property / attribute of the object from being assigned a value. But I assume that this is not what you want from your example.

+2
Jun 13 '12 at 23:32
source share

This is not possible in the global namespace, but you can take advantage of the more advanced metaprogramming of Python to prevent multiple instances of the Protect object from being created. The Singleton image is a good example of this.

In the case of Singleton, you must make sure that after creating the instance, even if the original variable referencing the instance is reassigned, the object will be saved. Any subsequent instances will simply return a link to the same object.

Despite this pattern, you can never prevent the reassignment of a global variable name.

+2
Jun 13 2018-12-12T00:
source share

Using the top-level namespace is not possible. When you run

 `var = 1` 

Saves the var key and the value 1 in the global dictionary. This is roughly equivalent to calling globals().__setitem__('var', 1) . The problem is that you cannot replace the global dictionary in a running script (you can probably by stepping on the stack, but that is not a good idea). However, you can execute code in the secondary namespace and provide a custom dictionary for your global variables.

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

This will launch the REPL, which works almost normally, but refuses any attempt to set the val variable. You can also use execfile(filename, myg) . Please note that this does not protect against malicious code.

+2
Nov 17 '16 at 2:14
source share

An ugly solution is to reassign the destructor. But this is not a real reassignment.

 import copy global a class MyClass(): def __init__(self): a = 1000 # ... def __del__(self): a = copy.copy(self) a = MyClass() a = 1 
+1
Aug 26 '16 at 12:55 on
source share

Yes, perhaps you can handle __assign__ with an ast change.

pip install assign

Test with

 class T(): def __assign__(self, v): print('called with %s' % v) b = T() c = b 

You'll get

 >>> import magic >>> import test called with c 

The project is located at https://github.com/RyanKung/assign And a simpler meaning: https://gist.github.com/RyanKung/4830d6c8474e6bcefa4edd13f122b4df

+1
Oct 26 '17 at 16:54 on
source share

Typically, the best approach I have found overrides __ilshift__ as a setter and __rlshift__ as a getter, duplicated by a property decorator. This is almost the last statement allowed only (| & ^), and the logical one below. It is rarely used ( __lrshift__ less, but it can be taken into account).

When using the PyPi destination package, only the direct destination can be controlled, so the actual "strength" of the operator is lower. PyPi assign sample package:

 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 

output:

 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' 

Same thing with shift:

 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 

output:

 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 

Thus, <<= operator receiving the value in the property is a much more visually clean solution, and it does not try to force the user to make some reflective errors, such as:

 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 
+1
Jul 22 2018-18-18 at
source share



All Articles