Python 当我们编辑(附加、删除…)一个列表时会发生什么?我们是否可以在每次编辑列表时执行操作

Python 当我们编辑(附加、删除…)一个列表时会发生什么?我们是否可以在每次编辑列表时执行操作,python,list,Python,List,我想知道是否有一种方法可以创建一个列表,每当我使用方法append(或其他类似方法)时,该列表将执行一些操作。 我知道我可以创建一个类,该类继承自列表并覆盖附加、删除以及所有其他更改列表内容的方法,但我想知道是否还有其他方法。 相比之下,如果每次编辑对象的属性时都要打印edited,则不会在该对象的类的所有方法中执行print(“edited”)。相反,我将只覆盖\uuuuu setattribute\uuuu。 我试图创建自己的类型,该类型继承列表并覆盖\uuuuu setattribute\

我想知道是否有一种方法可以创建一个
列表
,每当我使用方法
append
(或其他类似方法)时,该列表将执行一些操作。 我知道我可以创建一个
,该类继承自
列表
并覆盖
附加
删除
以及所有其他更改
列表
内容的方法,但我想知道是否还有其他方法。 相比之下,如果每次编辑对象的属性时都要打印
edited
,则不会在该对象的
类的所有方法中执行
print(“edited”)
。相反,我将只覆盖
\uuuuu setattribute\uuuu
。 我试图创建自己的类型,该类型继承
列表
并覆盖
\uuuuu setattribute\uuuuu
,但不起作用。当我使用myList.append时,不会调用
\uuuu setattribute\uuuu
。我想知道当我使用myList.append时实际发生了什么?是否有一些神奇的方法,我可以覆盖

我知道已经有人问过这个问题:。给出的答案只是,没有答案。。。我希望这是个错误

我不知道我的请求是否有答案,所以我也会向你解释为什么我会遇到这个问题。也许我可以换个方向去做我想做的事。我有一个
,有几个属性。编辑属性时,我希望执行一些操作。就像我之前解释的那样,要做到这一点,我需要覆盖
\uuuuu setattribute\uuuu
。这对大多数属性都适用。问题是
列表
。如果该属性是这样使用的:
myClass.myListAttr.append(something)
,则在该属性的值发生更改时,不会调用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu


问题与
词典的问题相同。像
pop
这样的方法不会在每次调用变异方法时调用
\uuuuu setattribute\uuuuu
如果我理解正确,您可能需要像
Notify\u list
这样的东西,它会在每次调用变异方法时调用某个方法(在我的实现中调用构造函数的参数),因此您可以这样做:

class Test:
    def __init__(self):

        self.list = Notify_list(self.list_changed)
    def list_changed(self,method):
        print("self.list.{} was called!".format(method))

>>> x = Test()
>>> x.list.append(5)
self.list.append was called!
>>> x.list.extend([1,2,3,4])
self.list.extend was called!
>>> x.list[1] = 6
self.list.__setitem__ was called!
>>> x.list
[5, 6, 2, 3, 4]
最简单的实现是创建一个子类并覆盖每个变异方法:

class Notifying_list(list):
    __slots__ = ("notify",)
    def __init__(self,notifying_method, *args,**kw):
        self.notify = notifying_method
        list.__init__(self,*args,**kw)

    def append(self,*args,**kw):
        self.notify("append")
        return list.append(self,*args,**kw)
    #etc.
这显然不是很实用,编写整个定义将非常繁琐且重复性很强,因此我们可以为任何给定类动态创建新的子类,其函数如下所示:

import functools
import types

def notify_wrapper(name,method):
    """wraps a method to call self.notify(name) when called
used by notifying_type"""
    @functools.wraps(method)
    def wrapper(*args,**kw):
        self = args[0]
        # use object.__getattribute__ instead of self.notify in
        # case __getattribute__ is one of the notifying methods
        # in which case self.notify will raise a RecursionError
        notify = object.__getattribute__(self, "_Notify__notify")
        # I'd think knowing which method was called would be useful
        # you may want to change the arguments to the notify method
        notify(name)
        return method(*args,**kw)
    return wrapper

def notifying_type(cls, notifying_methods="all"):
    """creates a subclass of cls that adds an extra function call when calling certain methods

The constructor of the subclass will take a callable as the first argument
and arguments for the original class constructor after that.
The callable will be called every time any of the methods specified in notifying_methods
is called on the object, it is passed the name of the method as the only argument

if notifying_methods is left to the special value 'all' then this uses the function
get_all_possible_method_names to create wrappers for nearly all methods."""
    if notifying_methods == "all":
        notifying_methods = get_all_possible_method_names(cls)
    def init_for_new_cls(self,notify_method,*args,**kw):
        self._Notify__notify = notify_method

    namespace = {"__init__":init_for_new_cls,
                 "__slots__":("_Notify__notify",)}
    for name in notifying_methods:
        method = getattr(cls,name) #if this raises an error then you are trying to wrap a method that doesn't exist
        namespace[name] = notify_wrapper(name, method)

    # I figured using the type() constructor was easier then using a meta class.
    return type("Notify_"+cls.__name__, (cls,), namespace)


unbound_method_or_descriptor = ( types.FunctionType,
                                 type(list.append), #method_descriptor,   not in types
                                 type(list.__add__),#method_wrapper, also not in types
                                )
def get_all_possible_method_names(cls):
    """generates the names of nearly all methods the given class defines
three methods are blacklisted: __init__, __new__, and __getattribute__ for these reasons:
 __init__ conflicts with the one defined in notifying_type
 __new__ will not be called with a initialized instance, so there will not be a notify method to use
 __getattribute__ is fine to override, just really annoying in most cases.

Note that this function may not work correctly in all cases
it was only tested with very simple classes and the builtin list."""
    blacklist = ("__init__","__new__","__getattribute__")
    for name,attr in vars(cls).items():
        if (name not in blacklist and
            isinstance(attr, unbound_method_or_descriptor)):
            yield name
一旦我们可以使用
通知类型
创建
通知列表
通知目录
将非常简单:

import collections
mutating_list_methods = set(dir(collections.MutableSequence)) - set(dir(collections.Sequence))
Notify_list = notifying_type(list, mutating_list_methods)

mutating_dict_methods = set(dir(collections.MutableMapping)) - set(dir(collections.Mapping))
Notify_dict = notifying_type(dict, mutating_dict_methods)

我还没有对它进行过广泛的测试,它很可能包含bug/未处理的死角案例,但我知道它在
列表
中工作正常

list.append
不使用任何神奇的方法。。。这是
append
方法
\uuuuSetAttribute\uuuuu
甚至不是python认可的神奇方法<代码>\uuuu setattr\uuuuuu
是其中之一,但这是用于设置与修改列表无关的属性的。因此确实没有办法做到这一点……我从来没有说过;)我想当对(包装的)属性调用方法时,可以使用描述符通知原始对象,我现在正在构造一个答案。好的,谢谢!不幸的是,我现在得走了。我明天看。提前谢谢!