如何制作一个python字典,返回字典中缺少键的键,而不是引发KeyError?

时间:2011-06-03 15:23:37

标签: python dictionary data-structures dictionary-missing

我想创建一个python字典,它返回字典中缺少键的键值。

用法示例:

dic = smart_dict()
dic['a'] = 'one a'
print(dic['a'])
# >>> one a
print(dic['b'])
# >>> b

6 个答案:

答案 0 :(得分:59)

dict有一个__missing__钩子:

class smart_dict(dict):
    def __missing__(self, key):
        return key

答案 1 :(得分:24)

为什么不使用

dic.get('b', 'b')

当然,你可以像其他人指出的那样将dict作为子类,但我发现每隔一段时间就会提醒自己get可以有一个默认值!

如果你想去defaultdict,试试这个:

dic = defaultdict()
dic.__missing__ = lambda key: key
dic['b'] # should set dic['b'] to 'b' and return 'b'

除了......好:AttributeError: ^collections.defaultdict^object attribute '__missing__' is read-only,所以你必须继承:

from collections import defaultdict
class KeyDict(defaultdict):
    def __missing__(self, key):
        return key

d = KeyDict()
print d['b'] #prints 'b'
print d.keys() #prints []

答案 2 :(得分:13)

第一位回应者提到defaultdict, 但您可以为__missing__的任何子类定义dict

>>> class Dict(dict):
        def __missing__(self, key):
            return key


>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d['z']
'z'

另外,我喜欢第二个受访者的方法:

>>> d = dict(a=1, b=2)
>>> d.get('z', 'z')
'z'

答案 3 :(得分:6)

恭喜。你也发现了无用的东西 标准collections.defaultdict类型。如果code smell那个可执行的midden堆 像我一样冒犯你敏感的感情,这是你的幸运 StackOverflow日。

感谢type() collections.defaultdict的禁忌 内置,制作一个非无用的默认字典类型既有趣又有利可图。

dict有什么不对.__缺少__()?

绝对没有,假设你喜欢过多的样板和dict的令人震惊的愚蠢 - 应该按预期行事,但实际上并非如此。公平地说,3-parameter variant Jochen Ritzel的子类DefaultDictaccepted solution 实施optional __missing__() method 太棒了 小规模用例的解决方法,只需要一个默认字典。

但这种样板很难扩展。如果你发现自己实例化了 多个默认词典,每个词典都有自己略有不同的逻辑 产生缺失的键值对,一种工业强度的替代品 自动化样板是有保证的。

或者至少很好。因为为什么不修理破碎的东西?

介绍DefaultDict

不到十行的纯Python(不包括docstrings,comments和 我们现在定义一个用用户定义初始化的collections.defaultdict类型 可调用生成缺失键的默认值。而可赎回通过了 标准DefaultDict类型无用地接受 参数,传递给我们的>>> dic = DefaultDict(lambda self, missing_key: missing_key) >>> dic['a'] = 'one a' >>> print(dic['a']) one a >>> print(dic['b']) b 类型的可调用有用地接受 以下两个参数:

  1. 此词典的当前实例。
  2. 生成默认值的当前缺失键。
  3. 鉴于此类型,解决sorin的问题 问题缩减为单行Python:

    def DefaultDict(keygen):
        '''
        Sane **default dictionary** (i.e., dictionary implicitly mapping a missing
        key to the value returned by a caller-defined callable passed both this
        dictionary and that key).
    
        The standard :class:`collections.defaultdict` class is sadly insane,
        requiring the caller-defined callable accept *no* arguments. This
        non-standard alternative requires this callable accept two arguments:
    
        #. The current instance of this dictionary.
        #. The current missing key to generate a default value for.
    
        Parameters
        ----------
        keygen : CallableTypes
            Callable (e.g., function, lambda, method) called to generate the default
            value for a "missing" (i.e., undefined) key on the first attempt to
            access that key, passed first this dictionary and then this key and
            returning this value. This callable should have a signature resembling:
            ``def keygen(self: DefaultDict, missing_key: object) -> object``.
            Equivalently, this callable should have the exact same signature as that
            of the optional :meth:`dict.__missing__` method.
    
        Returns
        ----------
        MappingType
            Empty default dictionary creating missing keys via this callable.
        '''
    
        # Global variable modified below.
        global _DEFAULT_DICT_ID
    
        # Unique classname suffixed by this identifier.
        default_dict_class_name = 'DefaultDict' + str(_DEFAULT_DICT_ID)
    
        # Increment this identifier to preserve uniqueness.
        _DEFAULT_DICT_ID += 1
    
        # Dynamically generated default dictionary class specific to this callable.
        default_dict_class = type(
            default_dict_class_name, (dict,), {'__missing__': keygen,})
    
        # Instantiate and return the first and only instance of this class.
        return default_dict_class()
    
    
    _DEFAULT_DICT_ID = 0
    '''
    Unique arbitrary identifier with which to uniquify the classname of the next
    :func:`DefaultDict`-derived type.
    '''
    

    理智。 最后。

    代码或它没有发生

    type()

    ...得到它, 到这个神秘的魔法是调用 3-parameter variant 内置type(default_dict_class_name, (dict,), {'__missing__': keygen,}) 的内容:

    dict

    这一行动态地生成一个新的__missing__子类别名 调用者定义的可调用的可选DefaultDict方法。注意截然不同 缺少样板,将@Override public void onCompletion(MediaPlayer mediaPlayer) { if (isShuffle) { if (intent.hasExtra("songIndex")) { Random random = new Random(); songIndex = random.nextInt((songList.size() - 1) + 1); playSongWithIndex(songIndex); } } } 用法减少到单行Python。

    极度胜利的自动化。

答案 4 :(得分:1)

我同意这应该很容易做到,也很容易设置不同的默认值或函数,以某种方式转换缺失值。

受到Cecil Curry answer的启发,我问自己:为什么不将默认生成器(常量或可调用的)作为类的成员,而不是生成不同的班级一直在吗?让我演示一下:

# default behaviour: return missing keys unchanged
dic = FlexDict()
dic['a'] = 'one a'
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'

# regardless of default: easy initialisation with existing dictionary
existing_dic = {'a' : 'one a'}
dic = FlexDict(existing_dic)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'

# using constant as default for missing values
dic = FlexDict(existing_dic, default = 10)
print(dic['a'])
# 'one a'
print(dic['b'])
# 10

# use callable as default for missing values
dic = FlexDict(existing_dic, default = lambda missing_key: missing_key * 2)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'bb'
print(dic[2])
# 4

它是如何工作的?没那么困难:

class FlexDict(dict):
    '''Subclass of dictionary which returns a default for missing keys.
    This default can either be a constant, or a callable accepting the missing key.
    If "default" is not given (or None), each missing key will be returned unchanged.'''
    def __init__(self, content = None, default = None):
        if content is None:
            super().__init__()
        else:
            super().__init__(content)
        if default is None:
            default = lambda missing_key: missing_key
        self.default = default # sets self._default

    @property
    def default(self):
        return self._default

    @default.setter
    def default(self, val):
        if callable(val):
            self._default = val
        else: # constant value
            self._default = lambda missing_key: val

    def __missing__(self, x):
        return self.default(x)

当然,人们可以争论是否允许在初始化后允许更改默认功能,但这只是意味着删除@default.setter并将其逻辑吸收到__init__

可以在两个额外的行中添加对当前(常量)默认值的内省。

答案 5 :(得分:0)