自定义词典以保持其在**上的__getitem__(星星解包)



大家圣诞快乐,

我正在实现一个允许属性访问的自定义字典,例如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.Mappingdict。然而,这并没有解决问题。

我想这可能与我不完全清楚的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']))

非常非常奇怪的

相关内容

  • 没有找到相关文章

最新更新