自定义词典,以使其在**上保持__getitem __(打开星级包装)

时间:2018-12-24 14:17:34

标签: python dictionary method-resolution-order

大家圣诞快乐,

我正在实现一个允许属性访问的自定义词典,例如dct.attribute。字典可以嵌套,因此dct.nested_dct.attribute也应该是可能的。除star-star-unpacking之外,这已经很好地工作了。我认为我能够表达出我想用代码做得比单词做得更好的事情。这是我正在写的课。测试应该非常清楚地说明其作用:

class DotDict(dict):
    def __getattr__(self, item):
        return self.__getitem__(item)

    def __getitem__(self, item):
        item = super().__getitem__(item)
        if isinstance(item, dict):
            return self.__class__(item)
        return item


class TestDotDict:
    @pytest.fixture
    def dot_dict(self):
        input_dict = dict(
            a=1,
            b=dict(
                c=2,
                d=3,
            )
        )
        return DotDict(input_dict)

    def test_can_access_by_dot(self, dot_dict):
        assert dot_dict.a == 1

    def test_returned_dicts_are_dot_dicts(self, dot_dict):
        b_dict = dot_dict["b"]
        assert isinstance(b_dict, DotDict)
        assert b_dict.c == 2

    def test_getting_item_also_returns_dot_dicts(self, dot_dict):
        b_dict = dot_dict["b"]
        assert isinstance(b_dict, DotDict)
        assert b_dict.c == 2

    def test_unpack_as_function_arguments_yields_dot_dicts_for_children(self, dot_dict):
        # this is failing
        def checker(a, b):
            assert a == 1
            assert b.c == 2
        checker(**dot_dict)

如评论中所述,最后一次测试失败。有人知道如何解决吗?

根据以下问题的答案:star unpacking for own classes,我认为我需要继承自collections.abc.Mappingdict。但是,这并不能解决问题。

我当时认为这可能与我不太清楚的MRO有关。但是无论我是否将类定义更改为

class DotDict(Mapping, item):

class DotDict(item, Mapping):

我的测试不会变成绿色。

3 个答案:

答案 0 :(得分:1)

test_star_star_mapping_maintains_child_dot_dicts中,您创建的是dict而不是DotDict,因此重构为:

def test_star_star_mapping_maintains_child_dot_dicts(self, dot_dict):
    obtained_via_star = DotDict(dict(**dot_dict))
    b_dict = obtained_via_star["b"]
    assert b_dict.c == 2

将通过测试,因为您现在正在创建DotDict。也许您想删除部分dict(**dot_dict),以便此版本也可以使用:

def test_star_star_mapping_maintains_child_dot_dicts(self, dot_dict):
    obtained_via_star = DotDict(**dot_dict)
    b_dict = obtained_via_star["b"]
    assert b_dict.c == 2

答案 1 :(得分:1)

您面临的问题是yu试图建立在本机dict上-对于此类,__getitem__只是可以检索其值的几种方法之一。由于在python中实现了命令的实现方式,出于历史和性能方面的考虑,有很多方法可以完全绕过__getitem__,因此嵌套字典永远不会被“包装”在DotDict中。 (例如:.values()items()和星图甚至可能会绕过这些)

您真正想拥有的是collections.abc.MutableMapping的子类-它的构造方式确保可以通过__getitem__进行任何项目检索(您必须实现文档中指定的方法但是,包括__delitem____setitem____iter__的建议是将实际数据作为简单的判词保留在.data方法中创建的__init__属性中)。

可以看出,这也使您可以更好地控制数据,例如,使您可以直接在setitem上将数据包装在自定义类中,而jsut则不在乎属性检索-或相反,将所有映射存储为普通字典以节省内存并提高效率,并将其包装在检索中。

答案 2 :(得分:0)

哇,尝试用未注释的__iter__

运行以下代码
class DotDict(dict):
#    def __iter__(self):
#        return super().__iter__()

    def __getattr__(self, item):
        return self.__getitem__(item)

    def __getitem__(self, item):
        item = super().__getitem__(item)
        if isinstance(item, dict):
            return self.__class__(item)
        return item

d = DotDict({'a': {'b':'c'}})

print(type(dict(**d)['a']))

非常非常奇怪