it-swarm.dev

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

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

用法示例:

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

dicts有一个 __missing__ hook为此:

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

你为什么不用它

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 []
25
Daren Thomas

第一个受访者提到了defaultdict,但你可以为dict的任何子类定义__missing__

>>> 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'
13
Raymond Hettinger

祝贺你。 您也发现了标准collections.defaultdict类型的无用性。如果那个令人难以忍受的 code smell 的堆积感冒了你的微妙感受,就像我一样,这是你幸运的StackOverflow日。

由于type()内置的 3参数变体 的禁忌奇迹,制作一个非无用的默认字典类型既有趣又有利可图。

什么是dict错误.__缺少__()?

绝对没有,假设你喜欢过多的样板和collections.defaultdict的令人震惊的愚蠢 - 哪个应该按预期运行但实际上没有。公平地说, Jochen Ritzel接受解决方案 继承​​dict并实现 optional __missing__()方法一个很好的解决方案,适用于只需要一个小规模的用例单个默认字典。

但是这种样板很难扩展。如果您发现自己实例化多个默认词典,每个词典都有自己稍微不同的逻辑来生成缺失的键值对,那么就需要一个工业级的替代自动化样板。

或者至少尼斯。因为为什么不修理坏了什么?

介绍DefaultDict

在不到十行纯Python(不包括文档字符串,注释和空格)中,我们现在定义一个DefaultDict类型,该类型使用用户定义的可调用来生成缺失键的默认值。传递给标准collections.defaultdict类型的callable无用地接受no参数,而传递给DefaultDict类型的callable有用地接受以下两个参数:

  1. 这本词典的当前实例。
  2. 当前缺少的密钥,用于生成默认值。

鉴于这种类型,求解 sorin 的问题简化为单行Python:

>>> dic = DefaultDict(lambda self, missing_key: missing_key)
>>> dic['a'] = 'one a'
>>> print(dic['a'])
one a
>>> print(dic['b'])
b

理智。 最后。

代码或它没有发生

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参数变体 的调用:

type(default_dict_class_name, (dict,), {'__missing__': keygen,})

此单行动态生成一个新的dict子类,将可选的__missing__方法别名化为调用者定义的可调用方法。注意明显缺乏样板,将DefaultDict用法减少到单行Python。

自豪的胜利。

6
Cecil Curry

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

灵感来自 Cecil Curry回答 ,我问自己:为什么不将默认生成器(常量或可调用的)作为类的成员,而不是始终生成不同的类?让我来证明:

# 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__中。

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

1
Axel

子类_​​dict__getitem__方法。例如, 如何正确地子类化dict并覆盖__getitem__和__setitem__

0
seb