Как сделать ключ 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'})
Вопросы:
- Каковы потенциальные возможности кода может сломаться, если я добавлю это изменение?
- Существует ли более простой (как, например, меньшее количество изменений, использование существующих решений, использование общих шаблонов) способ достижения этого?
Ответы
Ответ 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
.