我想要一个处理字典的函数,像字典或类似的东西一样,只需很少的工作,这样当调用时我可以运行其他代码。
我认为对字典进行子分类似乎更容易,所以我有。通过阅读help(dict)
我认为我已经涵盖了所有字典编辑函数,以便也调用回调。我有吗?还有没有其他像 pop 一样返回编辑值?
class BindedDict(dict):
"""Custom dictionary with callback when edited."""
def __init__(self, callback, *a, **kw):
self.callback = callback
super().__init__(*a, *kw)
return
def __delitem__(self, *a, **kw):
super().__delitme__(*a, **kw)
self.callback()
return
def __setitem__(self, *a, **kw):
super().__setitem__(*a, **kw)
self.callback()
return
def clear(self, *a, **kw):
super().clear(*a, **kw)
self.callback()
return
def pop(self, *a, **kw):
r = super().pop(*a, **kw)
self.callback()
return r
def popitem(self, *a, **kw):
super().popitem(*a, **kw)
self.callback()
return
def setdefault(self, *a, **kw):
super().setdefault(*a, **kw)
self.callback()
return
def update(self, *a, **kw):
super().update(*a, **kw)
self.callback()
return
更好的标题和类名也会很好。
我会使用组合而不是继承,并从collections.abc
实现MutableMapping
,以便免费实现一些方法。根据文档,您必须提供__getitem__
、__setitem__
、__delitem__
、__iter__
和__len__
的实现:
from collections.abc import MutableMapping
class BoundDict(MutableMapping):
"""Dict-like class that calls a callback on changes.
Note that the callback is invoked *after* the
underlying dictionary has been mutated.
"""
def __init__(self, callback, *args, **kwargs):
self._callback = callback
self._map = dict(*args, **kwargs)
def __getitem__(self, key):
return self._map[key]
def __setitem__(self, key, value):
self._map[key] = value
self._callback()
def __delitem__(self, key):
del self._map[key]
self._callback()
def __iter__(self):
return iter(self._map)
def __len__(self):
return len(self._map)
请注意,您不需要在方法末尾放置裸return
,并且我添加了一个文档字符串来解释该类的作用。
多亏了抽象基类,现在将为您实现以下附加方法:__contains__
、keys
、items
、values
、get
、__eq__
和__ne__
、pop
、popitem
、clear
、update
和setdefault
。因为它们都调用上面定义的五个基本方法,所以可以保证通过MutableMapping
接口进行的任何更改(尽管不是直接对_map
的更改)都将调用回调,因为它始终涉及调用__setitem__
或__delitem__
。
使用中:
>>> bd = BoundDict(lambda: print('changed'), [('foo', 'bar')], hello='world')
>>> bd
<BoundDict object at 0x7f8a4ea61048>
>>> list(bd.items())
[('foo', 'bar'), ('hello', 'world')]
>>> bd['foo']
'bar'
>>> bd['hello']
'world'
>>> bd['foo'] = 'baz'
changed
>>> del bd['hello']
changed
>>> bd['foo']
'baz'
>>> bd['hello']
Traceback (most recent call last):
File "python", line 1, in <module>
File "python", line 16, in __getitem__
KeyError: 'hello'
这样做的唯一缺点是,如果您有显式类型检查,则可能会遇到问题:
>>> isinstance(bd, dict)
False
但是,您通常也应该使用 ABC 进行类型检查(或者只是鸭子打字):
>>> isinstance(bd, MutableMapping)
True
>>> isinstance(dict(), MutableMapping)
True
我想当你问"这解释了为什么我在我的示例中只看到一个回调?">时,你想知道为什么我们的不同实现会发生以下情况:
>>> BindedDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
>>> BoundDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
changed
这是由于MutableMapping.clear
的实现;它循环字典中的键,为每个键调用popitem
,而__delitem__
又调用回调。相比之下,您的实现只调用回调一次,因为您直接实现clear
并从那里调用它。
请注意,ABC 方法不会阻止您执行此操作。从您的问题(您可能还不知道)中不清楚哪个是正确的行为,但您仍然可以进入并覆盖 ABC 提供的默认实现:
class BoundDict(MutableMapping):
"""..."""
...
def clear(self):
self._map.clear() # or e.g. self._map = {}
self._callback()
我建议使用 ABC 而不是子类化dict
的原因是,这为您提供了合理的默认实现,您可以在需要的地方覆盖这些实现,因此您只需要担心您的行为与默认值的不同之处。实现的方法越少也意味着出现简单拼写错误的风险较小,例如__delitme__
(如果未提供必需的@abstractmethod
则在尝试实例化类时会出现错误)和super().__init__(*a, *kw)
。