以Pythonic方式轮询字典 - 一旦键的值存在就使用它

4 投票
6 回答
1779 浏览
提问于 2025-04-18 09:10

我有一个能解决这个问题的方法,只是感觉不太符合Python的风格。我在用Python 2.7,所以不能用Python 3的解决方案。

我有一个字典,它会定期更新。最终,字典里会出现一个键,我们叫它“foo”,它会有一个对应的值。我想不断地检查这个字典,直到“foo”这个键出现,然后获取与这个键相关的值并使用它。

下面是一些现在能用的伪代码:

polled_dict = my_object.get_dict()
while('foo' not in polled_dict.keys()):
    polled_dict = my_object.get_dict()
fooValue = polled_dict['foo']

我想强调的是,这段代码现在是有效的。虽然感觉不太好,但确实能工作。我想到的一个可能的解决方案是:

fooValue = None
While fooValue is None:
    polled_dict = my_object.get_dict()
    fooValue = polled_dict.get('foo')

这个方法也能用,但感觉只好了一点。它的改进在于,当“foo”出现在字典中时,我们只调用一次polled_dict.get('foo'),而不是在while循环中和退出循环时各调用一次。但说实话,感觉并没有好太多,改进也很有限。

当我回顾其他实现的解决方案时,我发现它们只是上面两个例子的不同逻辑变体(比如“not”放在不同的位置等),但都没有感觉到很Pythonic。我觉得应该有一种更简单、更清晰的方法来做到这一点。有什么建议吗?如果没有,上面的两个方法哪个更好呢?

编辑 很多回答建议我重写或者改变代码所查询的字典。我同意这通常是个不错的解决方案,但引用我下面的一些评论:

“这段代码需要和更新字典的API分开存在。这个代码需要是通用的,能够访问许多不同类型对象的字典。添加一个触发器最终会要求完全重构所有这些对象(而且不会像这个函数需要的那样通用)。这显然是大大简化了,但最终,我需要检查这个字典中的值,直到它出现,而不是在对象中触发某个东西。我不太相信做这样广泛且可能有害的改变是一个Pythonic的解决方案(不过如果API从头重写,这肯定是解决方案,而对于那些不需要分开的/可以访问API的情况,这绝对是Pythonic的解决方案。)”

6 个回答

1

生成器的乐趣:

from itertools import repeat
gen_dict = (o.get_dict() for o in repeat(my_object))
foo_value = next(d['foo'] for d in gen_dict if 'foo' in d)
1

如果你的对象是在原地修改字典,那么你只需要获取一次字典。这样,你和你的对象就都指向同一个字典对象。如果你需要继续使用轮询的方法,那么这可能是最简单的解决方案:

polled_dict = my_object.get_dict()
while 'foo' not in polled_dict:
    pass # optionally sleep
fooValue = polled_dict['foo']

最好的方法是通过某种方式,比如管道、套接字或线程锁,发送一些事件。

1

也许使用 Try/Except 会被认为更符合 Python 的风格?

在 while 循环中加一个 sleep 语句,可以防止它消耗你所有的资源。

polled_dict = my_object.get_dict()
while True:
    time.sleep(0.1)
    try: 
        fooValue = polled_dict['foo']
        return (foovalue) # ...or break
    except KeyError:
        polled_dict = my_object.get_dict()
1

这样做是不是不行呢?(显然这不是线程安全的)唯一的问题是下面的方法无法捕捉到通过构造函数初始化字典的情况。也就是说,当字典创建时添加的键是无法被捕捉到的;比如 MyDict(watcher=MyWatcher(), a=1, b=2) 这个例子中的 a 和 b 这两个键就不会被记录为添加的。我不太确定该怎么实现这一点。

class Watcher(object):
    """Watches entries added to a MyDict (dictionary).  key_found() is called
    when an item is added whose key matches one of elements in keys.
    """
    def __init__(self, *keys):
        self.keys = keys

    def key_found(self, key, value):
        print key, value


class MyDict(dict):

    def __init__(self, *args, **kwargs):
        self.watcher = kwargs.pop('watcher')
        super(MyDict, self).__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        super(MyDict, self).__setitem__(key, value)
        if key in self.watcher.keys:
              self.watcher.key_found(key, value)


watcher = Watcher('k1', 'k2', 'k3')
d = MyDict(watcher=watcher)
d['a'] = 1
d['b'] = 2
d['k1'] = 'k1 value'
1

你可以尝试创建一个新的类,去继承dict这个字典类。

这个方法我没有测试过,但大概可以这样做:

class NoisyDict(dict):
    def __init__(self, *args, **kwargs):
        self.handlers = {}
        #Python 3 style
        super().__init__(*args, **kwargs)

    def add_handler(self, key, callback):
        self.handlers[key] = self.handlers.get(key, [])
        self.handlers[key].append(callback)

    def __getitem__(self, key):
        for handler in self.handlers.get(key, []):
            handler('get', key, super().__getitem__(key))
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        for handler in self.handlers.get(key, []):
            handler('set', key, value)
        return super().__setitem(value)

然后你可以这样使用:

d = NoisyDict()

d.add_handler('spam', print)

d['bar'] = 3
d['spam'] = 'spam spam spam'

撰写回答