首页 > 解决方案 > 为什么使用 setattrib 重载 __setitem__ 不能按预期工作?

问题描述

我正在构建一个简单的可观察列表,当列表改变时触发观察者函数。为了避免对每个方法一遍又一遍地重复几乎相同的代码,我用for循环和setattr. 我要装饰的方法存储在methods_to_decorate. 这是我的班级定义。

class ObservableList(list):
    def __init__(self, value):
        super().__init__(value)
        self.observers = []
        self._decorate_methods()

    def register_observer(self, callback):
        self.observers.append(callback)

    def _trigger_observers(self):
        for func in self.observers:
            func()

    def _make_observable(self, func):
        """Decorator that makes a method observable"""
        def wrapper(*args, **kwargs):
            value = func(*args, **kwargs)
            self._trigger_observers()
            return value
        return wrapper

    def _decorate_methods(self):
        """Make methods that change the list observable 
        using the decorator
        """
        methods_to_decorate = [
            '__setitem__', '__reversed__', '__delitem__', 
            'append', 'clear', 'copy', 'extend', 'insert', 'pop', 'remove', 
            'reverse', 'sort'
        ]
        for attr in methods_to_decorate:
            setattr(self, attr, self._make_observable(getattr(self, attr)))

请注意,这__setitem__是一种装饰方法。这表现如预期,除了切片设置操作:

def my_observer():
    print(' -> The list was changed')

lst = ObservableList([1, 2, 3])
lst.register_observer(my_observer)
print('append() should trigger')
lst.append(4)                   # -> The list was changed
print('pop() should trigger')
lst.pop()                       # -> The list was changed
print('Slice reading should not trigger')
lst[0]                          # (no output, expected)
print('Slice writing should trigger')
lst[0] = 0                      # (no output, not expected)

设置切片不会触发观察者。但是,如果我显式覆盖__setitem__(处理切片设置),则对象的行为与预期一样,切片设置也会触发观察者:

    # Only if __setitem__ is overloaded explicitly 
    # the observers are triggered on slice setting. 
    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self._trigger_observers()

为什么重载__setitem__显式工作,但为什么用它的修饰版本重载它setattr不起作用?

标签: pythoninheritance

解决方案


user2357112 的答案支持 Monica 为我指明了找到答案的方向。根据这篇文章setattr覆盖对象实例的方法,而显式重载改变了类本身的定义。按照设计,魔术方法(即以双下划线开头的方法)不能在实例级别上被覆盖。因此__setitem__,其他魔术方法必须在类定义中显式重载。


推荐阅读