22

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?

agf
  • 171,228
  • 44
  • 289
  • 238
Paulo
  • 6,982
  • 7
  • 42
  • 56
  • 1
    This question doesn't make sense. You're asking about the default argument, but you're trying it on a key that does exist - obviously, `get` will return the value for that key if it exists. What are you trying to achieve? – Daniel Roseman Sep 30 '11 at 13:21
  • 3
    @DanielRoseman His question is how does he get `run` to only be called if the value _doesn't_ exist, as for his current `get` it gets called no matter what. I clarified the last line of his question. – agf Sep 30 '11 at 13:28
  • Why is `run()` called in the first place? One would think `get()` wouldn't bother with the default value unless it was needed. – Caleb Koch May 21 '21 at 14:10
  • @CalebKoch: `get` doesn't do anything with the second argument if it's not needed, but ignoring the argument doesn't stop it from being evaluated. – user2357112 Jan 22 '23 at 18:08
  • @CalebKoch You're suggesting that Python should not evaluate its function arugments before calling a function, but that instead the called function should evaluate them, in the caller's context, if and only if they're "needed". That's not how argument passing works in Python. – Tom Karzes Jan 22 '23 at 18:08

6 Answers6

19

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
Eric
  • 95,302
  • 53
  • 242
  • 374
10

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.

Community
  • 1
  • 1
agf
  • 171,228
  • 44
  • 289
  • 238
  • I was trying to avoid using a conditional, however i think using defaultdict does exactly what i need, im going to mark glglgl's answer as the accepted because he was first, but thanks a lot :P, rly appreciate it – Paulo Sep 30 '11 at 13:48
3

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.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • Having to create `lambda`s around all of his data in the `dict` doesn't sound practical for the vast majority of real world situations. Unfortunately, the `defaultdict` also doesn't work for a lot of situations where you don't want to actually set values in the `dict` or you only want to get the default value in some situations. – agf Sep 30 '11 at 13:33
  • It works if you subclass it - see my 3rd example after the edit. – glglgl Sep 30 '11 at 13:42
  • That still stores the value in the `dict`, which he may or may not want to do. – agf Sep 30 '11 at 13:43
  • right - it is just an example which can be modified to not store that value: `def __missing__(self, key): return self.default_factory(key)`. I wanted to keep it close to the original behaviour. -> another edit – glglgl Sep 30 '11 at 13:44
  • If you're not storing in the `dict` just use a regular `dict` subclass with `__missing__` overridden, not a `defaultdict`. – agf Sep 30 '11 at 13:52
  • `defaultdict` gives me a handy `default_factory()` which I otherwise have to plug in manually. – glglgl Sep 30 '11 at 15:41
1

Here's what I use:

def lazy_get(d, k, f):
  return d[k] if k in d else f(k)

The fallback function f takes the key as an argument, e.g.

lazy_get({'a': 13}, 'a', lambda k: k)  # --> 13
lazy_get({'a': 13}, 'b', lambda k: k)  # --> 'b'

You would obviously use a more meaningful fallback function, but this illustrates the flexibility of lazy_get.


Here's what the function looks like with type annotation:

from typing import Callable, Mapping, TypeVar

K = TypeVar('K')
V = TypeVar('V')

def lazy_get(d: Mapping[K, V], k: K, f: Callable[[K], V]) -> V:
  return d[k] if k in d else f(k)
Kris
  • 22,079
  • 3
  • 30
  • 35
0

If you only know what the callable is likely to be at he get call site you could subclass dict something like this

    class MyDict(dict):

        def get_callable(self,key,func,*args,**kwargs):
            '''Like ordinary get but uses a callable to 
            generate the default value'''

            if key not in self:
                val = func(*args,**kwargs)
            else:
                val = self[key]
            return val

This can then be used like so:-

     >>> d = MyDict()
     >>> d.get_callable(1,complex,2,3)
     (2+3j)
     >>> d[1] = 2
     >>> d.get_callable(1,complex,2,3)
     2
     >>> def run(): print "run"
     >>> repr(d.get_callable(1,run))
     '2'
     >>> repr(d.get_callable(2,run))
     run
     'None'

This is probably most useful when the callable is expensive to compute.

rgammans
  • 307
  • 5
  • 11
0

I have a util directory in my project with qt.py, general.py, geom.py, etc. In general.py I have a bunch of python tools like the one you need:

# Use whenever you need a lambda default
def dictGet(dict_, key, default):
    if key not in dict_:
        return default()
    return dict_[key]

Add *args, **kwargs if you want to support calling default more than once with differing args:

def dictGet(dict_, key, default, *args, **kwargs):
    if key not in dict_:
        return default(*args, **kwargs)
    return dict_[key]
MathCrackExchange
  • 595
  • 1
  • 6
  • 25