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
)
精彩评论