Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callable as the default argument to dict.get without it being called if the key exists

I am trying to provide a function as the default argument for the dictionary's get function, like this

def run():
   print "RUNNING"

test = {'store':1}
test.get('store', run())

However, when this is run, it displays the following output:

RUNNING
   1

so my question is, as the title says, is there a way to provide a callable as the default value for the get method without it being called if the key exists?

like image 705
Paulo Avatar asked Sep 30 '11 13:09

Paulo


3 Answers

Another option, assuming you don't intend to store falsy values in your dictionary:

test.get('store') or run()

In python, the or operator does not evaluate arguments that are not needed (it short-circuits)


If you do need to support falsy values, then you can use get_or_run(test, 'store', run) where:

def get_or_run(d, k, f):
    sentinel = object()  # guaranteed not to be in d
    v = d.get(k, sentinel)
    return f() if v is sentinel else v
like image 187
Eric Avatar answered Nov 17 '22 16:11

Eric


See the discussion in the answers and comments of dict.get() method returns a pointer. You have to break it into two steps.

Your options are:

  1. Use a defaultdict with the callable if you always want that value as the default, and want to store it in the dict.

  2. Use a conditional expression:

    item = test['store'] if 'store' in test else run()
    
  3. Use try / except:

    try:
        item = test['store']
    except KeyError:
        item = run()
    
  4. Use get:

    item = test.get('store')
    if item is None:
        item = run()
    

And variations on those themes.

glglgl shows a way to subclass defaultdict, you can also just subclass dict for some situations:

def run():
    print "RUNNING"
    return 1

class dict_nokeyerror(dict):
    def __missing__(self, key):
        return run()

test = dict_nokeyerror()

print test['a']
# RUNNING
# 1

Subclassing only really makes sense if you always want the dict to have some nonstandard behavior; if you generally want it to behave like a normal dict and just want a lazy get in one place, use one of my methods 2-4.

like image 8
agf Avatar answered Nov 17 '22 16:11

agf


I suppose you want to have the callable applied only if the key does not exist.

There are several approaches to do so. One would be to use a defaultdict, which calls run() if key is missing.

from collections import defaultdict
def run():
   print "RUNNING"

test = {'store':1}
test.get('store', run())

test = defaultdict(run, store=1) # provides a value for store
test['store'] # gets 1
test['runthatstuff'] # gets None

Another, rather ugly one, one would be to only save callables in the dict which return the apropriate value.

test = {'store': lambda:1}
test.get('store', run)() # -> 1
test.get('runrun', run)() # -> None, prints "RUNNING".

If you want to have the return value depend on the missing key, you have to subclass defaultdict:

class mydefaultdict(defaultdict):
    def __missing__(self, key):
        val = self[key] = self.default_factory(key)
        return val

d = mydefaultdict(lambda k: k*k)
d[10] # yields 100

@mydefaultdict # decorators are fine
def d2(key):
    return -key
d2[5] # yields -5

And if you want not to add this value to the dict for the next call, you have a

def __missing__(self, key): return self.default_factory(key)

instead which calls the default factory every time a key: value pair was not explicitly added.

like image 3
glglgl Avatar answered Nov 17 '22 15:11

glglgl