I've been fighting with a weird bug in my app: if I'm sending a push notification to 2+ users, only the first one receives the notification.
The bug was that I .pop
ed the message
key from the data.
Here's how it works (pseudo-code):
def send(users: [User], data: dict):
notifications = reduce(lambda acc, user: acc + [Notification(user, data)], users, [])
db.bulk_insert(notifications)
for notification in notifications:
notification.send()
...
def Notification.send(self):
message = self.data.pop('message')
Firebase.send(message, self.data)
The first notification will be sent and I thought that message
would be popped out of self.data
, but actually it would be popped out of the dict that self.data
points to! That means that every Notification object that has been initialized in the send
function using Notification(user, data)
(and not Notification(user, data.copy())
) would not have data['message']
anymore, leading to unexpected behavior.
If you really need to get rid of a key in a dict, you should .copy()
it before mutating.
If you don't - just stick to .get()
.
Top comments (5)
Good catch Defman!
Keep in mind that
copy()
does not create a deep copy so if you modify theNotification
objects... both references will point to the sameNotification
object that you modified :DIf you have memory constraints and you want to let the GC do its work you could investigate the usage of weakref:
I don't quite understand that. Doesn't
{"a": 1, "b": {"c": 2}}.copy()
create a copy of a dict (and allocate it in the memory) that would passassert id(Notification1.data) != id(Notification2.data)
(so by modifyingNotification1.data
I would not affectNotification2.data
)?copy()
performs a shallow copy:as you can see both
d
and its copyc_d
point to the sameinner_dict
which when modified it reflects on both the original and the copy.TLDR; if any of the values of the dict that's about to be copied is a reference to an object (in this case another dictionary, in your case a
Notification
object) then the original and the copy will point to the same object.I see. Thanks!
In my case,
data
does not hold aNotification
object. It doesn't hold any references at all, actually. It's a simple dict that looks like this:{"message": "my message", "some_attr": 123}
.If you're working with reference-types, it's probably better to never use mutating functions unless you absolutely have to.