Validating Inputs with Python Class Properties
Tags: python, beginners
Published: 2023-11-05 22:04:16

## Using Properties to Validate an Input at the Time a Class Instance is Created


The purpose of this blog post is to discuss some fundamental guidelines for creating class properties, namely: (1) the need to use getter and setter functions to validate inputs, and (2) the use of different variables for class properties and attributes.  I did not have a clear understanding of those two things when I was first learning how to use class properties in python, so my attempts to use class properties were more frustrating than they needed to be.  In my discussion of these points, I will refer to the following example: 

#### Example 1:
```
class Person:

    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self._name

    def set_name(self, new_name):
        if type(new_name) == str and len(new_name) > 0:
            self._name = new_name

    name = property(get_name, set_name)

```

Example 1 establishes a Person class which is meant to satisfy these conditions:

- An instance of the Person class must be given a name at the time of creation.
- The property functions are used to ensure that a person's name is a string containing at least one character.
- Calling the person's name should return the person's name.

If an instance of the Person class is created with a valid name,
```
> p1 = Person("Frank")
```
calling the instance's name produces the following result:
```
> p1.name
'Frank'
```

Creating an instance of the Person class with an invalid name and then calling the instance's name produces an error:
```
> p2 = Person(2)
> p2.name
*** AttributeError: 'Person' object has no attribute '_name'
```


I am pointing out these conditions because you can find many other good examples of how to use class properties, but they will not necessarily work the same way that Example 1 does.  When I was trying to learn about class properties, I tried to follow examples from various blogs but had difficulty getting the class properties to work on my own.  That was because I did not recognize that the examples I found were implementing class properties in slightly different ways than what I was doing.  I did not understand how the differences impacted how the class properties behaved.  With that in mind, here are some important points I learned about creating class properties:

### 1. When the Getter and Setter Functions Are Required

In Example 1, the methods get_name and set_name are required.  If you take out either method and try to set or retrieve a person's name, you will get an error:
```
AttributeError: can't set attribute
```
I point this out because there are many blogs which say that each of the default property functions are optional.  While that is technically true, it is not very helpful advice when you are trying to learn how to use a class property to validate an input at the time an instance is created.

To back up a bit: python's property function contains four default attributes, as described in the python documentation for built-in functions:

>
>class property(fget=None, fset=None, fdel=None, doc=None)
>>Return a property attribute.
>
>>fget is a function for getting an attribute value. fset is a function for setting an attribute value. fdel is a function for deleting an attribute value. And doc creates a docstring for the attribute.
>
>https://docs.python.org/3/library/functions.html#property

There are times when you do not need to use any of the default attributes.  For instance:

#### Example 2:

```
class Dog:

    def __init__(self, name):
        self.name = name

    @property
    def bark(self):
        print("Woof!")
```
Creating an instance of the Dog class and calling the bark property produces the following:
```
> d1 = Dog("Fido")
> d1.bark
Woof!
```
To use the `bark` property of the Dog class instance, there was no need to create a `get_bark` or `set_bark` function.  However, in order to validate an input as in Example 1, you must create both a getter function and a setter function for the property.  The reason is that instead of `name` simply pointing to an assigned value, it points to the `property()` function.  Therefore, instead of `p1.name` retrieving the assigned value, it calls the property's getter function, which returns the value that has been assigned to `_name`.  Similarly, `p1.name = 'Tim'` doesn't directly re-assign the `name` attribute.  Instead, it calls the property's setter function, which assigns 'Tim' to `_name`.  If either the getter or setter function is omitted from Example 1, the class's name property cannot be set or retrieved.

### 2. Be Mindful About How Variable Names Are Used

In Example 1,  the variable `name` is used in the `__init__` function and has the property function assigned to it.  Within get_name and set_name, the variable `_name` is used.  You can find many other examples of class properties that use two different variables, with the only difference between the variable names being a leading underscore in one of the variables.  This is a naming convention used to indicate that the variable with the leading underscore is meant to be a "private" attribute.  See a discussion regarding the use of underscores here: https://stackoverflow.com/questions/1301346/what-is-the-meaning-of-single-and-double-underscore-before-an-object-name

By having the variables `name` and `_name` identical aside from the underscore, it is easier to see that they are related.  Although they are related, they must use different variables.  You cannot use the same variable for both the property assignment and input assignment.  If I change all the attribute values in Example 1 to 'name', it gets stuck in a loop:
```
RecursionError: maximum recursion depth exceeded while calling a Python object
```
That is because the `self.name` in the `__init__` method calls the `property()` function which has been assigned to it.  The property function then invokes either the get_name or set_name function.  If either the get_name or set_name function uses `self.name` as the variable, it tries to call the `property()` function, which starts the loop over again.  To avoid that issue, the variable used inside get_name and set_name must be distinct.  It is convention to simply modify the property variable by adding an underscrore.

When learning about class properties, I was confused by many of the examples using the 'underscore' variable in the `__init__` method.  For instance, an example in the python documentation for built in functions uses \_x as the variable in the `__init__`, `getx`, `setx`, and `delx` functions:

#### Example 3

```
class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")
```

The reason it works to use `_x` in all the methods in Example 3 is that the value of _x is not being established at the time an instance of class C is created.  Unlike the conditions for Example 1, where a person's name is provided when a Person instance is created, an instance of class C is created and, only after the instance exists, then x is assigned a value:
```
> c1 = C()
> c1.x
> c1.x = "value"
> c1.x
'value'
```
The variable `_x` is given a default value of `None` so that, before `x` is assigned a value, `c1.x` will return `None` rather than causing an AttributeError.

Example 1 would not work properly if the `__init__` method used the `_name` variable.  Instead of validating the name at the time an instance is created, it would only validate the name when the name is changed.  A Person instance could be created with a name of the integer 1 (which violates the rule established in the set_name function), but its name could not then be changed to the integer 2 (because a name of 2 does not satisfy the rule in the set_name function):
```
ipdb> p2 = Person(1)
ipdb> p2.name
1
ipdb> p2.name = 2
ipdb> p2.name
1
```
But a Person class instance isn't supposed to be created in the first place if the name does not satisfy the rule in the setter function.  So `self._name` cannot be used in Example 1's `__init__` method.  Instead, it must use `self.name`, which points to the set_name property function, so that the name provided at the time an instance is created can be validated.

### Conclusion

There are many blogs and tutorials that explain how to create a class property, and they can be very helpful and informative.  However, in order to get the most out of the advice they offer, it is important to be aware of how an example's use of the property function differs from how you may want to use it.  The values that are provided when a class instance is created and whether those values can be changed make a difference in how the class property methods must be created, and even what default property attributes are required.  Personally, once I understood the underlying reasons for variable names and property attributes, creating class properties became much easier.