如果没有定义setter或deleter,为什么有人会使用@property?

问题描述 投票:0回答:5

在Python代码中我经常看到@property的使用。

如果我理解正确的话,使用属性函数可以定义getter、setter和deleter。

如果没有定义setter和deleter(@x.setter,@x.deleter),为什么要使用@property?这不是和根本不使用@property一样吗?

python properties decorator
5个回答
10
投票

它创建了一个不允许设置值的 API。这在其他语言中类似于常量。


4
投票

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

2
投票

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
            )

0
投票

这是一个很好的答案。此外,您还可以根据其他 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

0
投票

您可以将属性视为提供虚拟价值。 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
© www.soinside.com 2019 - 2024. All rights reserved.