小能豆

在运行时覆盖 __setattr__

py

我知道在 Python 中可以在运行时向类添加方法:

class Test:
    def __init__(self):
        self.a=5

test=Test()

import types
def foo(self):
    print self.a
test.foo = types.MethodType(foo, test)
test.foo() #prints 5

而且我还知道可以覆盖类定义中的默认setattr :

class Test:
    def __init__(self):
        self.a=5
    def __setattr__(self,name,value):
        print "Possibility disabled for the sake of this test"

test=Test() #prints the message from the custom method called inside __init__

但是,似乎无法在运行时覆盖setattr

class Test:
    def __init__(self):
        self.a=5

test=Test()

import types
def __setattr__(self,name,value):
    print "Possibility disabled for the sake of this test"
test.__setattr__ = types.MethodType(__setattr__, test)
test.a=10 #does the assignment instead of calling the custom method

在最后两种情况下,dir(test) 还报告了方法setattr。但是,虽然在第一种情况下它可以正常工作,但在第二种情况下却不能。请注意,我也可以显式调用它,在这种情况下它可以工作。似乎虽然该方法已定义,但尚未正确映射以覆盖默认分配方法。我遗漏了什么吗?

顺便说一下,我使用的是 Python 2.7。这个问题主要是学术性的,因为从程序设计的角度来看,这样做可能不是一个好主意,但它仍然值得回答——尽管我搜索过,但我没能找到任何地方的文档。


阅读 10

收藏
2024-11-14

共1个答案

小能豆

在 Python 中,__setattr__ 是一个特殊方法,用于在对象的属性赋值时拦截并处理属性赋值操作。您所提到的问题实际上涉及了 Python 对于 __setattr__ 方法的处理方式。下面我会解释为什么您的第二种方法没有像预期的那样工作,以及如何正确地覆盖 __setattr__

问题的原因

Python 中的 __setattr__ 方法并不像普通的方法那样直接在实例对象上进行映射。实际上,__setattr__ 是一个类的特殊方法,当你给实例添加 __setattr__ 时,它并不会直接覆盖默认的属性赋值操作。特别是,__setattr__ 会在类的定义过程中被调用,而不是在运行时动态地通过实例对象调用

在您的第二个例子中,虽然您尝试通过 test.__setattr__ = types.MethodType(__setattr__, test) 来覆盖 __setattr__,但这个操作不会影响到实例的正常属性赋值机制。Python 会在实例创建时使用类的默认 __setattr__,而不是动态分配的方法。

为什么 __setattr__ 不能在运行时直接覆盖

__setattr__ 是通过类定义来控制的,它并不是实例方法。因此,当你尝试直接修改实例的 __setattr__ 时,它实际上不会影响类的行为。Python 会始终使用类的 __setattr__ 来处理属性的赋值,而实例本身并不会“接管” __setattr__ 的功能。

如何正确地覆盖 __setattr__

要覆盖 __setattr__,应该在类的定义中进行,或者通过 types.MethodType 给类本身动态绑定。换句话说,您不能像普通的实例方法那样修改实例的 __setattr__,而是应该通过修改类的 __setattr__ 来实现。

解决方法 1: 通过类的定义覆盖 __setattr__

class Test:
    def __init__(self):
        self.a = 5

    def __setattr__(self, name, value):
        print(f"Setting {name} to {value}")
        super(Test, self).__setattr__(name, value)  # 确保正常设置属性

test = Test()
test.a = 10  # 调用自定义的 __setattr__

在这个例子中,我们通过在类定义中定义 __setattr__ 来拦截属性的设置。super() 确保属性可以正常地被设置到实例中。

解决方法 2: 动态地修改类的 __setattr__

你可以通过 types.MethodType 来动态地修改类的 __setattr__,而不是修改实例的 __setattr__。这样做会影响整个类的行为:

import types

class Test:
    def __init__(self):
        self.a = 5

def custom_setattr(self, name, value):
    print(f"Setting {name} to {value}")
    super(Test, self).__setattr__(name, value)

# 修改类的 __setattr__ 方法
Test.__setattr__ = types.MethodType(custom_setattr, Test)

test = Test()
test.a = 10  # 调用自定义的 __setattr__

总结

  • __setattr__ 是类的特殊方法,用于拦截实例的属性赋值。它不能像普通实例方法那样在运行时通过实例对象修改。
  • 如果要修改 __setattr__,可以在类定义中直接覆盖它,或者通过 types.MethodType 修改类本身的 __setattr__
  • 在 Python 中,实例的方法和类的方法处理方式不同,__setattr__ 必须在类级别覆盖才能生效。
2024-11-14