0

My understanding is that using .copy() creates a shallow copy of a dictionary, so when you modify the copy, it will be reflected in the original (since it is a reference). When the dictionary value is a list, this is indeed the case. However, when the value is a string, the change is not reflected in the original. Example:

In [36]: x = {"string": "example", "list": []}

In [37]: y = x.copy()

In [38]: x
Out[38]: {'string': 'example', 'list': []}

In [39]: y
Out[39]: {'string': 'example', 'list': []}

In [40]: y["string"] += " one"

In [41]: x
Out[41]: {'string': 'example', 'list': []}

In [42]: y
Out[42]: {'string': 'example one', 'list': []}

In [43]: y["list"].append("one")

In [44]: x
Out[44]: {'string': 'example', 'list': ['one']}

In [45]: y
Out[45]: {'string': 'example one', 'list': ['one']}

Why does this behavior change based on the value of the key? I'm aware that copy.deepcopy should be used for modification after copying, but I'm very confused by this behavior.

sdweldon
  • 192
  • 1
  • 19
  • 2
    Strings are not mutable. The only way to change them is to make a new one, which allocates new memory and creates a new reference. – Mark Jul 13 '23 at 14:28
  • @Mark thanks for your reply. So when you update strings in the copy (`y`), is `y` still considered a shallow copy of `x`? I would think yes since you can see the `list` change reflected in the original dictionary (`x`). If that's the case, does that mean that `x` now has multiple references for the same key (`string`)? Or that `y` has its own reference for `string`, and uses `x`'s references for unchanged and mutable values? `y` does not simply become a deepcopy when the new memory is allocated, correct? – sdweldon Jul 13 '23 at 14:37
  • 1
    Yes, it is a shallow copy. The phenomenon you are seeing is perfectly compatible and expected with it being a shallowncopy. No, there is no special way in which references are handled. They are handled in precisely the same way. See my answer. – juanpa.arrivillaga Jul 13 '23 at 15:29
  • 1
    And `deepcopy` doesn't *have* to be used if you only **mutate the dict**, it only needs to be used if you are going to mutate the objects in the dict and don't want to mutate the same objects in the original dict (since they are the same.objevts) – juanpa.arrivillaga Jul 13 '23 at 15:31
  • 1
    @sdweldon I'm not 100% sure what you are asking in the comment, so maybe juanpa.arrivillaga's answer addresses it. If you are wondering about the references though it is sometimes helpful to look at `id()`. When I use your code I see: `id(y['string'])` -> `4301987184`, `id(x['string'])` -> `4301987184` indicating a single string in memory referenced by both dicts. – Mark Jul 13 '23 at 15:41
  • Thanks for the idea to use `id()` @Mark, that's very helpful! Makes complete sense now - I wasnt thinking about this in terms of modifying the dict's objects, but rather the dict itself. – sdweldon Jul 13 '23 at 16:15

1 Answers1

1

The behavior doesn't change on based on the value in the dict. You are doing two completely different things so the result you are seeing is different.

In the list case, you are mutating the list. In the str case, you are not mutating the string. If you did the same thing with the list:

y["list"] = y["list"] + ["one"]

You would see the same behavior.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • Thanks very much for the example. This makes complete sense. For others interested, here's a related answer that dives deep into @juanpa.arrivillaga example above: https://stackoverflow.com/a/2022044/1920263 – sdweldon Jul 13 '23 at 16:14