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

后端 未结 5 837
一向
一向 2021-01-04 05:50

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         


        
相关标签:
5条回答
  • 2021-01-04 06:02

    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]
    
    0 讨论(0)
  • 2021-01-04 06:04

    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.

    0 讨论(0)
  • 2021-01-04 06:06

    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.

    0 讨论(0)
  • 2021-01-04 06:08

    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.

    0 讨论(0)
  • 2021-01-04 06:23

    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
    
    0 讨论(0)
提交回复
热议问题