自定义字典以在 ** 上维护它的 __getitem__(星-星-拆包)
Custom dictionary to maintain it's __getitem__ on ** (star-star-unpacking)
大家圣诞快乐,
我正在实现一个允许属性访问的自定义字典,例如dct.attribute
。字典可以嵌套,所以 dct.nested_dct.attribute
应该也是可以的。这已经很好用了,除了 star-star-unpacking。我认为我能够使用代码比文字更好地表达我想做的事情。所以这是我正在写的 class。测试应该非常清楚地解释它的作用:
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 定义更改为
class DotDict(Mapping, item):
或
class DotDict(item, Mapping):
我的测试不会变绿。
在 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']))
非常非常奇怪
您面临的问题是您正在尝试构建原生 dict
- 对于此 class,__getitem__
只是其值的几种方式之一检索。由于字典在 Python 中的实现方式,出于历史和性能原因,有很多方法可以完全绕过 __getitem__
,因此,嵌套字典永远不会 "wrapped"在 DotDict 中。 (例如:.values()
、items()
,starmap甚至可能会绕过这些)
你真正想要的是 subclass collections.abc.MutableMapping - 它的构造方式确保任何项目检索都将通过 __getitem__
,(你必须实施文档中指示的方法,包括 __delitem__
、__setitem__
和 __iter__
- 建议将实际数据作为普通字典保存在 .data
中创建的属性中__init__
方法)。
意识到这也可以让您更好地控制数据,例如,使您能够将数据包装在自定义的 class 中,直接放在 setitem 上,而不必关心属性检索 -或者,反过来,将任何映射存储为普通字典以节省内存和提高效率,并在检索时将其包装起来。
大家圣诞快乐,
我正在实现一个允许属性访问的自定义字典,例如dct.attribute
。字典可以嵌套,所以 dct.nested_dct.attribute
应该也是可以的。这已经很好用了,除了 star-star-unpacking。我认为我能够使用代码比文字更好地表达我想做的事情。所以这是我正在写的 class。测试应该非常清楚地解释它的作用:
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 定义更改为
class DotDict(Mapping, item):
或
class DotDict(item, Mapping):
我的测试不会变绿。
在 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']))
非常非常奇怪
您面临的问题是您正在尝试构建原生 dict
- 对于此 class,__getitem__
只是其值的几种方式之一检索。由于字典在 Python 中的实现方式,出于历史和性能原因,有很多方法可以完全绕过 __getitem__
,因此,嵌套字典永远不会 "wrapped"在 DotDict 中。 (例如:.values()
、items()
,starmap甚至可能会绕过这些)
你真正想要的是 subclass collections.abc.MutableMapping - 它的构造方式确保任何项目检索都将通过 __getitem__
,(你必须实施文档中指示的方法,包括 __delitem__
、__setitem__
和 __iter__
- 建议将实际数据作为普通字典保存在 .data
中创建的属性中__init__
方法)。
意识到这也可以让您更好地控制数据,例如,使您能够将数据包装在自定义的 class 中,直接放在 setitem 上,而不必关心属性检索 -或者,反过来,将任何映射存储为普通字典以节省内存和提高效率,并在检索时将其包装起来。