可调用对象作为默认参数传递给dict.get()方法,在键存在时不被调用

22
我正在尝试将函数作为字典的get函数的默认参数提供,像这样:
def run():
   print "RUNNING"

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

然而,当运行此代码时,它显示以下输出:

RUNNING
   1

那么我的问题是:如标题所述,是否有一种方法可以为get方法提供可调用的默认值,而不会在键存在时调用它?


1
这个问题没有意义。你在询问默认参数,但是你正在尝试使用一个已经存在的键 - 显然,如果它存在,get将返回该键的值。你想要实现什么? - Daniel Roseman
3
他的问题是如何使run只在值不存在时调用,因为他当前的get会在任何情况下都被调用。我澄清了他问题的最后一行。 - agf
为什么首先要调用 run()?人们可能认为 get() 不会在不需要时使用默认值。 - Caleb Koch
@CalebKoch:如果第二个参数不需要,get函数不会对其进行任何操作,但忽略该参数并不会阻止其被计算。 - user2357112
@CalebKoch,您建议Python在调用函数之前不应该评估其函数参数,而是应该在调用函数时在调用者的上下文中仅在需要时对它们进行评估。但这不是Python中参数传递的工作方式。 - Tom Karzes
6个回答

19

另一种选择是,假设您不打算在字典中存储假值

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

在Python中,or运算符不会对不需要的参数进行求值(它短路)。
如果您确实需要支持假值,请使用get_or_run(test, 'store', run),其中:
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

2
简短而精炼。完美。 - shadfc

10
请参阅dict.get()方法返回指针的答案和评论。您需要分成两个步骤。
您的选项有:
  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()
    

还有一些类似的变体。

glglgl展示了一种对defaultdict进行子类化的方法,对于某些情况,您也可以只对dict进行子类化:

def run():
    print "RUNNING"
    return 1

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

test = dict_nokeyerror()

print test['a']
# RUNNING
# 1

只有当您始终希望 dict 具有某些非标准行为时,子类才真正有意义;如果您通常希望它的行为像普通的 dict ,并且只想在一个位置使用 get ,请使用我的方法2-4。


我一开始试图避免使用条件语句,但我认为使用defaultdict正好满足我的需求,我将标记glglgl的答案为已接受,因为他最先回答了,但非常感谢你的帮助:P。 - Paulo

3
我想你希望在键不存在时仅应用可调用对象。有几种方法可以实现这一点。其中一种方法是使用defaultdict,如果键不存在则调用run()
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

另一种相对不太好看的方法是,只在字典中保存返回适当值的可调用函数。

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

如果您希望返回值取决于缺失的键,则需要子类化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

如果你不想将这个值添加到字典中以便下一次调用,你可以使用
def __missing__(self, key): return self.default_factory(key)

相反,它每次未明确添加 key: value 对时都会调用默认工厂函数。


在大多数实际情况下,需要在字典中创建lambda表达式并不实用。不幸的是,defaultdict也不能适用于许多情况,例如您不想在字典中设置值或者只想在某些情况下获取默认值。 - agf
如果你将它作为子类,它就能工作 - 请参考我在编辑后的第三个示例。 - glglgl
这仍然将值存储在“dict”中,他可能想要这样做,也可能不想。 - agf
对,这只是一个例子,可以进行修改,以避免存储该值: def __missing__(self, key): return self.default_factory(key)。我想保持它接近原始行为。-> 另一次编辑 - glglgl
如果你不需要在dict中存储,只需使用一个常规的dict子类,并覆盖__missing__,而不是使用defaultdict - agf
defaultdict 给了我一个方便的 default_factory(),否则我就必须手动插入它。 - glglgl

1
这是我使用的:
def lazy_get(d, k, f):
  return d[k] if k in d else f(k)

回退函数f以键作为参数,例如:

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

显然,您会使用更有意义的回退函数,但这说明了lazy_get的灵活性。


这是带有类型注释的函数示例:

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)

0
如果您只知道在获取调用站点时可调用对象可能是什么,那么您可以像这样子类化字典:
    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

然后可以像这样使用:

     >>> 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'

这在可调用对象计算代价高昂时可能非常有用。

0

我的项目中有一个名为util的目录,其中包含qt.pygeneral.pygeom.py等文件。在general.py中,我有一堆像你需要的Python工具。

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

如果您想支持使用不同的参数多次调用默认值,请添加*args, **kwargs
def dictGet(dict_, key, default, *args, **kwargs):
    if key not in dict_:
        return default(*args, **kwargs)
    return dict_[key]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接