Reputation: 202
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?
Upvotes: 3
Views: 1800
Reputation: 53
Maybe a wrapper like the following would be what you need.
# To solve scope issues. See
# <https://stackoverflow.com/questions/3431676/creating-functions-in-a-loop>
# for details.
def make_new_method(self, method_name):
def new_method(self, *a, **aa):
method = getattr(self._type, method_name)
return method(self._heart, *a, **aa)
return new_method
class Mutable():
def __init__(self, obj):
self._heart = obj
self._type = type(obj)
# Remap each of the heart object's attributes to be used on the
# wrapper object.
for attr_name in dir(self._type):
# Do not overwrite existing Mutable methods, only set new ones
# from its heart.
if attr_name not in dir(Mutable):
# Methods need to be treated differently.
if callable(getattr(self._type, attr_name)):
new_attr = make_new_method(self, attr_name)
setattr(Mutable, attr_name, new_attr)
else:
attr_value = getattr(self._heart, attr_name)
setattr(self, attr_name, attr_value)
def set(self, new_value):
if self._type is type(new_value):
self._heart = new_value
else:
self.__init__(new_value)
def __repr__(self):
return f'Mutable({repr(self._heart)})'
When you write mutable3 = Mutable(3)
it stores the value you want to be mutable (3, in this case) in the atribute _heart
of an instance of the Mutable
class and remaps its methods to be used directly with the instance itself. It allows, for example, doing
In [1]: a = list(map(Mutable, range(3)))
In [2]: a
Out[2]: [Mutable(0), Mutable(1), Mutable(2)]
In [3]: b = a[1]
In [4]: b.set('hello')
In [5]: a
Out[5]: [Mutable(0), Mutable('hello'), Mutable(2)]
It sounds to me like creating "symlinks" between variables, a behaviour we would achieve using pointers in a lower level language like C.
Upvotes: 2
Reputation: 1423
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
Upvotes: 5