在Python代码中我经常看到@property的使用。
如果我理解正确的话,使用属性函数可以定义getter、setter和deleter。
如果没有定义setter和deleter(@x.setter,@x.deleter),为什么要使用@property?这不是和根本不使用@property一样吗?
它创建了一个不允许设置值的 API。这在其他语言中类似于常量。
使用 getter 函数但不使用 setter 定义属性在某些情况下非常有用。假设您在 django 中有一个如下所示的模型;模型本质上是一个数据库表,其中包含称为字段的条目。属性主机名是根据数据库模型中的一个或多个字段计算得出的。这避免了每次相关字段更改时都必须更改数据库表中的另一个条目。
使用属性的真正好处是调用
object.hostname()
与 object.hostname
。后者会自动与对象一起传递,因此当我们转到像 jinja 模板这样的地方时,我们可以调用 object.hostname
,但调用 object.hostname()
会引发错误。
下面的示例是一个带有名称字段的虚拟机模型,以及我们传递虚拟机对象的 jinja 代码示例。
# 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...
TL;博士
因此,如果
@property
函数中有大量逻辑,请注意每次访问该属性时它将运行整个逻辑。在这种情况下,我建议使用 getter 和 setter
冗长
我觉得还没有探索过的另一个方面是,
@property
是一个getter
,可能并且很可能会被多次调用,而setter
很可能在实例化时被调用一次物体。
IMO,如果
@property
功能没有做太多繁重的工作,则应使用此模型。在下面的示例中,我们只是连接一些字符串来生成电子邮件地址。
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)
但是,如果您要向该函数添加一些扩展或繁重的逻辑,那么我建议为它创建一个
getter
,以便它只运行一次。例如,假设我们需要检查电子邮件是否唯一,这种逻辑在 getter
中会得到更好的服务,否则您将在每次想要访问电子邮件时运行逻辑来检查电子邮件的唯一性。
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
)
这是一个很好的答案。此外,您还可以根据其他 kwargs 修改属性的值,并在同一方法声明中执行此操作。如果您创建 self._hostname 实例变量,您还可以根据其他 kwargs 或变量修改该值。您还可以从您的属性中获取值并在其他方法中使用它,因为 self.scheme (见下文)在语法上令人愉悦且简单:)。
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
您可以将属性视为提供虚拟价值。 getter 的用途之一是返回计算值。
例如:
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
这里有两个属性,
area
和circumference
,可以像计算属性一样访问它们。将它们设为真实属性是错误的,因为您实际上是在复制数据。
当然,如果你还添加了一个setter,你可以用它来逆向工程原始值:
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