Reputation: 12378
Is there a way in python such that given a python class:
class Foo(object):
apple = None
orange = None
def __init__(self, apple=None, orange=None)
super(Foo, self).__init__()
self.apple = apple
self.orange = orange
after a Foo object is init, one of the two attributes (apple, orange) must always be assigned to a value other than None, and that at no time should both attributes be assigned to something other than None.
In other words:
orange is None | orange is not None
|
apple is None NO | YES
________________________________________|___________________________
|
apple is not None YES | NO
How would one do this in python?
Upvotes: 1
Views: 769
Reputation: 29866
In the constructor, it's simple enough to raise
a ValueError
if they are both None
or both set. The problem is later on in the code.
Following the principle of least surprise, I think you should mark the variables private and use setter methods (not property setter, plain old methods). This clearly suggests you're doing extra logic when the value is set, and it gives you an obvious place to add extra logic later if needed. Using getter property methods would be fine, though. So something like this:
class Foo(object):
def __init__(self, apple=None, orange=None):
super(Foo, self).__init__()
if apple is None and orange is None:
raise ValueError('apple and orange cannot both be None')
if apple is not None and orange is not None:
raise ValueError('apple and orange cannot both be set')
self._apple = apple
self._orange = orange
@property
def apple(self):
return self._apple
@property
def orange(self):
return self._orange
def setAppleClearOrange(self, value):
if value is None:
raise ValueError('Cannot set both to None')
self._orange = None
self._apple = value
def setOrangeClearApple(self, value):
if value is None:
raise ValueError('Cannot set both to None')
self._apple = None
self._orange = value
Yes, it's a bit verbose, but it's obvious what your intentions are, which is actually more important.
Upvotes: 4
Reputation: 304137
Here is a version that will set the other aspect to None
if necessary. You'll have to define what should happen if they are both set to None
class Foo(object):
def __init__(self, apple=None, orange=None):
# make sure exactly 1 of apple/orange is not None
if sum(x is None for x in (apple, orange)) != 1:
raise ...
self._apple = apple
self._orange = orange
@property
def apple(self):
return self._apple
@apple.setter
def apple(self, value):
if value is not None:
self._orange = None
elif self._orange is None:
raise ...
self._apple = value
@property
def orange(self):
return self._orange
@orange.setter
def orange(self, value):
if value is not None:
self._apple = None
elif self._apple is None:
raise ...
self._orange = value
Upvotes: 0
Reputation:
assert (apple is None) != (orange is None)
If you put that code in the __init__ method, and your precondition is false, an AssertionError
will be thrown.
Upvotes: 0
Reputation: 7035
You could override __setattr__()
for that class, and have it handle that logic for you. See http://docs.python.org/2/reference/datamodel.html#object.setattr
Something like
class FooBar(object):
def __setattr__(self, key, val):
if key == 'apple' or key == 'orange':
# Decide what to do about it
self.__dict__[key] = val
Note that you need to access the key through the object's __dict__
to avoid an infinite loop, because __setattr__()
replaces the normal assignment mechanism.
Also, note that this will check every assignment to these two variables, including in your own code. If that's not what you want, then put some test in __init__()
, as others suggested.
Upvotes: 0
Reputation: 4367
objs = filter(None, [apple, orange, ...])
if len(objs) == 0:
raise Exception("At least 1 object of [apple, orange] must be different from None.")
elif len(objs) > 1:
raise Exception("At most 1 object of [apple, orange] should be different from None.")
Upvotes: 0
Reputation: 880
Something like
if !((self.apple==None)^(self.orange==None))
// some error
Might do the trick... ^ is the XOR operator, which returns true if one, but not both, of the operands are true.
Upvotes: 1