Let's say I have a function in python that returns a dict with some objects.
class MyObj:
pass
def my_func():
o = MyObj()
return {'some string' : o, 'additional info': 'some other text'}
At some point I notice that it would make sense to rename the key 'some string'
as it is misleading and not well describing what is actually stored with this key. However, if I were to just change the key, people who use this bit of code, would be really annoyed because I didn't give them time via a deprecation period to adapt their code.
So the way I thought about implementing a deprecation warning is using a thin wrapper around 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)
This way I can create the dict with the old and the new key pointing towards the same object
class MyObj:
pass
def my_func():
o = MyObj()
return MyDict({'some string' : o, 'some object' : o, 'additional info': 'some other text'})
To be honest I do not think there is something particularly wrong or an anti-pattern with your solution, except for the fact that my_func
has to duplicate each deprecated key with its replacement (see below).
You could even generalize it a bit (in case you will decide to deprecate other keys):
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'})
Then
>> my_func()['some string'])
UserWarning: Please use the new key: `some object` instead of `some string`
All you have to do now in order to "deprecate" more keys is update 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
and my_func
). If I may quote Raymond Hettinger:
There must be a better way
This can be remedied with the following changes to __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)
Then my_func
can only use the new keys:
def my_func():
o = MyObj()
return MyDict({'some object': o, 'additional info': 'some other text'})
The behavior is the same, any code using the deprecated keys will get the warning (and, of course, accessing the new keys work):
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With