当我们编辑(追加,删除...)列表时会发生什么,每次编辑列表时我们都可以执行操作吗? [英] What happens when we edit(append, remove...) a list and can we execute actions each time a list is edited

查看:67
本文介绍了当我们编辑(追加,删除...)列表时会发生什么,每次编辑列表时我们都可以执行操作吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有一种创建list的方法,该方法将在每次使用方法append(或其他类似方法)时执行一些操作. 我知道我可以创建一个从list继承的class并覆盖appendremove和所有其他更改list内容的方法,但是我想知道是否还有其他方法. 相比之下,如果我每次编辑对象的属性时都想print编辑",则在该对象的class的所有方法中我都不会执行print("edited").相反,我只会覆盖__setattribute__. 我试图创建自己的类型,该类型继承了list并覆盖了__setattribute__,但这是行不通的.当我使用myList.append时,不会调用__setattribute__.我想知道使用myList.append时发生了什么情况吗?有一些我可以覆盖的魔术方法吗?

I would like to know if there is a way to create a list that will execute some actions each time I use the method append(or an other similar method). I know that I could create a class that inherits from list and overwrite append, remove and all other methods that change content of list but I would like to know if there is an other way. By comparison, if I want to print 'edited' each time I edit an attribute of an object I will not execute print("edited") in all methods of the class of that object. Instead, I will only overwrite __setattribute__. I tried to create my own type which inherits of list and overwrite __setattribute__ but that doesn't work. When I use myList.append __setattribute__ isn't called. I would like to know what's realy occured when I use myList.append ? Is there some magic methods called that I could overwrite ?

我知道那里已经有人问过这个问题:当您在列表上调用追加"时会发生什么?.给出的答案是,没有答案...我希望这是一个错误.

I know that the question have already been asked there : What happens when you call `append` on a list?. The answer given is just, there is no answer... I hope it's a mistake.

我不知道我的请求是否有答案,因此我还将向您解释为什么我会遇到该问题.也许我可以朝另一个方向搜索以完成自己想要的事情.我有几个属性的class.编辑属性后,我要执行一些操作.就像我之前解释的那样,要做到这一点,我将覆盖__setattribute__.这对于大多数属性都适用.问题是lists.如果按如下方式使用属性:myClass.myListAttr.append(something),则在属性值更改时不会调用__setattribute__.

I don't know if there is an answer to my request so I will also explain you why I'm confronted to that problem. Maybe I can search in an other direction to do what I want. I have got a class with several attributes. When an attribute is edited, I want to execute some actions. Like I explain before, to do this I am use to overwrite __setattribute__. That works fine for most of attributes. The problem is lists. If the attribute is used like this : myClass.myListAttr.append(something), __setattribute__ isn't called while the value of the attribute have changed.

问题与dictionaries相同.像pop这样的方法不会调用__setattribute__.

The problem would be the same with dictionaries. Methods like pop doesn't call __setattribute__.

推荐答案

如果我理解正确,那么您会希望每次使用Notify_list之类的东西时,都会在某个变异方法被调用时调用某种方法(实现中的构造函数参数).调用,因此您可以执行以下操作:

If I understand correctly, you would want something like Notify_list that would call some method (argument to the constructor in my implementation) every time a mutating method is called, so you could do something like this:

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]

最简单的实现方法是创建一个子类并覆盖每个变异方法:

The most simple implementation of this would be to create a subclass and override every mutating method:

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.

这显然不是很实用,编写整个定义将非常繁琐且非常重复,因此我们可以使用以下功能为任何给定类动态创建新的子类:

This is obviously not very practical, writing the entire definition would be very tedious and very repetitive, so we can create the new subclass dynamically for any given class with functions like the following:

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

一旦我们可以使用notifying_type创建Notify_listNotify_dict,将很简单:

Once we can use notifying_type creating Notify_list or Notify_dict would be as simple as:

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)

我尚未对此进行广泛的测试,它很可能包含错误/未处理的极端情况,但我确实知道它可以与list一起正常工作!

I have not tested this extensively and it quite possibly contains bugs / unhandled corner cases but I do know it worked correctly with list!

这篇关于当我们编辑(追加,删除...)列表时会发生什么,每次编辑列表时我们都可以执行操作吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆