скажем, у меня есть две (простые игрушки) вложенные структуры данных, подобные этой:
d = dict(zip(list('abc'), list(range(3))))
nested_dict = {k:d.copy() for k in d}
nested_listof_dict = {k:[d.copy() for _ in range(3)] for k in d}
Теперь я хочу, чтобы это вело себя как «обычный» классоподобный объект (имеется в виду точка-индексируемость)
class dictobj(dict):
def __init__(self, data: dict, name):
data['_name'] = name
super().__init__(data)
for name, item in data.items():
if isinstance(item, (list, tuple)):
setattr(self, name, [dictobj(x, name) if isinstance(x, dict) else x for x in item])
else:
setattr(self, name, dictobj(item, name) if isinstance(item, dict) else item)
def __repr__(self):
return f"{self['_name']}"
data_dictobj = dictobj(data, 'test') # size 1185 bytes
, который хорошо работает как для вложенного dict, так и для nested_listof_dict
assert nested_listof_dict.a[0].b == nested_listof_dict['a'][0]['b']
, но поскольку атрибуты и словари являются изменяемыми, это может произойти
nested_listof_dict['a'][0]['b'] = 2
assert nested_listof_dict.a[0].b != nested_listof_dict['a'][0]['b'] # unwanted behavior
поэтому было бы неплохо реализовать атрибуты как свойства. Я подумал, что было бы неплохо избегать использования лямбда-функций из-за замыкания замыкания. Сначала рассмотрев реализацию геттера, я сосредоточился на nested_dict, поскольку это более простая структура.
class dictobj(dict):
def __init__(self, data: dict, name):
def make_property(self, name, item):
def getter(self):
return dictobj(item, name) if isinstance(item, dict) else item
setattr(self.__class__, name, property(getter))
# def setter(self, value):
# if not isinstance(value, type(item)):
# raise ValueError(f'cannot change the data structure, expected '+
# f'{type(item).__name__} got {type(value).__name__}')
# self[name] = value
# setattr(self.__class__, name, property(getter, setter))
data['_name'] = name
super().__init__(data)
for name, item in data.items():
if isinstance(item, (list, tuple)):
setattr(self, name, [dictobj(x, name) if isinstance(x, dict) else x for x in item])
else:
make_property(self, name, item)
def __repr__(self):
return f"{self['_name']}"
, а затем проверьте, нельзя ли установить атрибут больше
d = dictobj(d, 'test')
# d.a = 1 # fails as should: "AttributeError: can't set attribute"
# d.a.a = 1 # fails as should: "AttributeError: can't set attribute"
Но почему-то я все еще не в порядке, наблюдается следующее поведение:
print(d.a) # returns object "a" - as desired
print(d.a) # returns 0 - second call returns the nested value
Я не знаю, как избежать этого поведения. Кроме того, я также хотел бы создать сеттер, который обеспечивает поддержку структуры данных. Не удивляя комментатора, которого я написал выше, неудивительно, что это также приводит к непреднамеренному поведению
d.a = {1} # ValueError: cannot change the data structure, expected dict got set - as desired
d.a.a = 2 # AttributeError: 'int' object has no attribute 'a'
d.a = 2
assert d.a == 0 and d['a'] == 2 # again unintended
Я хотел бы понять, что я делаю неправильно, и заставить это работать. Также следует отметить, что я еще даже не рассматривал создание свойств для nested_listof_dict, которые также были бы необходимы.