Ответ 1
Дерево атрибутов
Проблема с вашей первой спецификацией заключается в том, что Python не может сказать в __getitem__
, если в my_obj.a.b.c.d
вы продолжите дальше по несуществующему дереву, и в этом случае ему нужно вернуть объект с помощью __getitem__
, чтобы вы не выбрали AttributeError
для вас или хотите получить значение, и в этом случае ему нужно вернуть None
.
Я бы сказал, что в каждом случае, у вас есть выше, вы должны ожидать, чтобы он выбрал KeyError
вместо возврата None
. Причина в том, что вы не можете определить, означает ли None
"нет ключа" или "кто-то действительно хранит None
в этом месте". Для этого вам нужно всего лишь взять dotdictify
, удалить marker
и заменить __getitem__
на:
def __getitem__(self, key):
return self[key]
Потому что вы действительно хотите dict
с __getattr__
и __setattr__
.
Возможно, существует способ полностью удалить __getitem__
и сказать что-то вроде __getattr__ = dict.__getitem__
, но я думаю, что это может быть чрезмерная оптимизация и будет проблемой, если вы позже решите, что хотите __getitem__
создать дерево как это происходит, например, dotdictify
, и в этом случае вы изменили бы его на:
def __getitem__(self, key):
if key not in self:
dict.__setitem__(self, key, dotdictify())
return dict.__getitem__(self, key)
Мне не нравится бизнес marker
в оригинале dotdictify
.
Поддержка путей
Вторая спецификация (переопределить get()
и set()
) состоит в том, что нормальный dict
имеет get()
, который работает иначе, чем то, что вы описываете, и даже не имеет set
(хотя он имеет setdefault()
, которая является обратной операцией get()
). Люди ожидают, что get
примет два параметра, второй - по умолчанию, если ключ не найден.
Если вы хотите расширить __getitem__
и __setitem__
для обработки меток с метками, вам необходимо изменить doctictify
на:
class dotdictify(dict):
def __init__(self, value=None):
if value is None:
pass
elif isinstance(value, dict):
for key in value:
self.__setitem__(key, value[key])
else:
raise TypeError, 'expected dict'
def __setitem__(self, key, value):
if '.' in key:
myKey, restOfKey = key.split('.', 1)
target = self.setdefault(myKey, dotdictify())
if not isinstance(target, dotdictify):
raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
target[restOfKey] = value
else:
if isinstance(value, dict) and not isinstance(value, dotdictify):
value = dotdictify(value)
dict.__setitem__(self, key, value)
def __getitem__(self, key):
if '.' not in key:
return dict.__getitem__(self, key)
myKey, restOfKey = key.split('.', 1)
target = dict.__getitem__(self, myKey)
if not isinstance(target, dotdictify):
raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
return target[restOfKey]
def __contains__(self, key):
if '.' not in key:
return dict.__contains__(self, key)
myKey, restOfKey = key.split('.', 1)
target = dict.__getitem__(self, myKey)
if not isinstance(target, dotdictify):
return False
return restOfKey in target
def setdefault(self, key, default):
if key not in self:
self[key] = default
return self[key]
__setattr__ = __setitem__
__getattr__ = __getitem__
Тестовый код:
>>> life = dotdictify({'bigBang': {'stars': {'planets': {}}}})
>>> life.bigBang.stars.planets
{}
>>> life.bigBang.stars.planets.earth = { 'singleCellLife' : {} }
>>> life.bigBang.stars.planets
{'earth': {'singleCellLife': {}}}
>>> life['bigBang.stars.planets.mars.landers.vikings'] = 2
>>> life.bigBang.stars.planets.mars.landers.vikings
2
>>> 'landers.vikings' in life.bigBang.stars.planets.mars
True
>>> life.get('bigBang.stars.planets.mars.landers.spirit', True)
True
>>> life.setdefault('bigBang.stars.planets.mars.landers.opportunity', True)
True
>>> 'landers.opportunity' in life.bigBang.stars.planets.mars
True
>>> life.bigBang.stars.planets.mars
{'landers': {'opportunity': True, 'vikings': 2}}