0

I'd like to create a function that writes to an attribute in a Python class, but be able to decide whether the attribute should be written.

class Test(object):
    def __init__(self):
        self.a = None
        self.b = None

    def write_attrib(self,which_attrib,value):
        # perform a check here
        which_attrib = value

    def write_a(self,value):
        self.write_attrib(self.a, value)

    def write_b(self,value):
        self.write_attrib(self.b, value)

if __name__ == '__main__':
    t  = Test()
    t.write_a(5)
    t.write_b(4)
    print(t.a, t.b)

Now this will result in both values being None, so they haven't been touched by the function.

I'd like to do this approach so I can do some checks in write_attrib before writing the attribute and I don't have to write these checks twice (but would like to make the convenience functions write_a and write_b available).

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
MichaelA
  • 1,866
  • 2
  • 23
  • 38
  • 3
    Why do you want to do this? Why not just use e.g. `setattr(t, 'a', 5)` (or `setattr(self, which_attrib, value)` in `write_attrib`), or if you need to do something specific when particular attributes are set implement a `@property` or even `__setattr__`? What exactly are you trying to achieve? – jonrsharpe Apr 22 '15 at 10:24
  • Well, maybe I just wasn't aware of the magic method __setattr__, but pretty much before writing the attribute I'd like to do some error checking to ensure consistency. If I'd change __setattr__ this would work for all attributes (I don't want that), but I can use @property to do this only for some? How would that work? – MichaelA Apr 22 '15 at 10:31
  • You could implement `__setattr__` to only apply the checking to a list of specified attributes, and ignore the rest. If you want to implement properties, read e.g. https://docs.python.org/2/library/functions.html#property – jonrsharpe Apr 22 '15 at 10:32

2 Answers2

3

To build on my comment, you can implement __setattr__ to only validate the properties you specify:

class Test(object):

    VALIDATED = {'a', 'b'}

    def __init__(self):
        self.a = None
        self.b = None

    def __setattr__(self, attr, val):
        if attr in self.VALIDATED:
            pass  # add your checking
        super(Test, self).__setattr__(attr, val)

Alternatively, you can implement a @property for the attributes you want to validate, managing access to a private-by-convention attribute (with a leading underscore in the identifier):

class Test(object):

    def __init__(self):
        self.a = None
        self.b = None

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, val):
        # add your checking here
        self._a = val

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, val):
        # add your checking here
        self._b = val

The former is probably neater if you have many attributes with the same validation rules, the latter if you have fewer attributes and/or different validation rules for each one. Both allow you to have clean attribute access, without getters or setters (see e.g. Python @property versus getters and setters).

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • The `a` in `self.a` is actually a reference to the property which sets `self._a = None` when `Test` is instantiated. Is that what's happening? – xyres Apr 22 '15 at 11:16
  • @xyres yes: `self.a = None` calls `self.a.setter(None)` which sets `self._a = None`. – jonrsharpe Apr 22 '15 at 11:16
  • Thanks. That's really creative. – xyres Apr 22 '15 at 11:19
  • @xyres no problem. You *might* assign directly to the private attribute `_a` if, for instance ,`None` would be a valid starting value but not one you want the user to set. If you're interested in this, I answered a similar question yesterday: http://stackoverflow.com/q/29770260/3001761 – jonrsharpe Apr 22 '15 at 11:20
1

Use property

class Test(object):
    def __init__(self):
        self._a = None

    @property:
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        # Do your validation here
        self._a = value
xyres
  • 20,487
  • 3
  • 56
  • 85
  • 1
    Note that assigning to `self.a` rather than `self._a` in `__init__` means that the validation runs there, too, which is generally good practice. It also minimises the code changes if you introduce properties for regular attributes later on. – jonrsharpe Apr 22 '15 at 10:45