How to validate the __init__ parameters
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Mon Dec 21 16:50:56 EST 2009
On Mon, 21 Dec 2009 09:41:22 -0800, Denis Doria wrote:
> Hi;
>
> I'm checking the best way to validate attributes inside a class.
There is no "best way", since it depends on personal taste.
> Of
> course I can use property to check it, but I really want to do it inside
> the __init__:
If you "really want to do it inside the __init__", then copy the code
that you would put in the property's setter into the __init__ method. But
why do you care that the check is inside the __init__ method? All that is
really important is that the __init__ method *calls* the check, not where
the check lives.
> class A:
> def __init__(self, foo, bar):
> self.foo = foo #check if foo is correct
> self.bar = bar
Here are some ways of doing this, more or less in order of complexity and
difficulty.
(1) Don't validate at all. Just document that foo must be no more than
five characters long, and if the caller pays no attention and passes a
too-long string, any explosions that happen are their fault, not yours.
class A:
"""Class A does blah blah.
If foo is longer than five characters, behaviour is undefined.
"""
def __init__(self, foo = None, bar = None):
self.foo = foo
self.bar = bar
(This may seem silly, but for more difficult constraints which are hard
to test, it may be your only choice.)
(2) Validate once only, at initialisation time:
class A:
def __init__(self, foo = None, bar = None):
if len(foo) > 5:
raise ValueError("foo is too big")
self.foo = foo
self.bar = bar
Note that further assignments to instance.foo will *not* be validated.
(3) Move the validation into a method. This is particularly useful if the
validation is complex, or if you expect to want to over-ride it in a sub-
class.
class A:
def __init__(self, foo = None, bar = None):
self.validate(foo)
self.foo = foo
self.bar = bar
def validate(self, foo):
if len(foo) > 5:
raise ValueError("foo is too big")
Further assignments to instance.foo are still not validated.
(4) Validate on every assignment to foo by using a property. Note that
for this to work, you MUST use a new-style class. In Python 3, you don't
need to do anything special, but in Python 2.x you must inherit from
object:
class A(object):
def __init__(self, foo = None, bar = None):
self.foo = foo # calls the property setter
self.bar = bar
def _setter(self, foo):
if len(foo) > 5:
raise ValueError("foo is too big")
self._foo = foo
def _getter(self):
return self._foo
foo = property(_getter, _setter, None, "optional doc string for foo")
If you think this looks "too much like Java", well, sorry, but that's
what getters and setters look like. But note that you never need to call
the getter or setter explicitly, you just access instance.foo as normal.
(5) Use explicit Java-style getter and setter methods. This avoids using
property, so it doesn't need to be a new-style class:
class A:
def __init__(self, foo = None, bar = None):
self.setfoo(foo)
self.bar = bar
def setfoo(self, foo):
if len(foo) > 5:
raise ValueError("foo is too big")
self._foo = foo
def getfoo(self):
return self._foo
If you want to write Java using Python syntax, this may be the solution
for you. But be aware that Python programmers will laugh at you.
(5) If the constraint on foo is significant enough, perhaps it should be
made part of the type of foo.
class FooType(str): # or inherit from list, or whatever...
def __init__(self, x):
if len(x) > 5:
raise ValueError("initial value is too big, invalid FooType")
class A:
def __init__(self, foo = None, bar = None):
self.foo = FooType(foo)
self.bar = bar
Other, more complex solutions include using decorators or even metaclass
programming. Don't worry about these at this point, I'm just showing off
*wink*
Hope this helps,
--
Steven
More information about the Python-list
mailing list