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 .poped 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 theNotificationobjects... both references will point to the sameNotificationobject 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.dataI would not affectNotification2.data)?copy()performs a shallow copy:as you can see both
dand its copyc_dpoint to the sameinner_dictwhich 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
Notificationobject) then the original and the copy will point to the same object.I see. Thanks!
In my case,
datadoes not hold aNotificationobject. 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.