【问题标题】:Overriding __setattr__ at runtime在运行时覆盖 __setattr__
【发布时间】:2013-05-07 18:26:05
【问题描述】:

我正在尝试覆盖 Python 类的 __setattr__ 方法,因为我想在每次实例属性更改其值时调用另一个函数。但是,我不希望在 __init__ 方法中出现这种行为,因为在此初始化期间,我设置了一些稍后将使用的属性:

到目前为止,我有这个解决方案,没有在运行时覆盖 __setattr__

class Foo(object):
    def __init__(self, a, host):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
        result = self.process(a)
        for key, value in result.items():
            object.__setattr__(self, key, value)

    def __setattr__(self, name, value):
        print(self.b) # Call to a function using self.b
        object.__setattr__(self, name, value)

但是,我想避免这些 object.__setattr__(...) 并在 __init__ 方法的末尾覆盖 __setattr__

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        result = self.process(a)
        for key, value in result.items():
            setattr(self, key, value)
        # override self.__setattr__ here

    def aux(self, name, value):
        print(self.b)
        object.__setattr__(self, name, value)

我尝试过self.__dict__['__setitem__'] = self.auxobject.__setitem__['__setitem__'] = self.aux,但这些尝试都没有效果。看过this section of the data model reference,不过貌似自己的__setattr__赋值有点棘手。

如何在__init__ 的末尾覆盖__setattr__,或者至少有一个pythonic 解决方案,其中__setattr__ 仅在构造函数中以正常方式调用?

【问题讨论】:

    标签: python


    【解决方案1】:

    不幸的是,没有办法“在 init 之后覆盖”python 特殊方法;作为该查找如何工作的副作用。问题的症结在于python实际上并没有查看实例;除了得到它的类;在它开始查找特殊方法之前;因此无法让对象的状态影响查找的方法。

    如果您不喜欢 __init__ 中的特殊行为,您可以重构代码以将特殊知识放入 __setattr__ 中。比如:

    class Foo(object):
        __initialized = False
        def __init__(self, a, b):
            try:
                self.a = a
                self.b = b
                # ...
            finally:
                self.__initialized = True
    
        def __setattr__(self, attr, value):
            if self.__initialzed:
                print(self.b)
            super(Foo, self).__setattr__(attr, value)
    

    编辑:实际上,有一种方法可以更改查找哪个特殊方法,只要在初始化后更改其类。这种方法会让您远离元类的杂草,因此无需进一步解释,如下所示:

    class AssignableSetattr(type):
        def __new__(mcls, name, bases, attrs):
            def __setattr__(self, attr, value):
                object.__setattr__(self, attr, value)
    
            init_attrs = dict(attrs)
            init_attrs['__setattr__'] = __setattr__
    
            init_cls = super(AssignableSetattr, mcls).__new__(mcls, name, bases, init_attrs)
    
            real_cls = super(AssignableSetattr, mcls).__new__(mcls, name, (init_cls,), attrs)
            init_cls.__real_cls = real_cls
    
            return init_cls
    
        def __call__(cls, *args, **kwargs):
            self = super(AssignableSetattr, cls).__call__(*args, **kwargs)
            print "Created", self
            real_cls = cls.__real_cls
            self.__class__ = real_cls
            return self
    
    
    class Foo(object):
        __metaclass__ = AssignableSetattr
    
        def __init__(self, a, b):
            self.a = a
            self.b = b
            for key, value in process(a).items():
                setattr(self, key, value)
    
        def __setattr__(self, attr, value):
            frob(self.b)
            super(Foo, self).__setattr__(attr, value)
    
    
    def process(a):
        print "processing"
        return {'c': 3 * a}
    
    
    def frob(x):
        print "frobbing", x
    
    
    myfoo = Foo(1, 2)
    myfoo.d = myfoo.c + 1
    

    【讨论】:

    • 哇!哇。我偶然发现了这一点并几乎将其驳回,但事实证明这正是我解决类似问题所需要的。这个答案中有一些严肃的 Python 魔法。对不起,我不能投票 100 次:p
    【解决方案2】:

    @SingleNegationElimination 的答案很好,但它不能与继承一起使用,因为子类的 __mro__ 商店是超类的原始类。受到他的回答的启发,几乎没有改变,

    思路很简单,在__init__之前切换__setattr__,在__init__完成后恢复。

    class CleanSetAttrMeta(type):
        def __call__(cls, *args, **kwargs):
            real_setattr = cls.__setattr__
            cls.__setattr__ = object.__setattr__
            self = super(CleanSetAttrMeta, cls).__call__(*args, **kwargs)
            cls.__setattr__ = real_setattr
            return self
    
    
    class Foo(object):
        __metaclass__ = CleanSetAttrMeta
    
        def __init__(self):
            super(Foo, self).__init__()
            self.a = 1
            self.b = 2
    
        def __setattr__(self, key, value):
            print 'after __init__', self.b
            super(Foo, self).__setattr__(key, value)
    
    
    class Bar(Foo):
        def __init__(self):
            super(Bar, self).__init__()
            self.c = 3
    
    >>> f = Foo()
    >>> f.a = 10
    after __init__ 2
    >>>
    >>> b = Bar()
    >>> b.c = 30
    after __init__ 2
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-15
      • 1970-01-01
      • 2014-06-26
      • 2015-08-11
      • 2018-02-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多