Overriding __setattr__ at runtime

后端 未结 2 2025
自闭症患者
自闭症患者 2020-12-09 11:01

I am trying to override the __setattr__ method of a Python class, since I want to call another function each time an instance attribute changes its value. Howev

相关标签:
2条回答
  • 2020-12-09 11:01

    @SingleNegationElimination's answer is great, but it cannot work with inheritence, since the child class's __mro__ store's the original class of super class. Inspired by his answer, with little change,

    The idea is simple, switch __setattr__ before __init__, and restore it back after __init__ completed.

    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
    
    0 讨论(0)
  • 2020-12-09 11:05

    Unfortunately, there's no way to "override, after init" python special methods; as a side effect of how that lookup works. The crux of the problem is that python doesn't actually look at the instance; except to get its class; before it starts looking up the special method; so there's no way to get the object's state to affect which method is looked up.

    If you don't like the special behavior in __init__, you could refactor your code to put the special knowledge in __setattr__ instead. Something like:

    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)
    

    Edit: Actually, there is a way to change which special method is looked up, so long as you change its class after it has been initialized. This approach will send you far into the weeds of metaclasses, so without further explanation, here's how that looks:

    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
    
    0 讨论(0)
提交回复
热议问题