Search code examples
pythonpython-3.ximmutabilitymutableshallow-copy

Is it possible to make wrapper object for numbers, e.g. float, to make it mutable?


In Python 3 everything is supposed to be an object, even numbers, but they're immutable.

Is it possible to create wrapper object for numbers, e.g. float, such that it would behave exactly as ordinary numbers except it must be mutable?

I've wondered whether it would be feasible using built-in type function by creating anonymous wrapper object deriving from float, but changing it behaviour to be mutable.

>>> f = lambda x : type('', (float,), dict())(x)
>>> a = f(9)
>>> a
9.0

What parameters must I change f to make number a be mutable?

How I verify if a number is mutable:

I must be able to create such function f that would create from integer value a float value and after shallow copy it would behave in the following manner:

>>> list_1 = [f(i) for i in [1, 2, 3, 4]]
>>> list_1
[1.0, 2.0, 3.0, 4.0]
>>> list_2 = copy.copy(list_1)
>>> list_1[0] *= 100
>>> list_1
[100.0, 2.0, 3.0, 4.0]
>>> list_2
[100.0, 2.0, 3.0, 4.0]

Modification of the first list, have changed both of them.

Maybe I must add some fields to dict() or add additional base class that would enforce mutability?


Solution

  • Values are immutable. They're platonic forms. An expression like 5 := 3 is nonsensical. What are mutable are locations, usually referred to as addresses or pointers. Python doesn't have those, but we can fake it by using a container type like a list, which is really a location that references other locations.

    Here's a partial implementation of a mutable numerical type by using a list to store a location where we will keep the value of the number and change the value in that location when it should change, and because all copies of a mutable number will share that location, all copies will see the change

    import copy
    
    # Convenience to work with both normal and mutable numbers
    def _get_value(obj):
        try:
            return obj.value[0]
        except:
            return obj
    
    class mutable_number(object):
        def __init__(self, value):
            # Mutable storage because `list` defines a location
            self.value = [value]
    
        # Define the comparison interface
        def __eq__(self, other):
            return _get_value(self) == _get_value(other)
    
        def __ne__(self, other):
            return _get_value(self) != _get_value(other)
    
        # Define the numerical operator interface, returning new instances
        # of mutable_number
        def __add__(self, other):
            return mutable_number(self.value[0] + _get_value(other))
    
        def __mul__(self, other):
            return mutable_number(self.value[0] * _get_value(other))
    
        # In-place operations alter the shared location
        def __iadd__(self, other):
            self.value[0] += _get_value(other)
            return self
    
        def __imul__(self, other):
            self.value[0] *= _get_value(other)
            return self
    
        # Define the copy interface
        def __copy__(self):
            new = mutable_number(0)
            new.value = self.value
            return new
    
        def __repr__(self):
            return repr(self.value[0])
    
    x = mutable_number(1)
    y = copy.copy(x)
    y *= 5
    print x
    
    list_1 = [mutable_number(i) for i in [1, 2, 3, 4]]
    list_2 = copy.copy(list_1)
    list_1[0] *= 100
    print list_1
    print list_2
    

    Please let me know if anything is unclear, and I can add more documentation