2016-06-13 36 views
1

我想知道是否有創建一個list將執行一些動作,每次的方式會發生什麼我使用方法append(或其他類似的方法)。 我知道我可以創建一個class繼承自list並覆蓋append,remove和其他所有更改list內容的方法,但我想知道是否有其他方法。 相比之下,如果我想print「編輯」每一次我編輯我不會在該對象的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__

+1

'list.append'不使用任何魔術方法......它是'append'方法。 '__setattribute__'甚至不是python認可的神奇方法。 '__setattr__'是一個,但是用於設置與修改列表無關的屬性。 –

+0

所以確實沒有辦法做到這一點...... – Morgan

+0

我從來沒有說過;)我認爲你可以使用描述符來通知你的原始對象當一個(包裝)屬性上調用方法時,我正在構造現在回答。 –

回答

3

如果我理解正確的話,你會希望每一個不同誘變方法被調用時,像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. 

這顯然不是很實用,寫的全高清將是非常乏味和非常repetit香港專業教育學院,所以我們可以動態地創建新的子類對於任何給定類一樣的功能如下:

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

+0

這很好,謝謝! – Morgan

相關問題