Pojo like classes in Python

Viewed 16367

I want to create a Pojo like class for User in Python. Each of the property will involve some validations. For example: I don't want someone to create a User with a negative value of age. To acheive that I will end up writing a class something like below.

 class User:
        def __init__(self, age):
            self.age = age

        @property
        def age(self):
            return self._age

        @age.setter
        def age(self, value):
            print("Called")
            if value >= 0:
                self._age = 0
            else:
                raise ValueError("Age can't be negative")

I am bit scared after seeing the class above. Reasons:

  1. My User class will have somewhere around 50'ish properties. I am bit scared to imagine how big this class will look in that case.

Is this a correct way of creating such classes in Python?

1 Answers

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.

Related