【问题标题】:What happens when we edit(append, remove...) a list and can we execute actions each time a list is edited当我们编辑(追加,删除...)列表时会发生什么,我们可以在每次编辑列表时执行操作吗
【发布时间】:2016-06-15 04:20:56
【问题描述】:

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

我知道那里已经有人问过这个问题:What happens when you call `append` on a list?。给出的答案只是,没有答案......我希望这是一个错误。

我不知道我的请求是否有答案,所以我也会向您解释为什么我会遇到这个问题。也许我可以在另一个方向搜索来做我想做的事。我有一个class 有几个属性。编辑属性时,我想执行一些操作。就像我之前解释的那样,为此我习惯于覆盖__setattribute__。这适用于大多数属性。问题是lists。如果像这样使用属性:myClass.myListAttr.append(something),则不会在属性值更改时调用__setattribute__

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

【问题讨论】:

  • list.append 没有使用任何魔法方法……它是append 方法。 __setattribute__ 甚至不是 python 识别的魔法方法。 __setattr__ 是一个,但它用于设置与修改列表无关的属性。
  • 所以确实没有办法做到……
  • 我从来没有这么说过;)我想你可以在(包装的)属性上调用方法时使用描述符来通知你的原始对象,我现在正在构建一个答案。跨度>
  • 好的,谢谢!不幸的是,我现在必须离开。明天我会看的。提前谢谢!

标签: python list


【解决方案1】:

如果我理解正确,你会想要像 Notify_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

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

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 一起正常工作!

【讨论】:

    猜你喜欢
    • 2022-01-23
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-04
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多