为什么有人会使用@property而不定义setter或deleter?

11 投票
4 回答
10280 浏览
提问于 2025-04-16 19:09

在Python代码中,我经常看到使用@property这个东西。

如果我理解得没错,使用property函数可以定义获取值、设置值和删除值的功能。

那么,如果没有定义设置值和删除值的功能(也就是没有使用@x.setter和@x.deleter),为什么还要用@property呢?这不是跟根本不使用@property一样吗?

4 个回答

2

简而言之

如果你的 @property 函数里有复杂的逻辑,要注意每次访问这个属性时,都会执行这些逻辑。因此,我建议使用一个带有获取器和设置器的方式。

详细说明

还有一个我觉得没有被充分讨论的点是,@property 作为一个获取器(getter),可能会被调用多次,而设置器(setter)通常只会在你创建对象时被调用一次。

在我看来,如果 @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)

但是,如果你打算在这个函数里添加一些复杂或重的逻辑,那么我建议为它创建一个获取器,这样就只会执行一次。例如,假设我们需要检查电子邮件是否唯一,这个逻辑放在获取器里会更好,否则每次你想访问这个电子邮件时,都会执行一次检查唯一性的逻辑。

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

在某些情况下,定义一个只有获取功能(getter)而没有设置功能(setter)的属性是非常有用的。比如说,在Django中,你有一个模型,模型其实就是一个数据库表,里面有一些叫做字段的条目。这里的hostname属性是从数据库中的一个或多个字段计算得来的。这样就避免了每次相关字段改变时,还需要在数据库表中再添加一个条目的麻烦。

使用属性的真正好处在于调用 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...
10

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

撰写回答