popkey() method for dictionaries?

Alex Martelli aleax at aleax.it
Mon Nov 18 04:25:53 EST 2002


Raymond Hettinger wrote:

>> [ 639806 ] Optional argument for dict.pop() method
>>
>> at:
>>
> 
https://sourceforge.net/tracker/index.php?func=detail&aid=639806&group_id=54
> 70&atid=105470
>>
>> Sorry but I just saw that my proof-of-concept code in python got squashed
> by
>> SF. I didn't know that SF removed all leading whitespace, hence
>> destroying indentation. Here it is again for reference:
> 
> Do you have use cases demonstrating the value of a default rather than an
> exception?

I'm not the OP, but: while I still have no use of dict.pop in my code (as
I need to keep my code compating with Python versions that lack dict.pop),
those areas of my code where I'd LIKE to eventually insert dict.pop are
as follows:

-- some base class X has methods that accept a lot of keyword arguments -- 
   e.g. X can typically be some GUI widget
-- I subclass X with my own Y to specialize some behavior, and typically
   want to add some specialized options (keyword arguments) of mine

So my code typically runs something like:

class Y(X):

    specialized_options = {'foo': 23, 'bar': 42, 'baz': 66}

    def __init__(self, **kwds):
        # get specialized opts out of kwds:
        specialized_options = {}
        for opt in self.specialized_options:
            try:
                val = kwds[opt]
            except KeyError:
                val = self.specialized_options[opt]
            else:
                del kwds[opt]
            specialized_options[opt] = val
        X.__init__(self, **kwds)
        for opt, val in specialized_options.items():
            setattr(self, opt, val)

or obvious variants such as starting with
        specialized_options = self.specialized_options.copy()
and/or using "if opt in kwds" rather than the try/except (not
much in it one way or another), and/or looping with "for opt, val"
on self.specialized_options.items() [or .iteritems()], etc.  (Of
course I could use a list of pairs just as well as a dict for
specialized_options, etc, etc -- just pointing all of these obvious
and irrelevant issues in the faint hope that followups won't detour
on them but rather stay focused on the .pop issue...:-).

Now, if I could use dict.pop _with exception behavior only_ that might 
become (repeating the body of __init__ only):

        specialized_options = {}
        for opt in self.specialized_options:
            try:
                val = kwds.pop(opt)
            except KeyError:
                val = self.specialized_options[opt]
            specialized_options[opt] = val
        X.__init__(self, **kwds)
        for opt, val in specialized_options.items():
            setattr(self, opt, val)

this IS a slight enhancement -- I save the else clause in the
try/except in the for loop's body.  But, rather slight: I still
do need the try/except, or equivalently an "if opt in kwds" test.

If I could give dict.pop a default value, then I would need
no try/except nor any membership-test:

        specialized_options = {}
        for opt in self.specialized_options:
             val = kwds.pop(opt, self.specialized_options[opt])
            specialized_options[opt] = val
        X.__init__(self, **kwds)
        for opt, val in specialized_options.items():
            setattr(self, opt, val)

I think this is a substantial simplification.  I might or might
not bother doing the refactoring to use kwds.pop with the exception,
as the enhancement is so slight, but this one I'd jump for.  Further,
removing the need to handle the exceptions would enable me to use
a list comprehension instead if I so wished:

        specialized_options = [ (opt, kwds.pop(opt, val))
            for opt, val in self.specialized_options.items() ]

replacing the first 4 statements in the latest snippet -- I'd definitely 
not make specialized_options a dict in this case, as a list of pairs is 
obviously simpler to build -- so the final for would be:
        for opt, val in specialized_options:
            setattr(self, opt, val)
instead.

Not sure if I'd go for this one.  Hmmm, perhaps.  But I'd sure
like to have the option, anyway.


> Also, discuss why similarity with dict.get() applies instead of symmetry
> with list.pop() or dict.popitem().

popitem has no symmetry whatsoever since it takes no argument, of course.  
So, I must be missing something: WHAT symmetry?

lists have no .get method which lets you supply a default.  Maybe they 
should, because I sure DO code very often:
    if 0 <= i < len(somelist):
        xx = somelist[i]
    else:
        xx = somedefault
or variants thereof where i's allowed to be negative down to -len(somelist),
and being able to code "xx = somelist.get(i, somedefault)", just as I could 
with a dictionary instead of a list, might be quite handy.

list.pop can take 0 or 1 argument.  If you're claiming there is symmetry, 
you must be proposing to have dict.pop take 0 or 1 argument too -- but 
that's not the case (it might have been a possible design, but instead a 
separate .popitem method was introduced for dictionaries).  Since there is 
no symmetry anyway, there is no argument for symmetry between list.pop and 
dict.pop.

I'm not sure what's being proposed for somedict.pop(x) (called with just 
one argument) to do when "x not in somedict" -- I'd like that to raise an 
exception, NOT meekly return None.  So, I'm not pumping for strong symmetry 
between dict.get and dict.pop -- not sure if the proposal as tabled is.  
The point is that, when you're simply accessing an item, you can already 
choose between:
    somedict[x]
when you want x's absence to raise an exception, versus
    somedict.get(x)
when you want x's absence to meekly return None instead.  Both behaviors 
are useful in different circumstances (i.e. whether you expect or not that 
absence, or whether in the absence case you must perform some very 
different processing).  I would NOT like it if there was no handy way to 
say "delete-and-return somedict[x] which SHOULD be there so please give me 
an exception if it isn't" which is probably more common than the cases 
where you fully expect it to possibly not be there.

The analogy rather than symmetry that I see is with builtin getattr: if I 
don't supply a default I get an exception if the attribute is absent, but I 
also get the opportunity to supply a default and avoid the exception.


Alex




More information about the Python-list mailing list