开发者

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?


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


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...


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


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
            )
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜