Как сделать ключ DICT устаревшим? - программирование
Подтвердить что ты не робот

Как сделать ключ DICT устаревшим?

вопрос

Допустим, у меня есть функция в Python, которая возвращает dict с некоторыми объектами.

class MyObj:
    pass

def my_func():
    o = MyObj()
    return {'some string' : o, 'additional info': 'some other text'}

В какой-то момент я заметил, что имеет смысл переименовать ключ в 'some string' как он вводит в заблуждение и плохо описывает то, что на самом деле хранится с этим ключом. Однако, если бы я просто изменил ключ, люди, которые используют этот фрагмент кода, были бы действительно раздражены, потому что я не дал им время через период устаревания, чтобы адаптировать их код.

Текущая попытка

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

from warnings import warn

class MyDict(dict):
    def __getitem__(self, key):
        if key == 'some string':
             warn('Please use the new key: 'some object' instead of 'some string'')
        return super().__getitem__(key)

Таким образом, я могу создать dict со старым и новым ключом, указывающим на один и тот же объект

class MyObj:
    pass

def my_func():
    o = MyObj()
    return MyDict({'some string' : o, 'some object' : o, 'additional info': 'some other text'})

Вопросы:

  • Каковы потенциальные возможности кода может сломаться, если я добавлю это изменение?
  • Существует ли более простой (как, например, меньшее количество изменений, использование существующих решений, использование общих шаблонов) способ достижения этого?
4b9b3361

Ответ 1

Если честно, я не думаю, что в вашем решении есть что-то особенно неправильное или анти-шаблон, за исключением того факта, что my_func должен дублировать каждый устаревший ключ с его заменой (см. Ниже).

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

class MyDict(dict):
    old_keys_to_new_keys = {'some string': 'some object'}
    def __getitem__(self, key):
        if key in self.old_keys_to_new_keys:
            msg = 'Please use the new key: '{}' instead of '{}''.format(self.old_keys_to_new_keys[key], key)
            warn(msg)
        return super().__getitem__(key)

class MyObj:
    pass

def my_func():
    o = MyObj()
    return MyDict({'some string' : o, 'some object': o, 'additional info': 'some other text'})

затем

>> my_func()['some string'])
UserWarning: Please use the new key: 'some object' instead of 'some string'

Все, что вам нужно сделать сейчас для того, чтобы "осудить" больше ключей, это обновить old_keys_to_new_keys.

Тем не мение,

обратите внимание, как my_func должен дублировать каждый устаревший ключ с его заменой. Это нарушает принцип СУХОЙ и будет загромождать код, если и когда вам понадобится отказаться от дополнительных ключей (и вам придется помнить об обновлении MyDict.old_keys_to_new_keys и my_func). Если я могу процитировать Раймонда Хеттингера:

Должен быть лучший способ

Это можно исправить с помощью следующих изменений в __getitem__:

def __getitem__(self, old_key):
    if old_key in self.old_keys_to_new_keys:
        new_key = self.old_keys_to_new_keys[old_key]
        msg = 'Please use the new key: '{}' instead of '{}''.format(new_key, old_key)
        warn(msg)
        self[old_key] = self[new_key]  # be warned - this will cause infinite recursion if
                                       # old_key == new_key but that should not really happen
                                       # (unless you mess up old_keys_to_new_keys)
    return super().__getitem__(old_key)

Тогда my_func может использовать только новые ключи:

def my_func():
    o = MyObj()
    return MyDict({'some object': o, 'additional info': 'some other text'})

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

print(my_func()['some string'])
# UserWarning: Please use the new key: 'some object' instead of 'some string'
# <__main__.MyObj object at 0x000002FBFF4D73C8>
print(my_func()['some object'])
# <__main__.MyObj object at 0x000002C36FCA2F28>

Ответ 2

Как уже говорили другие, ваш нынешний подход кажется довольно хорошим. Единственное потенциальное предостережение, которое я вижу, заключается в том, что класс MyDict централизует все знания об устаревших значениях. В зависимости от вашего варианта использования вы можете определить, что является устаревшим, а что нет, в том месте, где оно определено. Например, вы можете сделать что-то вроде этого:

from warnings import warn

class MyDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._deprecated_keys = {}
    def __getitem__(self, key):
        if key in self._deprecated_keys:
            new_key = self._deprecated_keys[key]
            if new_key:
                warn(f'Please use the new key: '{new_key}' instead of '{key}'.')
            else:
                warn(f'Deprecated key: '{key}'.')
        return super().__getitem__(key)
    # Option A
    def put_deprecated(self, key, value, new_key=None):
        self._deprecated_keys[key] = new_key
        self[key] = value
    # Option B
    def put(self, key, value, deprecated_keys=None):
        self[key] = value
        for deprecated_key in (deprecated_keys or []):
            self[deprecated_key] = value
            self._deprecated_keys[deprecated_key] = key


my_dict = MyDict()
# Option A
my_dict['new_key'] = 'value'
my_dict.put_deprecated('old_key', 'value', new_key='new_key')
# Option B
my_dict.put('new_key', 'value', deprecated_keys=['old_key'])

my_dict['old_key']
# UserWarning: Please use the new key: 'new_key' instead of 'old_key'.

Вариант A требует повторения, но допускает использование устаревших ключей без замены, тогда как вариант B является более кратким. Преимущество здесь состоит в том, что определение новых ключей и их устаревание выполняется в точке, где назначены ключ и значение, вместо того, чтобы требовать изменения MyDict.