自定义字典以维护它的 __getitem__ on ** (star-star-unpacking)

Posted

技术标签:

【中文标题】自定义字典以维护它的 __getitem__ on ** (star-star-unpacking)【英文标题】:Custom dictionary to maintain it's __getitem__ on ** (star-star-unpacking) 【发布时间】:2018-12-24 14:17:34 【问题描述】:

祝大家圣诞快乐

我正在实现一个允许属性访问的自定义字典,例如dct.attribute。字典可以嵌套,所以dct.nested_dct.attribute 也应该是可能的。这已经很好地工作了,除了明星明星拆包。我认为我能够用代码而不是文字更好地表达我想要做的事情。这就是我正在写的课程。测试应该非常清楚地解释它的作用:

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):

我的测试不会变成绿色。

【问题讨论】:

【参考方案1】:

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

您真正想要的是子类collections.abc.MutableMapping - 它的构造方式确保任何项目检索都将通过__getitem__,(但您必须实现文档中指示的方法,包括@ 987654328@、__setitem____iter__ - 建议将实际数据作为普通字典保存在 .data 属性中,该属性是在 __init__ 方法中创建的。

认为这还可以让您更好地控制数据,例如,使您能够将数据直接包装在 setitem 上的自定义类中,并且 jsut 不关心属性检索 - 或者,反过来,将任何映射存储为普通字典以节省内存和提高效率,并在检索时将其包装起来。

【讨论】:

太好了,感谢您的回答。你知道MappingMutableMapping 有什么区别吗?文档对此有点短。【参考方案2】:

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

【讨论】:

很遗憾没有。所以当然它的测试是绿色的。但我希望能够进行解包返回DotDict 以将其作为函数参数动态传递(如**kwargs)。因此,再次将其包装在 DotDict 中是行不通的。我编辑了问题和测试以使其更清晰。【参考方案3】:

哇,尝试使用未注释的__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']))

非常非常奇怪

【讨论】:

对我没有帮助。适合你吗? 实现了__iter__ 我得到<class '__main__.DotDict'>,没有<class 'dict'> 你在用什么蟒蛇?我正在使用 3.6 显然__iter__ 根本没有被调用 我使用 Python 3.6.7

以上是关于自定义字典以维护它的 __getitem__ on ** (star-star-unpacking)的主要内容,如果未能解决你的问题,请参考以下文章

自定义字典类

理解 __getitem__ 方法

就地自定义对象使用 __getitem__ python 3.5 与 python 3.6 解包不同的行为

Tornado之自定义session

python迭代器和生成器

Python迭代和解析:自定义迭代器