自定义字典以维护它的 __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.Mapping
和dict
继承。然而,这并没有解决问题。
我在想这可能与我并不完全清楚的 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 不关心属性检索 - 或者,反过来,将任何映射存储为普通字典以节省内存和提高效率,并在检索时将其包装起来。
【讨论】:
太好了,感谢您的回答。你知道Mapping
和MutableMapping
有什么区别吗?文档对此有点短。【参考方案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)的主要内容,如果未能解决你的问题,请参考以下文章