如何不建议使用dict键?

时间:2019-01-08 15:50:07

标签: python dictionary

问题

假设我在python中有一个函数,该函数返回带有一些对象的字典。

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)

这样,我可以使用旧键和新键指向同一对象来创建字典

class MyObj:
    pass

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

问题:

  • 如果添加此更改,代码可能会破坏代码的哪些潜在方式?
  • 是否有更简单的方法(例如,较少的更改,使用现有解决方案,使用常见模式)来实现这一目标?

2 个答案:

答案 0 :(得分:11)

说实话,我认为您的解决方案没有什么特别的错误或反模式,除了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必须复制每个已弃用的键及其替换。这违反了DRY原则,并且在确实需要弃用更多键的情况下会使代码混乱(并且您将必须记住同时更新MyDict.old_keys_to_new_keys my_func)。如果可以引用Raymond Hettinger:

  

必须有更好的方法

可以通过对__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>

答案 1 :(得分: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