Is there a short-circuiting dictionary "get" method?

D

Dave Opstad

In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...

Dave
 
P

Peter Hansen

Dave said:
In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...

try:
value = d['x']
except KeyError:
value = bigscaryfunction()

get() is just a method, and arguments to methods are always
evaluated before being passed to the method, so the short
answer is "no, there is no 'version' of get() that will do
what you want".

-Peter
 
B

Bill Mill

Dave,

In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...

There is no short-circuit function like you're asking for, because
it's impossible in python. To pass an argument to the 'get' function,
python evaluates the bigscaryfunction before calling 'get'.

(I believe this means that python doesn't have "lazy evaluation", but
the language lawyers may shoot me down on that. Wikipedia seems to say
that it means python doesn't have "delayed evaluation").

Here are two ways to do what you want:

if 'x' in d: value = d['x']
else: value = bigscaryfunction()

or:

def sget(dict, key, func, *args):
if key in dict: return key
else: return func(*args)

sget(d, 'x', bigscaryfunction)

Both methods are untested, but should work with minor modifications.

Peace
Bill Mill
bill.mill at gmail.com
 
B

bearophileHUGS

Maybe this can help:

value = d.get('x', lambda: bigscaryfunction())

Bearophile
 
F

F. Petitjean

Le Wed, 09 Mar 2005 09:45:41 -0800, Dave Opstad a écrit :
In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...
def scary():
print "scary called"
return 22

d = dict(x=1)
d.get('x', lambda *a : scary())
# print 1
d.get('z', (lambda *a : scary())())
scary called
22

First (wrong) version :
d.get('z', lambda *a : scary())
 
B

Bill Mill

Maybe this can help:

value = d.get('x', lambda: bigscaryfunction())
<function <lambda> at 0x008D6870>

So this seems to be merely an obfuscation of:
<function test at 0x008D66B0>

I just wanted to ask, am I missing something?

Peace
Bill Mill
bill.mill at gmail.com
 
B

Bill Mill

Le Wed, 09 Mar 2005 09:45:41 -0800, Dave Opstad a écrit :
def scary():
print "scary called"
return 22

d = dict(x=1)
d.get('x', lambda *a : scary())

# print 1
d.get('z', (lambda *a : scary())())
scary called
22
but:
test called
1

So how is this different than d.get('x', test()) ?

Peace
Bill Mill
bill.mill at gmail.com
 
K

Kent Johnson

F. Petitjean said:
Le Wed, 09 Mar 2005 09:45:41 -0800, Dave Opstad a écrit :

def scary():
print "scary called"
return 22

d = dict(x=1)
d.get('x', lambda *a : scary())
# print 1
d.get('z', (lambda *a : scary())())
scary called
22

So you have to change the code at the point of call depending on whether the requested value is in
the dict? ;)

If you can get this to work I'm sure we can find other applications for such 'smart code' :)

Kent
 
S

Skip Montanaro

Dave> In this snippet:
Dave> d = {'x': 1}
Dave> value = d.get('x', bigscaryfunction())

Dave> the bigscaryfunction is always called, even though 'x' is a valid
Dave> key.

I sometimes use

value = d.get('x') or bsf()

Of course, this bsf() will get called if d['x'] evaluates to false, not just
None, so it won't work in all situations. It may help often enough to be
useful though.

Skip
 
R

Reinhold Birkenfeld

Dave said:
In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...

Well, if the dict only contains ints, here is a dirty hack (but don't
use it instead of the try/except approach):

class Littletinyproxy:
def __int__(self):
return bigscaryfunction()

d = dict(x=1)
value = int(d.get('x', Littletinyproxy()))


Reinhold
 
M

Michael Spencer

Dave said:
In this snippet:

d = {'x': 1}
value = d.get('x', bigscaryfunction())

the bigscaryfunction is always called, even though 'x' is a valid key.
Is there a "short-circuit" version of get that doesn't evaluate the
second argument if the first is a valid key? For now I'll code around
it, but this behavior surprised me a bit...

Dave
If (and this is a big if) you know that the dictionary contains no values that
evaluate to boolean false, then you can use the short-circuiting 'or' operator:
... print "scary"
...
Alternatively, you can just write your own getter function: ... if key in dict_:
... return dict_[key]
... else:
... return default()
...
The optimal choice of whether to "look before you leap" i.e., "if key in dict_"
or simply catch KeyError, depends on the ratio of hits to misses. Google will
turn up some experimental data on this, but, I seem to recall that if more than
10% attempts are misses, then LBYL is faster, because raising the exception is slow


Michael
 
J

Jeff Epler

untested

def my_getter(m, i, f):
try:
return m
except (KeyError, IndexError):
return f()

my_getter(d, 'x', bigscaryfunction)
my_getter(d, 'y', lambda: scaryinlineexpresion)

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)

iD8DBQFCL4SfJd01MZaTXX0RAhrjAJ9hYiHBfdCdVOI2fR41A/zuLlSaZACfeLAs
Uhjj/Aiqa80532KKeD0dPYI=
=YVEi
-----END PGP SIGNATURE-----
 
T

Terry Reedy

Skip Montanaro said:
value = d.get('x') or bsf()

Of course, this bsf() will get called if d['x'] evaluates to false, not
just
None,

value = (d.get('x') is not None) or bsf() #??

tjr
 
S

Steve Holden

Terry said:
value = d.get('x') or bsf()

Of course, this bsf() will get called if d['x'] evaluates to false, not
just
None,


value = (d.get('x') is not None) or bsf() #??
Unfortunately this will set value to True for all non-None values of
d['x']. Suppose d['x'] == 3:

regards
Steve
 
B

Bengt Richter

Terry said:
value = d.get('x') or bsf()

Of course, this bsf() will get called if d['x'] evaluates to false, not
just
None,


value = (d.get('x') is not None) or bsf() #??
Unfortunately this will set value to True for all non-None values of
d['x']. Suppose d['x'] == 3:
maybe (untested)
value = ('x' in d and [d['x']] or [bsf()])[0]

then there's always

if 'x' in d: value = d['x']
else: value = bsf()

or

try: value = d['x']
except KeyError: value = bsf()


Regards,
Bengt Richter
 
D

Duncan Booth

Bengt said:
then there's always

if 'x' in d: value = d['x']
else: value = bsf()

or

try: value = d['x']
except KeyError: value = bsf()

Its worth remembering that the first of those two suggestions is also
faster than using get, so you aren't losing on speed if you write the code
out in full: choose whichever seems clearest and uses the least contorted
code.

(The second is the fastest of all if the value is found, but a lot slower
if the exception gets thrown.)

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"value = d.get('x45', 'notfound')"
1000000 loops, best of 3: 0.427 usec per loop

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"value = d.get('z45', 'notfound')"
1000000 loops, best of 3: 0.389 usec per loop

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"if 'x45' in d: value=d['x45']" "else: value='notfound'"
1000000 loops, best of 3: 0.259 usec per loop

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"if 'z45' in d: value=d['z45']" "else: value='notfound'"
1000000 loops, best of 3: 0.131 usec per loop

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"try: value=d['x45']" "except: value='notfound'"
1000000 loops, best of 3: 0.158 usec per loop

C:\Python24\Lib>timeit.py -s "d={}" -s "for k in range(1000): d['x%s'%k]=k"
"try: value=d['z45']" "except: value='notfound'"
100000 loops, best of 3: 2.71 usec per loop
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top