1

Given a class definition that allows 3 possible inputs:

class FooBar(object):
    def __init__(self, x=None, y=None, z=None):
        if x is not None:
            self.x = x
        elif if y is not None:
            self.y = y
        elif if z is not None:
            self.z = z
        else:
            raise ValueError('Please supply either x,y or z')

This 3 inputs are related each other, lets say:

x = .5*y = .25*z

This also implies:

y = .5*z = 2*x

and

z = 2*y = 4*x

When creating a instance of FooBar(), the user need to supply one of those and the __init__ takes care of it.

Now I would like to do the following

  • If any one of the 3 variables are changed the others change following the relationship.

To try to accomplish that I did:

@property
def x(self):
    return self._x

@x.setter
def x(self, value):
    self._x = value
    self._y = 2*self._x
    self._z = 4*self._x

And to the others:

@property
def y(self):
    return self._y

@y.setter
def y(self, value):
    self._y = value
    self._x = .5*self._y
    self._z = 2*self._y

@property
def z(self):
    return self._z

@z.setter
def z(self, value):
    self._z = value
    self._x = .25*self._z
    self._y = .5*self._z

Is this the correct approach?

4
  • What if I provide FooBar(x=1, y=1425)? Commented Jan 2, 2018 at 23:55
  • 1
    If x = 2*y, then self._y = value/2 for the setter of x. Not value * 2. Commented Jan 2, 2018 at 23:59
  • Based on my __init__, x takes precedence, that is the reason to be and if elif.... Commented Jan 2, 2018 at 23:59
  • ok. I did the opposite, you're right. But I'm more worried about the instance variable setting. Commented Jan 3, 2018 at 0:01

1 Answer 1

2

I think you make this more complicated than you have to. If the variables are related, and one can fully be determined by the other, there is no need to store three variables. You can store one variable, and dynamically calculate the others. Like:

class Foo(object):

    def __init__(self, x=None, y=None, z=None):
        if x is not None:
            self.x = x
        elif x is not None:
            self.y = y
        elif z is not None:
            self.z = z
        else:
            raise ValueError('Provide an x, y, or z value.')

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = x

    @property
    def y(self):
        return self._x / 2.0

    @y.setter
    def y(self, value):
        self._x = 2 * value

    @property
    def z(self):
        return self._x / 4.0

    @z.setter
    def z(self, value):
        self._x = 4 * value

We thus store only a _x attribute on the class, and all the rest of the getters and setters, use the _x attribute (we can of course use _y or _z instead).

Furthermore something that is not very elegant is that a programmer can instantiate a Foo(x=1, y=425). As you can see, that means that it contains inconsistency. Perhaps it is worth raising an error in that case.

You can ensure that you only have one parameter provided by adding the following check in the init module:

class Foo(object):

    def __init__(self, x=None, y=None, z=None):
        data = [i for i in [x, y, z] if i is not None]
        if len(data) > 1:
            raise ValueError('Multiple parameters provided.')
        if x is not None:
            self.x = x
        elif x is not None:
            self.y = y
        elif z is not None:
            self.z = z
        else:
            raise ValueError('Provide an x, y, or z value.')

    # ...

Here we thus construct a list of all non-None values, if there is more than one, the programmer provided two or more values, and then it is better to raise an exception.

Sign up to request clarification or add additional context in comments.

2 Comments

To avoid the " inconsistency", is a simple matter to add more conditions on if clauses as: if x is not None and y is None and z is None: . And do the same to all other 2 cases, but this grows insanely (in case of more parameters). Any other elegant way to do this?
Good idea, did this way: if sum(i is not None for i in locals().values()) > 2: raise ValueError("..."). Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.