I need a lot of small objects in my applications. They must be immutable and return a new instance when I set a new attribute.
I found a lot of ways to disable the obj.prop = newValue behavior, now I need this:
newObj = obj.setTitle(title)
newObj = obj.setDirection(x, y)
newObj = obj.incrementCount()
newObj = obj.swap()
Currently I do this:
class Info(object):
    __slots__ = ['_x', '_y', ...]
    def setDirection(self, x, y):
        newObj = copy.copy(self)  # shallow copy is OK
        newObj._x = x
        newObj._y = y
        return newObj
    def swap(self):
        newObj = copy.copy(self)
        #  methods can do different things
        newObj._x, newObj._y = self._y, self._x
        return newObj
Is this good in terms of perfomance? Are there any faster ways to return a clone of the object with some attributes changed?
I use __slots__. My object has predefined properties. I have no generic .set(prop, value) methods
(Python 3.5+ is welcome)
To gain true immutability, I would rather subclass collections.namedtuple and use the method _replace():
import collections as co
# this will create a class with five attributes
class Info(co.namedtuple('BaseInfo', 'x y a b c')):
    __slots__ = ()
    def setDirection(self, x, y):
        return self._replace(x=x, y=y)
    def swap(self):
        return self._replace(x=self.y, y=self.x)
I've benchmarked the performance of the swap() method in both classes, and the class derived from namedtuple is about 3-4 times faster in python 3. Here's the benchmark code:
import copy
import collections as co
class Info(object):
    __slots__ = ['x', 'y', 'a', 'b', 'c']
    def swap(self):
        newObj = copy.copy(self)
        newObj.x, newObj.y = self.y, self.x
        return newObj
    # for the sake of convenience
    def __init__(self, x, y, a, b, c):
        self.x = x
        self.y = y
class TupleInfo(co.namedtuple('BaseInfo', 'x y a b c')):
    __slots__ = ()
    def swap(self):
        return self._replace(x=self.y, y=self.x)
if __name__ == "__main__":
    from timeit import timeit
    i1 = Info(1, 2, 0, 0, 0)
    i2 = TupleInfo(1, 2, 0, 0, 0)
    print("Built from scratch")
    print(timeit("z = i1.swap()", "from __main__ import i1", number=100000))
    print("Derived from namedtuple")
    print(timeit("z = i2.swap()", "from __main__ import i2", number=100000))
Results:
Built from scratch
1.8578372709998803
Derived from namedtuple
0.520611657999325
                        You can get a significant performance boost by defining a custom copy method for your objects:
class Info(object):
    __slots__ = ['x', 'y', 'z']
    def swap(self):
        newObj = self.copy()
        newObj.x, newObj.y = self.y, self.x
        return newObj
    def copy(self):
        clone = type(self)()
        for slot in self.__slots__:
            if hasattr(self, slot):
                setattr(clone, slot, getattr(self, slot))
        return clone
Test:
i = Info()
i.x = i.y = 4
def fn1(i=i, copy=copy.copy): return copy(i)
def fn2(i=i): return i.copy()
print('copy.copy')
print(timeit('fn1()', 'from __main__ import fn1', number=100000))
print('custom copy')
print(timeit('fn2()', 'from __main__ import fn2', number=100000))
Results:
copy.copy
1.5658336669985147
custom copy
0.4359149369993247
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With