1

I would like to have some validation inside of a property setter in a class. This property has to be set during object creation and then it needs to be read-only from the outside.

I found the following solution and I wonder if there is a more straightforward or Pythonic way to do it. (I am aware that by calling user._username from the outside it is still possible to set this property, but "we are all consenting adults" etc. - the important thing is that user.username should not have a setter).

class User:
    def __init__(self, username):
        self._usernname = username

    @property
    def username(self):
        return self.__username

    @property
    def _username(self):
        return self.__username

    @_username.setter
    def _username(self, value):
        if not value or len(value) < 3:
            raise ValueError('Invalid username')
        self.__username = value

Edit: In addition to the comments here, options for validation in the constructor can be found here: numpy performance and random numbers.

9
  • 1
    just check the value to store into _username with one underscore in the constructor, and remove the _username property altogether... Commented Dec 21, 2017 at 19:50
  • Do all the initialisation/validation in the __init__ method. The additional _username property looks very artificial and serves no real purpose. Commented Dec 21, 2017 at 19:52
  • Thanks @AnttiHaapala, but then if there is no _username property, where can I validate this value? Commented Dec 21, 2017 at 19:55
  • 1
    @SamuBalogh. Just move the setter validation code into __init__ and adjust accordingly. Get rid of the two _username property functions. Commented Dec 21, 2017 at 20:00
  • 2
    @SamuBalogh, The _username attribute is internal and should have no outward-facing api. It's only ever set once in __init__, so there's no point in creating a property for it. Commented Dec 21, 2017 at 20:05

1 Answer 1

0

Your example is interesting because you both have a need for read-only (that is why you create a read-only property facade username) and for validation (that is why you create a property also for the internal attribute _username, with a setter).

You can do the same efficiently with pyfields:

from pyfields import field

class User(object):
    username = field(read_only=True, 
                     validators={'should contain more than 2 characters': lambda s: len(s) > 2})

u = User()
u.username = "earthling"
print("Hello %s !\n" % u.username)
print(vars(u))
print()
u.username = "earthling2"  # <-- raises error

yields

Hello earthling !

{'_username': 'earthling'}

pyfields.core.ReadOnlyFieldError: 
   Read-only field '<...>.User.username' has already been initialized on 
   instance <<...>.User object at 0x0000022895908EF0> and cannot be modified anymore.

It is basically similar to what you were doing manually, except that it relies on descriptors rather than properties (descriptor is the lower-level protocol used by python behind the scenes in properties). Also, it only relies on a single "nesting" level: the internal attribute _username is a plain old attribute as ekhumoro's comment above suggests.

See pyfields documentation for details.

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.