itemgetter with default arguments

Peter Otten __peter__ at web.de
Sat May 5 03:33:37 EDT 2018


Steven D'Aprano wrote:

> A re-occurring feature request is to add a default to itemgetter and
> attrgetter. For example, we might say:
> 
> from operator import itemgetter
> f = itemgetter(1, 6, default="spam")  # proposed feature
> f("Hello World!")  # returns ('e', 'W')
> f("Hello")         # returns ('e', 'spam')
> 
> 
> Two senior developers have rejected this feature, saying that we should
> just use a lambda instead.
> 
> I might be slow today, but I cannot see how to write a clear, obvious,
> efficient lambda that provides functionality equivalent to itemgetter
> with a default value.
> 
> Putting aside the case where itemgetter takes multiple indexes, how about
> the single index case? How could we get that functionality using a lambda
> which is simple and obvious enough to use on the fly as needed, and
> reasonably efficient?
> 
> Here are the specifications:
> 
> * you must use lambda, not def;
> 
> * the lambda must take a single function, the sequence you want to
>   extract an item from;
> 
> * you can hard-code the index in the body of the lambda;
> 
> * you can hard-code the default value in the body of the lambda;
> 
> * if sequence[index] exists, return that value;
> 
> * otherwise return the default value;
> 
> * it should support both positive and negative indices.
> 
> Example: given an index of 2 and a default of "spam":
> 
>     (lambda seq: ... )("abcd") returns "c"
> 
>     (lambda seq: ... )("") returns "spam"
> 
> 
> I might be missing something horribly obvious, but I can't see how to do
> this using a lambda. I tried using slicing:
> 
>     seq[index:index+1]
> 
> which will return either an empty slice or a one-item slice, but that
> didn't help me. I feel I'm missing something either obvious, or something
> impossible, and I don't know which.
> 
> (This isn't a code-golf problem. I care more about being able to do it at
> all, than about doing it in the minimum number of characters.)

I think you have established that there is no straight-forward way to write 
this as a lambda. But is adding a default to itemgetter the right 
conclusion?

If there were an exception-catching decorator you could write

f = catch(IndexError, "spam")(itemgetter(2))


>>> from operator import itemgetter
>>> def catch(exc, default):
...     def deco(f):
...         def catcher(*args, **kw):
...             try: return f(*args, **kw)
...             except exc: return default
...         return catcher
...     return deco
... 
>>> f = catch((IndexError, KeyError), "spam")(itemgetter(1))
>>> f("abc")
'b'
>>> f("")
'spam'
>>> f(dict(a=1))
'spam'
>>> f({1: "ham"})
'ham'

This may be useful for other applications:

>>> g = catch(ZeroDivisionError, "#error")(lambda x: 1/x)
>>> g(2)
0.5
>>> g(0)
'#error'





More information about the Python-list mailing list