9

In Python the dict object has a "pop" method which returns and removes a key from a dict, with an optional default if the key doesn't exist.

What is the best way to do this for general object attributes?

I'm thinking:

my_obj.__dict__.pop('key_name', default)

Should be a good option but I'm worried that directly muting the object's dict might have unintended side effects that I'm not aware of. Is there a better option?

galarant
  • 1,959
  • 19
  • 24
  • 2
    Why do you want to do this? It's usually not a good idea to treat object attributes this way in Python. – user2357112 Oct 23 '13 at 19:45
  • what exactly are you asking? i mean, you'd use `my_obj.pop('key_name', default)`, and then you'd implement to do something that makes sense for your object, which will be different if it represents an ion storm or an ice cream truck... – Corley Brigman Oct 23 '13 at 19:46
  • 2
    Just because the attributes of an object are implemented with a dictionary doesn't mean it makes sense for an object itself to support a `pop` operation. Part of object-oriented design is that each instance of a class shares certain properties defined by the class. Removing such a property from one object but not another breaks that design. – chepner Oct 23 '13 at 19:51

2 Answers2

7

(for objects with a __dict__) "popping" an attribute is the same thing as popping it from the __dict__, and therefor your suggested implementation is correct.

EDIT: @Erik correctly pointed out that using __dict__.pop can raise a KeyError, where the apporpriate exception is an AttributeError. So a better implementation would add try/catch/reraise-as-AttributeError.

I would just point out that what you're trying to do is "popping" an attribute of an object, not a key.

shx2
  • 61,779
  • 13
  • 130
  • 153
  • Ok this might be a stupid question but...doesn't every new-style object have a ```__dict__``` attribute? It's built into the language itself. – galarant Oct 26 '13 at 22:04
  • no. e.g. if they use [slots](http://stackoverflow.com/questions/472000/python-slots) or if they are types implemented in an extension (e.g. numpy arrays) – shx2 Oct 27 '13 at 06:37
3

EDIT: A possibly faster and thread safe alternative with proper mapping of KeyError to AttributeError:

_NO_DEFAULT = object()  # so that None could be used as a default value also

def popattr(obj, name, default=_NO_DEFAULT):
    try:
        return obj.__dict__.pop(name)
    except KeyError:
        if default is not _NO_DEFAULT:
            return default
        raise AttributeError("no attribute '%s'" % name)
        # or getattr(obj, name) to generate a real AttributeError

(previous answer)

Something like this should work:

def popattr(obj, name):
    ret = getattr(obj, name)
    delattr(obj, name)
    return ret

Although obj.__dict__.pop(name) also works but you'll get a KeyError in case of non-existent attributes as opposed to AttributeError, and I'd say the latter is semantically correct for object attribute access; KeyError is used for dictionaries, but you're not really accessing a dictionary; the fact that you're using __dict__ is just an implementation detail of attribute popping and should be hidden, which is what popattr does.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
  • @Ben: sorry, my bad; fixed (and simplified) – Erik Kaplun Oct 23 '13 at 19:47
  • using `obj.__dict__.pop`, like OP suggested, is better for being faster, and atomic (thus thread-safe). Your point about `KeyError` vs `AttributeError` is a good point though. – shx2 Oct 23 '13 at 19:51
  • @shx2: I've added a solution that covers both aspects. – Erik Kaplun Oct 23 '13 at 20:21
  • That'd do. I'd add `*args` to support a default value, because it seems like that's the functionality th OP is after. – shx2 Oct 23 '13 at 20:28
  • `*args` would be more compact but the argument signature of the function would suffer then (e.g. if you're used to doing `?` in IPython. – Erik Kaplun Oct 23 '13 at 20:40