1

i have an instance of a container class that serves as basis for several other instances. it is called data0 in the example. i would like to be able to make copies of that and then update some of the members of the copies.

yes, that could be done using the copy module, creating a copy and assigning the new values for the members. but what if the container class is immutable?

i was hoping to be able to implement what is needed to turn the class into a mapping (keys and __getitem__) and make __init__ accept the base class plus some modifications. this was probably inspired by the way you can update a dictionary:

x = {'a': 0, 'b': 1, 'c': 2}
y = {'b': 7}
z = {**x, 'b': 7}
print(z)  # {'a': 0, 'b': 7, 'c': 2}

so what i would like to be able to do is this:

data3 = Container(**data0, b=7)
print(data3)

here is what i tried:

class Container:
    KEYS = ('a', 'b', 'c')

    def __init__(self, a=None, b=None, c=None):
        self._a = a
        self._b = b
        self._c = c

    @property
    def a(self):
        return self._a
    @property
    def b(self):
        return self._b
    @property
    def c(self):
        return self._c

    def keys(self):
        return Container.KEYS

    def __getitem__(self, key):
        if key not in Container.KEYS:
            raise KeyError(key)
        return getattr(self, key)

    def __str__(self):
        return f'{self.__class__.__name__}(a={self.a}, b={self.b}, c={self.c})'

data0 = Container(a=1, b=2, c=3)
print(data0) # Container(a=1, b=2, c=3)
data1 = Container(**data0)  # 'copy constructor'
print(data1)  # Container(a=1, b=2, c=3)
data2 = Container(**{**data0, 'b':7})
print(data2)  # Container(a=1, b=7, c=3)

the last method works but is ugly...

as stated above: what i would like to be able to do is this:

data3 = Container(**data0, b=7)
print(data3)

but this (understandably) fails with the error message

TypeError: type object got multiple values for keyword argument 'b'

how can i fix that? or is there a more obvious way to create a new instance from a given basis with updated members?


or is the standard way to go just to inherit from that class with the desired values as defaults?

class Container1(Container):
    def __init__(self, a=1, b=2, c=3):
       super().__init__(a=a, b=b, c=c)

data3 = Container1(b=7)
print(data3)  # Container1(a=1, b=7, c=3)

this is a bit verbose and has the drawback that data3 is of the type Container1...


my attempt behaves differently on python 3.5 than on python 3.6 as described/asked here.

hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • `copy.copy` honors the `pickle` magic methods and pickle attempts to solve this very problem. Make the object picklable and it should copy as you like. – tdelaney May 16 '18 at 16:45
  • Note that Guido van Rossum has called `dict(x, **y)` a [weird hack](https://mail.python.org/pipermail/python-dev/2010-April/099485.html) / [despicable](https://mail.python.org/pipermail/python-dev/2010-April/099459.html) and is okay with [making the construct illegal](https://mail.python.org/pipermail/python-dev/2010-April/099435.html). So you may want to avoid `{**data0, 'b':7}` even though (currently) it works. – unutbu May 16 '18 at 18:11
  • Have you considered using `@classmethod` to define an [alternative constructor](https://stackoverflow.com/a/1950927/190597)? – unutbu May 16 '18 at 18:13
  • @unutbu yes, i have. an that will probably be the way i'll go. this just seemed way more elegant (not to guido van rossum as you pointed out). thanks! – hiro protagonist May 16 '18 at 18:24
  • This is exactly what `evolve` does in the attrs library. – o11c May 17 '18 at 05:31

0 Answers0