Python 字典子类,所有的编辑方法都包含回调吗?

Python 字典子类,所有的编辑方法都包含回调吗?,python,python-3.x,dictionary,Python,Python 3.x,Dictionary,我希望有一个处理字典的函数,像处理字典或类似的函数一样,只需很少的工作,这样在调用时就可以运行其他代码 我认为将dict子类化似乎更容易,所以我选择了。通过阅读help(dict)我想我已经涵盖了所有字典编辑功能,因此也调用了回调。是吗?还有其他像pop一样返回编辑值的吗 class BindedDict(dict): """Custom dictionary with callback when edited.""" def __init__(self, callback,

我希望有一个处理字典的函数,像处理字典或类似的函数一样,只需很少的工作,这样在调用时就可以运行其他代码

我认为将dict子类化似乎更容易,所以我选择了。通过阅读
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

还有一个更好的标题和类名也不错。

我会使用组合而不是继承,并从中实现
MutableMapping
,这样我可以免费获得一些实现的方法。根据文档,您必须提供
\uuuu getitem\uuuuuuu
\uuuu setitem\uuuuuu
\uuuu delitem\uuuuu
\uuuu iter\uuuuuuuu
的实现:

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)
注意,您不需要在方法的末尾放一个裸露的
返回
,我已经添加了一个docstring来解释类的功能

由于使用了抽象基类,现在将为您实现以下附加方法:
\uuuuu包含
获取
\uuuuu eq
、和
\uu ne
弹出
弹出项
清除
更新
,和
setdefault
。因为它们都调用了上面定义的五种基本方法,所以可以保证通过
可变映射
接口进行的任何更改(尽管不是直接更改
\u map
)都将调用回调,因为它总是涉及调用
\u setitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

使用中:

>>> 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'
但是,您通常也应该使用ABC进行类型检查(或仅限于):


我想当你问“这就解释了为什么我在我的示例中只看到一个回调?”时,你想知道为什么我们的不同实现会发生以下情况:

>>> BindedDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
>>> BoundDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
changed
这是由于实施了,;它在字典中的键上循环,为每个键调用
popitem
,然后调用
\uuu delitem\uuuu
,然后调用回调。相反,您的实现只调用回调一次,因为您直接实现
clear
,并从那里调用它

请注意,ABC方法不会阻止您这样做。从您的问题(您可能还不知道)中不清楚哪种行为是正确的,但您仍然可以进入并覆盖ABC提供的默认实现:

class BoundDict(MutableMapping):
    """..."""

    ...

    def clear(self):
        self._map.clear()  # or e.g. self._map = {}
        self._callback()

我建议使用ABC而不是子类化
dict
的原因是,这为您提供了合理的默认实现,您可以在需要的地方覆盖它,因此您只需要担心您的行为与默认行为的不同之处。实现的方法更少也意味着简单键入错误的风险更小,如
\uuu delitme\uuuu
(如果您不提供所需的
@abstractmethod
,则在尝试实例化类时会出现错误)和
super()。\uu init\uuuuu(*a,*kw)
\uu delitme\uu
?我建议查看
可变映射
。如果您的代码工作正常,并且希望得到改进建议,请参阅。谢谢@jornsharpe。我还没有测试过删除,这是一个打字错误。顺便说一句,ABC链接已经死了。@Peilonrayz成功粘贴了两次@jonrsharpe看了看文档,里面没有太多解释,我打算用谷歌搜索一下,但是如果你看到这个:我能用什么,怎么用?谢谢你,这是一个很好的解释。我不明白的是,为什么人们在不同的问题上说,当你把dict子类化时,你不知道方法?因为一切都是从我测试的工作,它没有缺点ABC毫米。另一件事,在上面的例子中,您说您只需要调用
\uuuuu setitem\uuuuuuu
\uuuuu delitem\uuuuuu
。其他方法是否使用内部方法访问它们,就像我的代码一样回调只被调用一次,就好像主方法和主方法没有被调用一样?我可能会在每个日志中添加一个日志,以查看……“当您将dict子类化时,为什么人们在不同的问题上说您不了解方法?”-我不理解这个问题,如果您有其他信息,可能会提供链接?“其他方法是否使用内部方法来访问它们”——正如我在回答中所说的,为您实现的额外方法都调用了五个必需的实现。例如,执行
bd=BoundDict(lambda:print('changed'),[('foo','bar')],hello='world')
然后
bd.clear()
您将看到
changed
两次?在您的示例中:
免费实现的方法
,您认为,它们的意思是我不必涵盖所有要更改的方法,因为它不使用super(),super()然后调用父类未更改的方法,这解释了为什么我在示例中只看到一个回调?@Max抱歉,我不知道您现在想问什么。不清楚你为什么认为自己缺少任何方法——ABC为你提供了这些方法。这种方法没有继承或
super
。不清楚你的例子是什么。@Max我想我至少明白了你最后一部分的意思,并编辑了我的答案。值得一看我链接到的源代码,在上下文中;
MutableMapping
Mapping
非常简单。
>>> BindedDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
>>> BoundDict(lambda: print('changed'), foo='bar', hello='world').clear()
changed
changed
class BoundDict(MutableMapping):
    """..."""

    ...

    def clear(self):
        self._map.clear()  # or e.g. self._map = {}
        self._callback()