Peter Smit
Peter Smit

Reputation: 28736

Why would someone use @property if no setter or deleter are defined?

In python code I often see the use of @property.

If I understand correctly, with the property function a getter setter and deleter can be defined.

Why would one use @property if the setter and deleter are not defined (@x.setter, @x.deleter)? Isn't this the same as not using @property at all?

Upvotes: 11

Views: 14798

Answers (5)

Manngo
Manngo

Reputation: 16381

You can think of properties as supplying virtual values. One use of a getter is to return a calculated value.

For example:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * self.radius ** 2

    @property
    def circumference(self):
        return math.pi * self.radius * 2
    
test = Circle(10)

print(test.circumference)   #   62.83185307179586
print(test.area)            #   314.1592653589793

Here you have two properties, area and circumference which can be accessed like calculated attributes. It would be a mistake to make them real attributes since you’re effectively duplicating data.

Of course, if you also add a setter, you can use this to reverse engineer the original value:

class Circle:
    def __init__(self, radius):
        self.radius = radius
    @property
    def area(self):
        return math.pi * self.radius ** 2
    @area.setter
    def area(self, value):
        self.radius = math.sqrt(value / math.pi)
    @property
    def circumference(self):
        return math.pi * self.radius * 2
    @circumference.setter
    def circumference(self,value):
        self.radius = value / math.pi / 2

        
test = Circle(10)

test.area = 1240
print(test.radius)          #   19.867165345562018
test.circumference = 60
print(test.radius)          #   9.549296585513721

Upvotes: 0

harnamc
harnamc

Reputation: 551

TL;DR

So if you have heavy logic in the @property function, be aware that it will be running the entire logic each time you access the property. In this case I would suggest using a getter with a setter

Verbose

Another aspect which I don't feel has been explored is that the @property which is a getter, could be and most likely will be called multiple times where as the setter will most likely be called once when you instantiate the object.

IMO, this model should be used if the @property function is not doing too much heavy lifting. In the example below, we are just concatenating some strings to generate an email address.

class User:
    DOMAIN = "boulder.com"

    def __init__(self, first_name: str, last_name: str) -> None:
        self.first_name = first_name
        self.last_name = last_name

    @property
    def email(self) -> str:
        return "{}_{}@{}".format(self.first_name, self.last_name, self.DOMAIN)

But if you are going to add some extended or heavy logic to the function, then I would recommend creating a getter for it so that it is only run once. For example, lets say we need to check whether the email is unique, this logic would be better served in a getter other wise you will run the logic to check for uniqueness of the email each time you want to access it.

class User:
    DOMAIN = "boulder.com"

    def __init__(self, first_name: str, last_name: str) -> None:
        self.first_name = first_name
        self.last_name = last_name

    @property
    def email(self) -> str:
        return self._email

    @email.setter
    def email(self) -> None:
        proposed_email = "{}_{}@{}".format(self.first_name, self.last_name, self.DOMAIN)

        if is_unique_email(proposed_email):
            self._email = proposed_email
        else:
            random_suffix = get_random_suffix()
            self._email = "{}_{}_{}@{}".format(
                self.first_name, self.last_name, random_suffix, self.DOMAIN
            )

Upvotes: 2

Jenobi
Jenobi

Reputation: 468

This is a good answer. Additionally, you can also modify the value of your property based on other kwargs and do this within the same method declaration. If you create a self._hostname instance variable, you can also modify the value based on other kwargs or variables. You can also obtain the value from your property and use it within other methods as self.scheme (see below) is syntactically pleasing and simple :).


class Neo4j(Database):
    def __init__(self, label, env, username, password, hostname, port=None, routing_context=False, policy=None, scheme=None, certificate=None):
        super().__init__(label, env)
        
        self.username = username
        self._password = password
        self.hostname = hostname
        self.port = port # defaults, 7687
        self._scheme = scheme # example - neo4j, bolt
        self.routing_context = routing_context # self.policy = policy policy=None,
        self.policy = policy # Examples, europe, america
        self.certificate = certificate # examples, None, +s, +ssc

    @property
    def scheme(self):
        if not self.certificate:
            return f'{self._scheme}'
        return f'{self._scheme}+{self.certificate}'

    def __repr__(self) -> str:
        return f'<{self.scheme}://{self.hostname}:{self.port}>' #if self.ro


db = Neo4j(label='test', env='dec', username='jordan', password='pass', hostname='localhost', port=7698, scheme='neo4j', certificate='ssc')

print(db.scheme) >>> neo4j+ssc

Upvotes: 0

Mitchell Walls
Mitchell Walls

Reputation: 1151

Defining a property with a getter function but without a setter can be very useful in certain scenarios. Lets say you have a model as below in django; a model is essentially a database table with entries called fields. The property hostname is computed from one or more fields in the model from the database. This circumvents needing another entry in the database table that has to be changed everytime the relevant fields are changed.

The true benefit of using a property is calling object.hostname() vs. object.hostname. The latter is passed along with the object automatically so when we go to a place like a jinja template we can call object.hostname but calling object.hostname() will raise an error.

The example below is a virtualmachine model with a name field and an example of the jinja code where we passed a virtualmachine object.

# PYTHON CODE
class VirtualMachine(models.Model):
    name = models.CharField(max_length=128, unique=True)

    @property
    def hostname(self):
        return "{}-{}.{}".format(
            gethostname().split('.')[0],
            self.name,
            settings.EFFICIENT_DOMAIN
        )

# JINJA CODE
...start HTML...
Name: {{ object.name }}

# fails
Hostname: {{ object.hostname() }}

# passes
Hostname: {{ object.hostname }}
...end HTML...

Upvotes: 4

bradley.ayers
bradley.ayers

Reputation: 38392

It creates an API that does not allow a value to be set. This is similar in other languages to a constant.

Upvotes: 11

Related Questions