Question

Ok I've been reading about using the @property decorator in Python rather than specific methods for getting, setting and deleting. Some example code is shown below.

class Person(object):

    def __init__(self):

        self._firstName = None
        self._lastName = None
        self._age = None

    @property
    def firstName(self): return self._firstName
    @firstName.setter
    def firstName(self, val): self._firstName = val
    @firstName.deleter
    def firstName(self): del self._firstName

    @property
    def lastName(self): return self._lastName
    @lastName.setter
    def lastName(self, val): self._lastName = val
    @lastName.deleter
    def lastName(self): del self._lastName

    @property
    def age(self): return self._age
    @age.setter
    def age(self, val): self._age = val
    @age.deleter
    def age(self): del self._age

Peter = Person()

Peter.firstName = 'Peter'
Peter.lastName = 'Smith'
Peter.age = 21

print ' '.join([Peter.firstName, Peter.lastName])

As you can see, the majority of the code in the example is devoted to defining the methods.

My question is whether there is a way to automatically assign the getter, setter and deleter for each of my variables? In my actual code I have a lot more variables and it seems like a lot of repetitive code setting each method.

I realise that for simple cases it may not strictly be necessary however I'd prefer to code the methods in now in case I ever decide to increase the complexity of the class.

Was it helpful?

Solution

Keep it simple.

If you have functions like that, just use a publicly accessible variable. That is:

class Person(object):
    def __init__(self):
        self.firstName = None
        self.lastName = None
        self.age = None

is perfectly fine. A better approach given the usage is to require the data as part of the constructor, thus:

class Person(object):
    def __init__(self, firstName, lastName, age):
        self.firstName = firstName
        self.lastName = lastName
        self.age = None

person = Person('Peter', 'Smith', 21)

If you are concerned about which name is which, you can always be specific:

person = Person(firstName='Peter', lastName='Smith', age=21)

In the future, if you need to make it more complex, you can add getters/setters where needed.

Another consideration is that given the constructor, you can create a string conversion function on the Person object:

def __str__(self):
    return ' '.join([self.firstName, self.lastName])

This then allows you to use it like:

person = Person(firstName='Peter', lastName='Smith', age=21)
print(person)

so you don't need to know how it is implemented internally.

Another consideration is that Chinese/Japanese names place the surname first, people can have multiple last names (esp. in Spain) and can have middle names.

OTHER TIPS

The whole point of the property decorator is so that you don't need to define it from the start. Accessing an attribute directly is exactly the same syntax as accessing one via a property. So you should leave it out completely until you actually do have the complex logic, and only then change to properties.

Manual setter and deleter definition is an explicit way of saying "I spent a lot of time creating this interface, and you are free to set, delete and get this property without any concerns of breaking code".

Otherwise you can simply use instance variables without the property decorator.

def __init__(self):

    self.firstName = None
    self.lastName = None
    self.age = None

On a side note, you probably want to take a look on PEP-8 for pythonic variable naming and code styling conventions.

1. Helper function for defining read/write properties in a Python class:

class PropertyHelperDemo:
    '''Demonstrates a property definition helper function'''

    def prop_helper(k: str):
        def _get(self): return self.__dict__.__getitem__(k)
        def _set(self, v): self.__dict__.__setitem__(k, v)
        def _del(self): self.__dict__.__delitem__(k)
        return property(_get, _set, _del)

    X: float = prop_helper('X')
    Y: float = prop_helper('Y')
    Z: float = prop_helper('Z')
    # etc...

    def __init__(self, X: float, Y: float, Z: float):
        super(PropertyHelperDemo, self).__init__()
        (self.X, self.Y, self.Z) = (X, Y, Z)

    # for read-only properties, the built-in technique remains sleek enough already
    @property
    def Total(self) -> float:
        return self.X + self.Y + self.Z

2. Runtime usage:

obj = PropertyHelperDemo(3.0, 4.0, 5.0)

print(obj.Total)                # ―► "12.0"

obj.X = obj.Y = obj.Z = 2.0     # set properties

print(obj.Total)                # ―► "6.0"

print(obj.X ** 3.0)             # get a property ―► "8.0"

del obj.X                       # delete a property
try: print(obj.X)
except: print('nope')           # it's gone ―► "nope"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top