Clean way to disable `__setattr__` until after initialization
Asked Answered
B

4

18

I've written the following wrapper class. I want to define __setattr__ such that it redirects all attributes to the wrapped class. However, this prevents me from initializing the wrapper class. Any elegant way to fix this?

class Wrapper:
    def __init__(self, value):
        # How to use the default '__setattr__' inside '__init__'?
        self.value = value

    def __setattr__(self, name, value):
        setattr(self.value, name, value)
Bromidic answered 21/10, 2012 at 14:52 Comment(3)
What does Wrapper wrap? Why doesn't it inherit object?Sisile
@Tichodroma It wraps anything. I'm using it in a GUI application; the wrapper notifies listeners when the enclosed object is modified. It doesn't inherit from object because I only want to use Python 3.Bromidic
This technique ("OOP pattern") is known as Delegation (or, more precisely, Forwarding). Python FAQ has an example usage, and a note about taking care when implementing __setattr__().Affirmatory
P
18

You are catching all assignments, which prevents the constructor from assigning self.value. You can use self.__dict__ to access the instance dictionary. Try:

class Wrapper:
    def __init__(self, value):
        self.__dict__['value'] = value

    def __setattr__(self, name, value):
        setattr(self.value, name, value)

Another way using object.__setattr__:

class Wrapper(object):
    def __init__(self, value):
        object.__setattr__(self, 'value', value)

    def __setattr__(self, name, value):
        setattr(self.value, name, value)
Prosaism answered 21/10, 2012 at 15:5 Comment(2)
Nice. One question though: why should I use self.__dict__['value'] inside __setattr__ as well?Bromidic
@Paul Sorry, you don't need that. I thought you were overriding __getattr__ as well.Prosaism
B
11

A way to disable the __setattr__ until after initialization without changing the self.value = value syntax in the __init__ method is covered here. In short, embed knowledge of initialization in the object and use it in the __setattr__ method. For your Wrapper:

class Wrapper:
    __initialized = False
    def __init__(self, value):
        self.value = value
        self.__initialized = True

    def __setattr__(self, name, value):
        if self.__initialized:
            # your __setattr__ implementation here
        else:
            object.__setattr__(self, name, value)
Beggar answered 20/6, 2014 at 19:0 Comment(0)
M
0

With __getattr__ overridden as well::

class Wrapper:
    def __init__(self,wrapped):
        self.__dict__['wrapped'] = wrapped
    def __setattr__(self,name,value):
        setattr(self.__dict__['wrapped'],name,value)
    def __getattr__(self,name):
        return getattr(self.__dict__['wrapped'],name)


class A:
    def __init__(self,a):
        self.a = a

wa = Wrapper(A(3))
#wa.a == wa.wrapped.a == 3
Margaretamargarete answered 3/6, 2016 at 13:54 Comment(0)
L
0

As suggested in other answers, one idea is to directly access the object dictionary to bypass setattr resolution.

For something easy to read, I suggest the following:

  def __init__(self,wrapped1, wrapped2):
       vars(self).update(dict(
          _wrapped1=wrapped1,
          _wrapped2=wrapped2,
        ))

Using vars is optional, but I find it nicer than directly accessing self.__dict__, and the inline dict() notation allows for grouping all instance variable initialization in a visible block with minimum boilerplate code overhead.

Lilialiliaceous answered 3/11, 2019 at 1:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.