I don't see what's clumpsy about this - most languages out there do not have built-in getter/setter validation so you would end up writing something similar to this in any language you choose - if you want to turn all your properties into getter/setter pairs with validation, you'll have to write them separately.
Now, Python is a dynamic language of a very flexible nature so there are several ways which you can use to reduce the verbosity (but not the complexity) of this process. For example, you can create your own validator decorator to be clamped on top of your setters, e.g.:
def validator(cmp, exc):
def decorator(setter): # a decorator for the setter
def wrapper(self, value): # the wrapper around your setter
if cmp(value): # if cmp function returns True, raise the passed exception
raise exc
setter(self, value) # otherwise just call the setter to do its job
return wrapper
return decorator
And now you can define your getter/setter pairs with validation included as:
class User:
def __init__(self, age):
self.age = age
@property
def age(self):
return self._age
@age.setter
@validator(lambda x: x < 0, ValueError("Age can't be negative"))
def age(self, value):
self._age = value
However, if you're only ever going to just do the validation and no other processing in your setters (and getters), you can just define your own validating property and save a lot on verbosity, something like:
class ValidatingProperty(object):
def __init__(self, prop, cmp, exc):
self.prop = prop
self.cmp = cmp
self.exc = exc
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.prop, None)
def __set__(self, instance, value):
if self.cmp(value):
raise self.exc
setattr(instance, self.prop, value)
def __delete__(self, instance):
delattr(instance, self.prop)
And now you can build your class getters/setters as simple as:
class User:
age = ValidatingProperty("_age", lambda x: x < 0, ValueError("Age can't be negative"))
def __init__(self, age):
self.age = age
And if you ever need to access the raw property (assuming it was set), without wrappers around it, you can still access it with self._age
(or whatever 'real' property that you've passed as the first argument to ValidatingProperty
). You can even build your validators separately so you don't rely on lambdas (e.g. create an IntegerValidator
class which lets you pass the ranges for validation and then reuse where needed).
The other option is to treat the users of your classes as adults and explain the valid values in the documentation and if they go outside of that - there be dragons. If the class is intended to be populated with data from end-users, the validation should be performed on the side that collects the end-user data (so that the end users can get a meaningful error with them in mind), not necessarily in the model itself.