1

I have initialized a class with one positional argument (defaults to an empty dict) that gets updated inside the __init__ function with the kwargs items. The problem is when I make multiple instances using kwargs argument, the subsequent calls gets the items from previous calls dict, but, given that every call is for a fresh instance, I would expect that data and kwargs arguments had a limited scope to the init method, here are some examples:

class Test:
    def __init__(self, data: dict = {}, **kwargs):
        data.update(kwargs)
        self.data = data


d1 = {"A": 1, "B": 2}
d2 = {"A": 1, "C": 2}


t1 = Test(**d1)
print(t1.data)
# printout -> {'A': 1, 'B': 2}
t2 = Test(**d2)
print(t1.data)
# printout -> {'A': 1, 'B': 2, 'C': 2}
# expected -> {'A': 1, 'A': 2}
print(t2.data)  
# printout -> {'A': 1, 'B': 2, 'C': 2}
# expected -> {'A': 1, 'B': 2}
martineau
  • 119,623
  • 25
  • 170
  • 301
Bravhek
  • 327
  • 3
  • 10
  • `def __init__(self, data: dict = None, **kwargs)` – azro May 31 '22 at 19:03
  • `kwargs` itself is already a new `dict` created for each call. You could just write `self.data = kwargs`. – chepner May 31 '22 at 19:04
  • 1
    `data = data or {}; self.data = {**data, **kwargs}` – azro May 31 '22 at 19:04
  • @azro Check explicitly for `data is None`; `data or {}` would use a new empty dict even if an explicit empty dict was provided, which may result in the wrong empty dict being used. – chepner May 31 '22 at 19:06
  • 1
    @chepner: That said, it's entirely reasonable API design to always copy the caller's argument to prevent such mutation of caller arguments. I actually kind of like keeping it `data: dict = {}`, but unconditionally leading with `data = dict(data)` or the like to force an immediate shallow-copy and conversion to a single uniform type (or even `copy.deepcopy(dict(data))` if nested data structures are expected). – ShadowRanger May 31 '22 at 19:16
  • thankyou guyys, very enlightning discussion. Indeed a similar question had been answered here https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument , with a very clear explanatiion here: https://web.archive.org/web/20200221224620/http://effbot.org/zone/default-values.htm, but i'd like to keep the question and correct answer based upont your comments. – Bravhek May 31 '22 at 19:23
  • Based upon @ShadowRanger and azro comments i ended up using: def __init__(self, data: dict = {}, **kwargs): kwargs.update(data) self.data = kwargs – Bravhek May 31 '22 at 19:25
  • @Bravhek: That works, but just a note, it has subtly different behavior if both `data` and `kwargs` contain the same key. In your new code, in case of overlap, you keep the value from `data`; in the original code, you kept the value from `kwargs`. This may be what you want, but be aware it's different. – ShadowRanger May 31 '22 at 19:27
  • @ShadowRanger, you are right, i will also add an error handlgin routine to prevent overlaps. Thanks. – Bravhek May 31 '22 at 19:32

0 Answers0