大家圣诞快乐,
我正在实现一个允许属性访问的自定义字典,例如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)
正如评论中所说,最后一次测试失败了。有人知道怎么修吗?
根据这个问题的答案:为自己的类进行星形拆包,我认为我需要继承collections.abc.Mapping
和dict
。然而,这并没有解决问题。
我想这可能与我不完全清楚的MRO有关。但无论我是否将类定义更改为
class DotDict(Mapping, item):
或
class DotDict(item, Mapping):
我的测试不会变成绿色。
您面临的问题是,您正试图在本机dict
的基础上构建,而对于这个类来说,__getitem__
只是检索其值的几种方法之一。由于dict在Python中的实现方式,出于历史和性能的原因,有很多方法可以完全绕过__getitem__
,因此,嵌套字典永远不会被"包装"在DotDict中。(例如:.values()
、items()
和星图甚至可能绕过这些)
你真正想要的是对collections.abc.MutableMapping进行子类化——它的构建方式确保任何项目检索都将通过__getitem__
,(不过,您必须实现文档中指出的方法,包括__delitem__
、__setitem__
和__iter__
——建议在__init__
方法中创建的.data
属性中保留实际数据作为普通字典)。
意识到这也让你更好地控制数据,例如,使你能够直接在setitem上包装自定义类中的数据,而jsut不关心属性检索——或者,反过来,为了节省内存和效率,将任何映射存储为普通字典,并在检索时包装它。
在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
哇,试着用未注释的__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']))
非常非常奇怪的