【问题标题】:Nested dictionary that acts as defaultdict when setting items but not when getting items嵌套字典,在设置项目时充当默认字典,但在获取项目时不充当
【发布时间】:2020-07-21 22:14:15
【问题描述】:

我想实现一个类似dict的数据结构,它具有以下属性:

from collections import UserDict

class TestDict(UserDict):
    pass

test_dict = TestDict()

# Create empty dictionaries at 'level_1' and 'level_2' and insert 'Hello' at the 'level_3' key.
test_dict['level_1']['level_2']['level_3'] = 'Hello'

>>> test_dict
{
    'level_1': {
        'level_2': {
            'level_3': 'Hello'
        }
    }
}

# However, this should not return an empty dictionary but raise a KeyError.
>>> test_dict['unknown_key']
KeyError: 'unknown_key'

据我所知,问题是python不知道__getitem__是在设置项目的上下文中被调用的,即第一个例子,还是在获取和项目的上下文中,第二个例子。

我已经看过Python `defaultdict`: Use default when setting, but not when getting,但我不认为这个问题是重复的,或者它回答了我的问题。

如果您有任何想法,请告诉我。

提前致谢。

编辑:

使用以下方法可以实现类似的效果:

def set_nested_item(dict_in: Union[dict, TestDict], value, keys):
    for i, key in enumerate(keys):
        is_last = i == (len(keys) - 1)
        if is_last:
            dict_in[key] = value
        else:
            if key not in dict_in:
                dict_in[key] = {}
            else:
                if not isinstance(dict_in[key], (dict, TestDict)):
                    dict_in[key] = {}

            dict_in[key] = set_nested_item(dict_in[key], value, keys[(i + 1):])
        return dict_in


class TestDict(UserDict):
    def __init__(self):
        super().__init__()

    def __setitem__(self, key, value):
        if isinstance(key, list):
            self.update(set_nested_item(self, value, key))
        else:
            super().__setitem__(key, value)

test_dict[['level_1', 'level_2', 'level_3']] = 'Hello'
>>> test_dict
{
    'level_1': {
        'level_2': {
            'level_3': 'Hello'
        }
    }
}



【问题讨论】:

  • 当前想法:覆盖 __setitem__ 以接受列表作为键。列表不能是字典键,因此当您发出 test_dict[['level_1', 'level_2', 'level_3']] = 'Hello' 时,该方法应该下降到内部字典或根据需要创建它们。这不是你要求的,但这是我能得到的最接近的。
  • @timgeb 谢谢,必须这样做。添加了一个编辑。

标签: python dictionary defaultdict


【解决方案1】:

这是不可能的。

test_dict['level_1']['level_2']['level_3'] = 'Hello'

在语义上等价于:

temp1 = test_dict['level_1'] # Should this line fail?
temp1['level_2']['level_3'] = 'Hello'

但是...如果无论如何都要实现它,您可以检查 Python 堆栈以获取/解析调用代码行,然后根据调用代码行是否包含赋值来改变行为!不幸的是,有时调用代码在堆栈跟踪中不可用(例如,交互调用时),在这种情况下,您需要使用 Python 字节码。

import dis
import inspect
from collections import UserDict

def get_opcodes(code_object, lineno):
    """Utility function to extract Python VM opcodes for line of code"""
    line_ops = []
    instructions = dis.get_instructions(code_object).__iter__()
    for instruction in instructions:
        if instruction.starts_line == lineno:
            # found start of our line
            line_ops.append(instruction.opcode)
            break
    for instruction in instructions:
        if not instruction.starts_line:
            line_ops.append(instruction.opcode)
        else:
            # start of next line
            break
    return line_ops

class TestDict(UserDict):
    def __getitem__(self, key):
        try:
            return super().__getitem__(key)
        except KeyError:
            # inspect the stack to get calling line of code
            frame = inspect.stack()[1].frame
            opcodes = get_opcodes(frame.f_code, frame.f_lineno)
            # STORE_SUBSCR is Python opcode for TOS1[TOS] = TOS2
            if dis.opmap['STORE_SUBSCR'] in opcodes:
                # calling line of code contains a dict/array assignment
                default = TestDict()
                super().__setitem__(key, default)
                return default
            else:
                raise

test_dict = TestDict()
test_dict['level_1']['level_2']['level_3'] = 'Hello'
print(test_dict)
# {'level_1': {'level_2': {'level_3': 'Hello'}}}

test_dict['unknown_key']
# KeyError: 'unknown_key'

以上只是部分解决方案。如果同一行上有其他字典/数组分配,它仍然可以被愚弄,例如other['key'] = test_dict['unknown_key']。更完整的解决方案需要实际解析代码行以找出变量在赋值中出现的位置。

【讨论】:

  • 这太棒了。
猜你喜欢
  • 1970-01-01
  • 2019-06-20
  • 2014-02-01
  • 1970-01-01
  • 2020-09-02
  • 2018-01-20
  • 1970-01-01
  • 2021-11-01
  • 2020-05-29
相关资源
最近更新 更多