3
  • I have following dictionary: original = {a:1, b:2}
  • I then run dict comprehension: extracted = {k:v for (k,v) in original.items() if k == 'a'}
  • The following dict is returned: {a:1}
  • If I mutate extracted['a'] = 2, original['a'] will still be equal to 1

Question:

Is there a way to make the above dict comprehension return by reference? For example extracted['a'] = 2 would result in original['a'] = 2.

I would prefer not to involve alteration of the original dictionary.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • 1
    Short answer: No. (Not without changing your datatypes, anyhow -- if `extracted['a']` and `original['a']` were both lists, *then* you could make them be the same object). – Charles Duffy Aug 16 '17 at 19:45
  • 4
    ...if you **really** wanted spooky action at a distance, I suppose you could implement your own `dict`-like object that would know about the original and update it on `__setitem__()`. But this would break peoples' expectations about your code, and otherwise be a Bad Idea. – Charles Duffy Aug 16 '17 at 19:47
  • 5
    Thinking about Python's datamodel in terms of the datamodel of C-like languages is rarely fruitful. So forget about the "by reference" stuff. For further info on this topic, please see [Facts and myths about Python names and values](http://nedbatchelder.com/text/names.html), which was written by SO veteran Ned Batchelder. – PM 2Ring Aug 16 '17 at 19:47
  • 1
    OTOH, if you make those dict values mutable, then you can mutate them, rather than assign to them. Eg, `original = {'a':[1], 'b':[2]}; extracted = {k:v for k,v in original.items() if k == 'a'}; extracted['a'][0] = 3; print(original)` prints `{'a': [3], 'b': [2]}` – PM 2Ring Aug 16 '17 at 19:52

2 Answers2

4

Your intended goal (of having a dictionary which, when updated, will also change the other dictionary from which it was derived) can be done even with immutable values, if your new dictionary is of a custom type with the desired logic added:

class MappedDict(dict):
    def __init__(self, orig, *args, **kwargs):
        self.__orig = orig
        dict.__init__(self, *args, **kwargs)
    def __setitem__(self, k, v):
        self.__orig[k] = v
        return dict.__setitem__(self, k, v)

d = {'a': 1, 'b': 2}
md = MappedDict(d, {k: v*2 for (k,v) in d.items()})
md['a']=5

...will leave both d and md having 'a' having the value 5, whereas b will differ (being 2 in the former and 4 in the latter).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    "Explicit is better than implicit", I seem to recall [someone saying](https://www.python.org/dev/peps/pep-0020/#id3). :) – Charles Duffy Aug 16 '17 at 20:12
  • But in case you use a conditional comprehension like in the question `{k:v for (k,v) in original.items() if k == 'a'}` and you alter the `md['b']` that would propagate to the original even though `md` didn't have a `b` element (by definition it was excluded). I'm not sure that's unwanted or wanted, I just wanted to mention this. :) – MSeifert Aug 16 '17 at 23:15
3

No, comprehensions always return a shallow copy (well, actually it's a new object containing references to the values you iterate over). However it's only a shallow copy, so if you use mutable types as values and you change them in-place the change will propagate to the original object.

>>> original = {'a':[], 'b':[]}
>>> extracted = {k:v for (k,v) in original.items() if k == 'a'}
>>> extracted['a'].append(1)  # change one value in extracted in-place
>>> original                  # original also changed
{'a': [1], 'b': []}
>>> extracted
{'a': [1]}
MSeifert
  • 145,886
  • 38
  • 333
  • 352