Schrodingers字典:获取间接访问的动态密钥值

时间:2017-06-18 21:49:17

标签: python python-3.x dictionary

我想创建一个包含一些动态键的专用字典。对dict进行子类化并覆盖__getitems__会使我获得大部分内容,但是您只能从上次直接访问中获取密钥的值。

$ cat schrodingers_dict.py
cats = []

class SchrodingersDict(dict):
    def __getitem__(self, key):
        if key == 'dead' or key == 'alive':
            self[key] = cats.count(key)
        return super().__getitem__(key)

box = SchrodingersDict([('dead', 0), ('alive', 0)])

$ python3 -i schrodinger_dict.py 
>>> box
{'dead': 0, 'alive': 0}
>>> cats.append('alive')
>>> cats.append('dead')
>>> cats.append('dead')
>>> box
{'dead': 0, 'alive': 0}
>>> box['dead']
2
>>> box['alive']
1
>>> box
{'dead': 2, 'alive': 1}

是否可以让间接访问返回动态值,例如repr(box)json.dumps(box)

2 个答案:

答案 0 :(得分:1)

你应该扩展MutableMapping并实现相关的抽象方法(委托给非计算密钥的具体底层字典),因为由于性能优化dict不能保证调用{{1例如迭代k / v。

__getitem__

您可能还希望将计算出的密钥设置为完全虚拟,而不是将它们存储在底层字典中,无论如何都要在每次访问时重新计算它们,还可以节省一些指令和空间。

答案 1 :(得分:0)

目标是以某种方式在访问'dead'的值时自动计算'alive'dict的值。

但是,实现对values内某种__getattribute__属性的访问权限的检查是不可能的,因为dict是一个(基于C的)python内置并且没有这样的属性。此外,由于速度优化,特殊方法查找会绕过__getattribute__

不幸的是,dict访问值的方法没有公开的常用方法(valuesitems只返回视图,一切都在内部处理),所以有必要所有这些方法都要事先明确更新。

一种解决方案是以

的方式覆盖每个这样的函数
def accessing_method(self):
    self._update_dead_and_alive()
    return super().accessing_method()

另一个是以编程方式

from functools import wraps

cats = []    

def add_state_update(cls):
    # all the methods accessing values of dict
    accmethods_names = [
        '__repr__', '__getitem__', 'get', 'items', 'pop', 'popitem',
        'setdefault', 'values'
    ]

    def state_update(method):
        def update_dead_and_alive(self):
            self['dead'] = cats.count('dead')
            self['alive'] = cats.count('alive')

        @wraps(method)
        def wrapper(*args, **kwargs):
            update_dead_and_alive(*args)
            return method(*args, **kwargs)
        return wrapper

    # put update_dead_and_alive in front of the execution of every
    # accmethod
    for n in accmethods_names:
        method = getattr(cls, n)
        setattr(cls, n, state_update(method))

    return cls

@add_state_update
class SchrodingersDict(dict):
    pass

box = SchrodingersDict([('dead', 0), ('alive', 0)])

根据上述功能进行测试时,会得到以下结果

>>> cats.append('alive')
>>> cats.append('dead')
>>> cats.append('dead')
>>>
>>> import pickle
>>> pickle.loads(pickle.dumps(box))
{'dead': 2, 'alive': 1}
>>>
>>> cats.append('alive')
>>> import json
>>> json.loads(json.dumps(box))
{'dead': 2, 'alive': 2}
>>>
>>> cats.append('alive')
>>> eval(repr(box)) == box
True

第一个版本会产生相似数量的代码,可能更明确。