Dictionay `__getitem__` переопределение нескольких подписок - PullRequest
0 голосов
/ 05 апреля 2019

Я пытаюсь реализовать настраиваемое поведение структуры данных dict.

Я хочу переопределить __getitem__ и применить какое-либо регулярное выражение к значению, прежде чем возвращать его пользователю.

Фрагмент:

class RegexMatchingDict(dict):
    def __init__(self, dct, regex, value_group, replace_with_group, **kwargs):
        super().__init__(**kwargs)
        self.replace_with_group = replace_with_group
        self.value_group = value_group
        self.regex_str = regex
        self.regex_matcher = re.compile(regex)
        self.update(dct)

    def __getitem__(self, key):
        value: Union[str, dict] = dict.__getitem__(self, key)
        if type(value) is str:
            match = self.regex_matcher.match(value)
            if match:
                return value.replace(match.group(self.replace_with_group), os.getenv(match.group(self.value_group)))
        return value # I BELIEVE ISSUE IS HERE

Это прекрасно работает для одного уровня индекса (т. Е. dict[key]).Однако при попытке его многоиндексировать (т. Е. dict[key1][key2]) происходит следующее: первый уровень индекса возвращает объект из моего класса.Но другие уровни вызывают значение по умолчанию __getitem__ в dict, которое не выполняет мое настроенное поведение.Как это исправить?


MCVE:

Вышеупомянутый код применяет регулярное выражение к значению и преобразует его в соответствующее значение переменной среды, если оно является строкой (то естьсамый низкий уровень в dict)

dictionary = {"KEY": "{ENVIRONMENT_VARIABLE}"}

custom_dict = RegexMatchingDict(dictionary, r"((.*({(.+)}).*))", 4 ,3)

Давайте установим переменную env с именем ENVIRONMENT_VARIABLE, установленную в 1.

import os

os.environ["ENVIRONMENT_VARIABLE"] = "1"

В этом случае код работает отлично

custom_dict["KEY"]

и возвращаемое значение будет:

{"KEY": 1}

Однако, если бы у нас была многоуровневая индексация

dictionary = {"KEY": {"INDEXT_KEY": "{ENVIRONMENT_VARIABLE}"}
custom_dict = RegexMatchingDict(dictionary, r"((.*({(.+)}).*))", 4 ,3)
custom_dict["KEY"]["INDEX_KEY"]

Это вернуло бы

{ENVIRONMENT_VARIABLE}

PS Существует много похожих вопросов, но все они (вероятно) касаются индексации верхнего уровня.

Ответы [ 2 ]

1 голос
/ 05 апреля 2019

Проблема, как вы говорите сами, заключается в последней строке вашего кода.

if type(value) is str:
    ...
else:
    return value # I BELIEVE ISSUE IS HERE

Это возвращает dict. Но вместо этого вы хотите вернуть RegexMatchingDict, который будет знать, как обрабатывать второй уровень индексации. Поэтому вместо того, чтобы возвращать value, если это dict, конвертируйте его в RegexMatchingDict и вместо этого возвращайте. Затем при вызове __getitem__() для выполнения второго уровня индексации вы получите свою версию, а не стандартную.

Примерно так:

return RegexMatchingDict(value, self.regex_str, self.value_group, self.replace_with_group)

Копирует остальные аргументы с первого уровня, поскольку трудно понять, чем может отличаться второй уровень.

0 голосов
/ 05 апреля 2019

В вашем примере ваш словарь второго уровня - normal dict и поэтому не использует ваш собственный метод __getitem__.

Приведенный ниже код показывает, что нужно сделать, чтобы иметь внутренний пользовательский код dict:

sec_level_dict = {"KEY": "{ENVIRONMENT_VARIABLE}"}

sec_level_custom_dict = RegexMatchingDict(sec_level_dict, r"((.*({(.+)}).*))", 4 ,3)

dictionary = {"KEY": sec_level_custom_dict}
custom_dict = RegexMatchingDict(dictionary, r"((.*({(.+)}).*))", 4 ,3)
print(custom_dict["KEY"]["KEY"])

Если вы хотите автоматизировать это и преобразовать все вложенные dict в пользовательские dict, вы можете настроить __setitem__, следуя этому шаблону:

class CustomDict(dict):

    def __init__(self, dct):
        super().__init__()
        for k, v in dct.items():
            self[k] = v

    def __getitem__(self, key):
        value = dict.__getitem__(self, key)
        print("Dictionary:", self, "key:", key, "value:", value)
        return value

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            dict.__setitem__(self, key, self.__class__(value))
        else:
            dict.__setitem__(self, key, value)

a = CustomDict({'k': {'k': "This is my nested value"}})

print(a['k']['k'])
...