Скажем, у меня есть функция на 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'})
Этот вопрос лучше подошел бы к codereview, чем к SO.
@dimid Я думаю, у вас сложилось такое впечатление, потому что в ответах обсуждался только мой предложенный ответ, тогда как я ожидал большего количества ответов, которые предлагали бы альтернативы или предлагали существующие решения, поэтому я разместил его здесь, поэтому люди, имеющие вопрос в title мог бы прийти сюда и найти рейтинг решений этой проблемы. Но я согласен с тем, что текущий статус этого вопроса сделает его кандидатом на проверку кода.






Честно говоря, я не думаю, что в вашем решении есть что-то особенно неправильное или анти-шаблон, за исключением того факта, что 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 has to duplicate each deprecated key with its replacement. This violates the DRY principle and will clutter the code if and when you do need to deprecate more keys (and you will have to remember to update both MyDict.old_keys_to_new_keysиmy_func). If I may quote Raymond Hettinger:
There must be a better way
Это можно исправить с помощью следующих изменений в __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>
Было бы неплохо также переопределить метод __setitem__(), чтобы он отображал предупреждение, когда это необходимо.
Взяв за основу это обсуждение, я создал небольшой пакет для заинтересованных: github.com/NOhs/dkey
Как говорили другие, ваш нынешний подход уже кажется неплохим. Единственное возможное предостережение, которое я вижу, заключается в том, что класс 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.
На самом деле, мне ваш подход кажется разумным. Режимы отказа включают другие способы доступа к элементу, например dict.update, dict.get ...