何时以及如何在 Python 中使用内置函数 property()

2025-03-10 08:52:00
admin
原创
68
摘要:问题描述:在我看来,除了一些语法糖之外,property() 并没有什么用处。当然,能够a.b=2用 代替写出来是件好事a.setB(2),但隐藏 ab=2 不是简单赋值的事实似乎会招致麻烦,要么是因为可能会发生一些意想不到的结果,比如a.b=2实际上会导致a.b。1要么是引发异常。要么是性能问题。要么就是让...

问题描述:

在我看来,除了一些语法糖之外,property() 并没有什么用处。

当然,能够a.b=2用 代替写出来是件好事a.setB(2),但隐藏 ab=2 不是简单赋值的事实似乎会招致麻烦,要么是因为可能会发生一些意想不到的结果,比如a.b=2实际上会导致a.b1要么是引发异常。要么是性能问题。要么就是让人困惑。

你能给我一个具体的例子来说明如何正确使用它吗?(用它来修补有问题的代码不算;-)


解决方案 1:

在依赖 getter 和 setter 的语言中,比如 Java,它们不应该也不期望做除了它们所说的之外的任何事情——如果x.getB()除了返回逻辑属性的当前值之外做了其他任何事情b,或者除了进行返回x.setB(2)所需的少量内部工作之外做了其他任何事情,那将是令人惊讶的。x.getB()`2`

但是,语言并没有对这种预期行为做出任何保证get,例如,编译器对名称以或开头的方法主体强制约束set:相反,这取决于常识、社会惯例、“风格指南”和测试。

在具有属性的语言(包括但不限于 Python 的一组语言)中,x.b访问和分配的行为(例如)与 Java 中的 getter 和 setter 方法完全相同:相同的期望,同样缺乏语言强制保证。x.b = 2

属性的第一个好处是语法和可读性。例如,

x.setB(x.getB() + 1)

而不是显而易见的

x.b += 1

向众神呼喊复仇。在支持属性的语言中,绝对没有理由强迫类的用户经历这种拜占庭式样板的旋转,这会影响他们代码的可读性而没有任何好处。

具体来说,在 Python 中,使用属性(或其他描述符)代替 getter 和 setter 还有一个很好的好处:如果你重组你的类,使得底层 setter 和 getter 不再需要,你可以(在不破坏类的已发布 API 的情况下)简单地消除那些方法和依赖于它们的属性,使其成为类b的正常“存储”属性,x而不是通过计算获取和设置的“逻辑”属性。

在 Python 中,直接执行操作(在可行的情况下)而不是通过方法执行操作是一项重要的优化,并且系统地使用属性使您能够在可行的情况下执行此优化(始终直接公开“正常存储的属性”,并且仅公开在访问和/或通过方法和属性设置时需要计算的属性)。

因此,如果您使用 getter 和 setter 而不是属性,除了影响用户代码的可读性之外,还会毫无意义地浪费机器周期(以及这些周期期间传送到计算机的能量;-)。

您反对属性的唯一理由是,例如“外部用户通常不会期望赋值会产生任何副作用”;但您忽略了一个事实,即同一个用户(在诸如 Java 之类的语言中,getter 和 setter 无处不在)也不会期望调用 setter 会产生(可观察到的)“副作用”(对于 getter 就更不会了;-)。这些是合理的期望,作为类作者,您需要尝试满足这些期望——无论您的 setter 和 getter 是直接使用还是通过属性使用,都没有区别。如果您的方法具有重要的可观察到的副作用,请不要将它们命名为getThis, setThat,也不要通过属性使用它们。

属性“隐藏实现”的抱怨完全没有道理:OOP 的大部分内容都是关于实现信息隐藏的——让一个类负责向外界呈现一个逻辑接口,并尽可能地在内部实现它。getter 和 setter 与属性完全一样,是实现此目标的工具。属性只是在这方面做得更好(在支持它们的语言中;-)。

解决方案 2:

这样做的目的是让你避免在真正需要 getter 和 setter 之前编写它们。

因此,首先你要写:

class MyClass(object):
    def __init__(self):
        self.myval = 4

显然你现在可以写myobj.myval = 5

但后来,你决定确实需要一个 setter,因为你想同时做一些巧妙的事情。但你不想更改使用你的类的所有代码 - 所以你将 setter 包装在@property装饰器中,这样一切就都正常了。

解决方案 3:

但隐藏 ab=2 不是一个简单的赋值这一事实,看起来似乎是自找麻烦

但你并没有隐瞒这个事实;这个事实从一开始就不存在。这是 Python——一种高级语言;不是汇编。其中的“简单”语句很少归结为单 CPU 指令。将简单性读入作业就是读出不存在的东西。

当你说 xb = c 时,可能你应该想到的是“无论刚刚发生了什么,xb 现在应该是 c”。

解决方案 4:

一个基本原因其实很简单,就是它看起来更好。它更符合 Python 风格。尤其是对于库来说。something.getValue() 看起来不如 something.value 好看

在 plone(一个相当大的 CMS)中,你曾经使用 document.setTitle() 来做很多事情,比如存储值、再次索引等等。只需执行 document.title = 'something' 就更好了。无论如何,你知道幕后发生了很多事情。

解决方案 5:

这是我的一个旧示例。我包装了一个 C 库,该库具有“void dt_setcharge(int atom_handle, int new_charge)”和“int dt_getcharge(int atom_handle)”等函数。我想在 Python 级别执行“atom.charge = atom.charge + 1”。

“属性”装饰器让这一切变得简单。例如:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

10 年前,当我编写这个包时,我必须使用 getattrsetattr 来实现它,但实现起来更容易出错。

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value

解决方案 6:

你说得对,它只是语法糖。根据你对有问题的代码的定义,它可能没有什么用处。

假设您的应用程序中有一个广泛使用的类 Foo。现在这个应用程序已经变得相当大,而且可以说它是一个非常流行的 Web 应用程序。

您发现 Foo 造成了瓶颈。也许可以向 Foo 添加一些缓存来加快速度。使用属性可以让您做到这一点,而无需更改 Foo 之外的任何代码或测试。

是的,这当然是有问题的代码,但你只需快速修复它就可以节省很多钱。

如果 Foo 位于一个拥有数百或数千名用户的库中,该怎么办?这样,当他们升级到 Foo 的最新版本时,您就不必告诉他们进行昂贵的重构了。

发行说明中有关于 Foo 的项目,而不是段落移植指南。

a.b=2经验丰富的 Python 程序员对以外的内容没有太多期待a.b==2,但他们知道甚至可能并非如此。类内部发生的事情是它自己的事。

解决方案 7:

许多情况下都需要 getter 和 setter,因为它们对代码是透明的,所以非常有用。如果对象 Something 具有属性 height,则可以分配一个值如 Something.height = 10,但是如果 height 具有 getter 和 setter,那么在分配该值时,您可以在过程中做很多事情,比如验证最小值或最大值,比如因为高度改变而触发事件,根据新的高度值自动设置其他值,所有这些都可能发生在分配 Something.height 值时。请记住,您不需要在代码中调用它们,它们会在您读取或写入属性值时自动执行。在某种程度上,它们就像事件过程,当属性 X 更改值时以及当读取属性 X 值时。

解决方案 8:

当您尝试在重构中用委托替换继承时,它很有用。以下是一个示例。Stack是Vector中的子

class Vector:
    def __init__(self, data):
        self.data = data

    @staticmethod
    def get_model_with_dict():
        return Vector([0, 1])


class Stack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()
        self.data = self.model.data


class NewStack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()

    @property
    def data(self):
        return self.model.data

    @data.setter
    def data(self, value):
        self.model.data = value


if __name__ == '__main__':
    c = Stack()
    print(f'init: {c.data}') #init: [0, 1]

    c.data = [0, 1, 2, 3]
    print(f'data in model: {c.model.data} vs data in controller: {c.data}') 
    #data in model: [0, 1] vs data in controller: [0, 1, 2, 3]

    c_n = NewStack()
    c_n.data = [0, 1, 2, 3]
    print(f'data in model: {c_n.model.data} vs data in controller: {c_n.data}') 
    #data in model: [0, 1, 2, 3] vs data in controller: [0, 1, 2, 3]

请注意,如果您直接使用访问而不是属性,self.model.data则不等于self.data,这超出了我们的预期。

您可以将之前的 __name__=='__main__'代码作为库。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   4046  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2770  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   90  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   105  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   93  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用