ChatGPT解决这个技术问题 Extra ChatGPT

使用 @property 与 getter 和 setter

与经典的 getter+setter 相比,@property 表示法有什么优势?在哪些特定情况/情况下,程序员应该选择使用其中一种?

带属性:

class MyClass(object):
    @property
    def my_attr(self):
        return self._my_attr

    @my_attr.setter
    def my_attr(self, value):
        self._my_attr = value

没有属性:

class MyClass(object):
    def get_my_attr(self):
        return self._my_attr

    def set_my_attr(self, value):
        self._my_attr = value

g
gsamaras

更喜欢属性。这就是他们的目的。

原因是所有属性在 Python 中都是公共的。以一个或两个下划线开头的名称只是一个警告,即给定属性是一个实现细节,在未来的代码版本中可能不会保持不变。它不会阻止您实际获取或设置该属性。因此,标准属性访问是访问属性的正常的 Pythonic 方式。

属性的优点是它们在语法上与属性访问相同,因此您可以在不更改客户端代码的情况下从一个更改为另一个。您甚至可以拥有一个使用属性的类版本(例如,用于代码按合同或调试)和一个不用于生产的版本,而无需更改使用它的代码。同时,您不必为所有内容编写 getter 和 setter,以防万一您以后可能需要更好地控制访问。


带有双下划线的属性名由 Python 专门处理;这不仅仅是一个惯例。请参阅docs.python.org/py3k/tutorial/classes.html#private-variables
它们的处理方式不同,但这并不妨碍您访问它们。 PS:公元30 C0
我不同意。结构化代码如何等同于意大利面条代码? Python 是一门美丽的语言。但是更好地支持简单的事情,比如适当的封装和结构化的类。
虽然我在大多数情况下都同意,但要小心将慢速方法隐藏在 @property 装饰器后面。 API 的用户期望属性访问像变量访问一样执行,并且偏离该期望太远可能会使您的 API 使用起来不愉快。
问题不在于直接属性访问与属性。很明显,更多的代码比更少的代码需要更多的时间来执行。
6
6502

在 Python 中,您不会仅仅为了好玩而使用 getter、setter 或属性。您首先只使用属性,然后仅在需要时才最终迁移到属性,而无需使用您的类更改代码。

确实有很多扩展名为 .py 的代码在任何地方都使用 getter 和 setter 以及继承和无意义的类,例如一个简单的元组,但它是来自使用 Python 用 C++ 或 Java 编写的代码。

那不是 Python 代码。


@ 6502,当您说“[...] 无意义的类无处不在,例如一个简单的元组”:类相对于元组的优势在于,类实例提供显式名称来访问其部分,而元组没有.与元组下标相比,名称在可读性和避免错误方面更好,尤其是在将其传递到当前模块之外时。
@Hibou57:我不是说上课没用。但有时一个元组就绰绰有余了。然而问题在于,Java 或 C++ 的出身者别无选择,只能为所有内容创建类,因为在这些语言中使用其他可能性只是烦人。使用 Python 进行 Java/C++ 编程的另一个典型症状是无缘无故地创建抽象类和复杂的类层次结构,在 Python 中,由于鸭子类型,您可以只使用独立的类。
@Hibou57 你也可以使用 namedtuple: doughellmann.com/PyMOTW/collections/namedtuple.html
@JonathonReinhart:它 IS 从 2.6 开始就在标准库中...参见 docs.python.org/2/library/collections.html
如果您想要一个很容易成为元组的类,也可以使用定义了 __slots__ 的类。您也可以定义方法,并且更节省内存。
I
Ignacio Vazquez-Abrams

使用属性可以让您从正常的属性访问开始,然后是 back them up with getters and setters afterwards as necessary


@GregKrsak 看起来很奇怪,因为它是。 “同意成年人的事情”是添加属性之前的 python 模因。这是对抱怨缺少访问修饰符的人们的反应。添加属性后,突然需要封装。抽象基类也发生了同样的事情。 “Python 总是与封装破坏交战。自由就是奴隶制。Lambda 应该只适合一行。”
M
Michael Currie

简短的回答是:属性胜出。总是。

有时需要 getter 和 setter,但即便如此,我也会将它们“隐藏”到外部世界。在 Python 中有很多方法可以做到这一点(getattrsetattr__getattribute__ 等...,但一种非常简洁明了的方法是:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Here's a brief article 介绍了 Python 中的 getter 和 setter 主题。


@BasicWolf - 我认为很明显我在围栏的财产一侧! :) 但是我在我的答案中添加了一个段落来澄清这一点。
提示:“总是”一词暗示作者试图用断言而不是论据来说服你。粗体字体的存在也是如此。 (我的意思是,如果你看到的是 CAPS,那么——哇——它一定是对的。)看,“属性”特性恰好与 Java 不同(由于某种原因,Python 的事实上的克星),因此 Python 的社区群体思维宣称它更好。实际上,属性违反了“显式优于隐式”的规则,但没有人愿意承认。它进入了语言,所以现在它通过重言式参数被声明为“Pythonic”。
没有感情受伤。 :-P 我只是想指出“Pythonic”约定在这种情况下是不一致的:“显式优于隐式”与使用 property 直接冲突。 (它看起来像一个简单的赋值,但它调用了一个函数。)因此,“Pythonic”本质上是一个没有意义的术语,除了重言式定义:“Pythonic 约定是我们定义的东西像 Pythonic 一样。”
现在,拥有一组遵循主题的约定的想法伟大的。如果确实存在这样的一组约定,那么您可以将其用作一组公理来指导您的思考,而不仅仅是要记住的冗长的技巧清单,后者的用处要小得多。公理可用于外推,并帮助您解决尚未遇到的问题。很遗憾,property 功能可能使 Python 公理的想法几乎毫无价值。所以我们只剩下一个清单。
我不同意。在大多数情况下,我更喜欢属性,但是当您想强调设置某些东西除了修改 self 对象之外,显式设置器可能会有所帮助。例如,user.email = "..." 看起来不会引发异常,因为它看起来只是设置一个属性,而 user.set_email("...") 清楚地表明可能存在异常等副作用。
m
martineau

[TL;博士?您可以跳到最后查看代码示例。]

实际上,我更喜欢使用不同的习语,这有点涉及作为一次性使用,但如果您有更复杂的用例,那就太好了。

先说一点背景。

属性很有用,因为它们允许我们以编程方式处理设置和获取值,但仍然允许将属性作为属性访问。我们可以把“gets”变成“computations”(本质上),我们可以把“sets”变成“events”。因此,假设我们有以下类,我使用类似 Java 的 getter 和 setter 对其进行了编码。

class Example(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

您可能想知道为什么我没有在对象的 __init__ 方法中调用 defaultXdefaultY。原因是对于我们的情况,我想假设 someDefaultComputation 方法返回的值会随着时间而变化,比如时间戳,并且无论何时未设置 x(或 y)(出于此目的,例如,“未设置”表示“设置为无”)我想要 x(或 y)默认计算的值。

因此,由于上述多种原因,这是蹩脚的。我将使用属性重写它:

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.

我们得到了什么?我们已经获得了将这些属性称为属性的能力,尽管在幕后我们最终会运行方法。

当然,属性的真正强大之处在于,我们通常希望这些方法除了获取和设置值之外还可以做一些事情(否则使用属性没有意义)。我在我的吸气剂示例中这样做了。我们基本上是在运行一个函数体来在未设置值时获取默认值。这是一种非常常见的模式。

但是我们失去了什么,我们不能做什么?

在我看来,主要的烦恼是,如果你定义了一个 getter(就像我们在这里所做的那样),你还必须定义一个 setter。[1]这是使代码混乱的额外噪音。

另一个烦恼是我们仍然需要初始化 __init__ 中的 xy 值。 (当然,我们可以使用 setattr() 添加它们,但这是更多的额外代码。)

第三,与类似 Java 的示例不同,getter 不能接受其他参数。现在我可以听到你说,好吧,如果它接受参数,它就不是吸气剂!在官方意义上,这是真的。但在实际意义上,我们没有理由不能参数化命名属性(例如 x)并为某些特定参数设置其值。

如果我们能做这样的事情会很好:

e.x[a,b,c] = 10
e.x[d,e,f] = 20

例如。我们能得到的最接近的方法是覆盖赋值以暗示一些特殊的语义:

e.x = [a,b,c,10]
e.x = [d,e,f,30]

当然要确保我们的设置器知道如何提取前三个值作为字典的键并将其值设置为数字或其他东西。

但是即使我们这样做了,我们仍然无法通过属性来支持它,因为我们根本无法将参数传递给 getter,因此无法获取值。所以我们不得不返回所有东西,引入不对称。

Java 风格的 getter/setter 确实让我们能够处理这个问题,但我们又回到了需要 getter/setter 的问题上。

在我看来,我们真正想要的是满足以下要求的东西:

用户只为给定属性定义一种方法,并且可以在此处指示该属性是只读的还是读写的。如果属性可写,则属性无法通过此测试。

用户不需要在函数底层定义额外的变量,因此我们不需要代码中的 __init__ 或 setattr。变量的存在是因为我们创建了这个新样式的属性。

该属性的任何默认代码都在方法体本身中执行。

我们可以将属性设置为属性,并将其作为属性引用。

我们可以参数化属性。

在代码方面,我们想要一种写法:

def x(self, *args):
    return defaultX()

然后能够做到:

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

等等。

我们还想要一种方法来为可参数化属性的特殊情况执行此操作,但仍允许默认分配情况工作。你会在下面看到我是如何解决这个问题的。

现在到重点(耶!重点!)。我为此提出的解决方案如下。

我们创建一个新对象来替换属性的概念。该对象旨在存储为其设置的变量的值,但还维护一个知道如何计算默认值的代码句柄。它的工作是存储集合 value 或在未设置该值时运行 method

我们称它为 UberProperty

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

我假设这里的 method 是一个类方法,valueUberProperty 的值,我添加了 isSet 因为 None 可能是一个真正的值,这使我们能够以一种干净的方式真正声明是“没有价值”。另一种方式是某种哨兵。

这基本上给了我们一个可以做我们想做的事情的对象,但是我们如何真正把它放在我们的类上呢?好吧,属性使用装饰器;为什么我们不能?让我们看看它的外观(从这里开始,我将坚持只使用一个“属性”,x)。

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

当然,这实际上还不起作用。我们必须实现 uberProperty 并确保它同时处理获取和设置。

让我们从获取开始。

我的第一次尝试是简单地创建一个新的 UberProperty 对象并返回它:

def uberProperty(f):
    return UberProperty(f)

当然,我很快发现这不起作用:Python 从不将可调用对象绑定到对象,我需要对象才能调用函数。即使在类中创建装饰器也不起作用,因为虽然现在我们有了类,但我们仍然没有可以使用的对象。

所以我们需要在这里做更多的事情。我们确实知道一个方法只需要表示一次,所以让我们继续保留我们的装饰器,但修改 UberProperty 以仅存储 method 引用:

class UberProperty(object):

    def __init__(self, method):
        self.method = method

它也是不可调用的,所以目前没有任何工作。

我们如何完成图片?好吧,当我们使用新的装饰器创建示例类时,我们最终会得到什么:

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

在这两种情况下,我们都会返回 UberProperty,它当然不是可调用的,所以这没什么用处。

我们需要某种方法,在创建类之后将装饰器创建的 UberProperty 实例动态绑定到该类的对象,然后再将该对象返回给该用户以供使用。嗯,是的,这是一个 __init__ 电话,伙计。

让我们写下我们希望我们的查找结果是第一个。我们将 UberProperty 绑定到一个实例,因此返回的一个显而易见的东西是 BoundUberProperty。这是我们实际维护 x 属性状态的地方。

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

现在我们表示;如何将这些放在对象上?有几种方法,但最容易解释的方法是使用 __init__ 方法进行映射。在调用 __init__ 时,我们的装饰器已经运行,因此只需查看对象的 __dict__ 并更新属性值为 UberProperty 类型的任何属性。

现在,超级属性很酷,我们可能会经常使用它们,因此创建一个为所有子类执行此操作的基类是有意义的。我想你知道基类将被称为什么。

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

我们添加它,将示例更改为从 UberObject 继承,然后...

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

x 修改为:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

我们可以运行一个简单的测试:

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

我们得到了我们想要的输出:

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(哎呀,我工作很晚。)

请注意,我在这里使用了 getValuesetValueclearValue。这是因为我还没有链接到让这些自动返回的方法。

但我认为这是一个暂时停下来的好地方,因为我累了。您还可以看到我们想要的核心功能已经到位;剩下的就是橱窗装饰。重要的可用性橱窗装饰,但这可以等到我有更改来更新帖子。

我将通过解决这些问题来完成下一篇文章中的示例:

我们需要确保 UberObject 的 __init__ 总是被子类调用。所以我们要么强制它在某个地方被调用,要么我们阻止它被实现。我们将看到如何使用元类来做到这一点。

所以我们要么强制它在某个地方被调用,要么我们阻止它被实现。

我们将看到如何使用元类来做到这一点。

我们需要确保处理常见的情况,即有人将函数“别名”为其他东西,例如: class Example(object): @uberProperty def x(self): ... y = x

我们需要 ex 默认返回 exgetValue() 。我们实际上会看到这是模型失败的一个领域。事实证明,我们总是需要使用函数调用来获取值。但是我们可以让它看起来像一个普通的函数调用,而不必使用 exgetValue()。 (如果你还没有解决这个问题,这样做是显而易见的。)

我们实际上会看到这是模型失败的一个领域。

事实证明,我们总是需要使用函数调用来获取值。

但是我们可以让它看起来像一个普通的函数调用,而不必使用 exgetValue()。 (如果你还没有解决这个问题,这样做是显而易见的。)

我们需要支持直接设置 ex,如 ex = 。我们也可以在父类中这样做,但是我们需要更新我们的 __init__ 代码来处理它。

最后,我们将添加参数化属性。我们将如何做到这一点也应该很明显。

这是到目前为止存在的代码:

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] 我可能落后于这种情况是否仍然如此。


是的,这是“tldr”。你能总结一下你在这里想要做什么吗?
@Adam return self.x or self.defaultX() 这是危险代码。 self.x == 0 时会发生什么?
仅供参考,您可以制作它,以便您可以参数化 getter,有点。这将涉及使变量成为自定义类,您已覆盖其中的 __getitem__ 方法。但这会很奇怪,因为您将拥有完全非标准的python。
@KellyThomas 只是试图使示例保持简单。要做到这一点,您需要完全创建和删除 x dict 条目,因为甚至可能已经专门设置了 None 值。但是,是的,您是绝对正确的,这是您在生产用例中需要考虑的事情。
类似 Java 的 getter 允许您进行完全相同的计算,不是吗?
z
zaius

我认为两者都有自己的位置。使用 @property 的一个问题是很难使用标准类机制在子类中扩展 getter 或 setter 的行为。问题是实际的 getter/setter 函数隐藏在属性中。

您实际上可以掌握这些功能,例如

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

您可以像 C.p.fgetC.p.fset 一样访问 getter 和 setter 函数,但不能轻易地使用常规方法继承(例如 super)工具来扩展它们。在深入了解 super 的复杂性之后,您确实可以以这种方式使用 super:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

然而,使用 super() 相当笨拙,因为必须重新定义属性,并且您必须使用稍微违反直觉的 super(cls,cls) 机制来获取 p 的未绑定副本。


H
Hobblin

使用属性对我来说更直观,更适合大多数代码。

比较

o.x = 5
ox = o.x

对比

o.setX(5)
ox = o.getX()

对我来说很明显,这更容易阅读。属性还允许更容易地使用私有变量。


佚名

在大多数情况下,我宁愿两者都不使用。属性的问题在于它们使类变得不那么透明。特别是,如果您要从 setter 引发异常,这将是一个问题。例如,如果您有一个 Account.email 属性:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

那么该类的用户不会期望为属性分配值会导致异常:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

结果,异常可能未处理,并且在调用链中传播得太高而无法正确处理,或者导致向程序用户呈现非常无用的回溯(遗憾的是,这在 python 和 java 世界中太常见了)。

我也会避免使用 getter 和 setter:

因为提前为所有属性定义它们非常耗时,

使代码量不必要地更长,这使得理解和维护代码更加困难,

如果您仅根据需要为属性定义它们,则类的接口会发生变化,从而损害该类的所有用户

我更喜欢在定义明确的地方(例如在验证方法中)执行复杂的逻辑,而不是属性和 getter/setter:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

或类似的 Account.save 方法。

请注意,我并不是说没有属性有用的情况,只是如果你可以让你的类足够简单和透明以至于你不需要它们,你可能会更好。


@user2239734 我认为您误解了属性的概念。虽然您可以在设置属性时验证值,但没有必要这样做。您可以在一个类中同时拥有属性和 validate() 方法。当您在简单的 obj.x = y 赋值后面有复杂的逻辑时,只需使用属性,这取决于逻辑是什么。
p
pistache

我觉得属性是关于让您仅在您真正需要它们时才获得编写 getter 和 setter 的开销。

Java 编程文化强烈建议永远不要访问属性,而是通过 getter 和 setter,并且只使用那些真正需要的。总是写这些明显的代码有点冗长,请注意,70% 的时间它们永远不会被一些非平凡的逻辑所取代。

在 Python 中,人们实际上关心这种开销,因此您可以采用以下做法:

如果不需要的话,一开始不要使用 getter 和 setter

使用 @property 来实现它们,而无需更改其余代码的语法。


“请注意,在 70% 的情况下,它们永远不会被一些重要的逻辑所取代。” - 这是一个相当具体的数字,它来自某个地方,还是你打算把它当作一个“绝大多数”的东西(我不是在开玩笑,如果有一项研究可以量化这个数字,我会真的有兴趣阅读它)
哦不抱歉。听起来我确实有一些研究可以备份这个数字,但我的意思只是“大部分时间”。
并不是人们关心开销,而是在 Python 中,您可以在不更改客户端代码的情况下从直接访问更改为访问器方法,因此一开始直接公开属性不会有任何损失。
C
Community

我很惊讶没有人提到属性是描述符类的绑定方法,Adam DonohueNeilenMarais 在他们的帖子中得到了这个想法——getter 和 setter 是函数,可以用来:

证实

更改数据

鸭子类型(将类型强制转换为另一种类型)

这提供了一种隐藏实现细节和代码杂乱无章的智能方法,如正则表达式、类型转换、try .. 除了块、断言或计算值。

通常,对对象执行 CRUD 可能通常相当普通,但考虑将持久保存到关系数据库的数据示例。 ORM 可以在绑定到 fget、fset、fdel 的方法中隐藏特定 SQL 语言的实现细节,该方法定义在一个属性类中,该属性类将管理在 OO 代码中如此丑陋的可怕 if .. elif .. else 阶梯——暴露简单和优雅的 self.variable = something 并为开发人员使用 ORM 消除细节。

如果人们仅将属性视为束缚和纪律语言(即 Java)的一些沉闷的遗迹,那么他们就忽略了描述符的意义。


C
Cyker

@property 和传统的 getter 和 setter 都有其优势。这取决于您的用例。

@property 的优点

您不必在更改数据访问的实现时更改接口。当您的项目较小时,您可能希望使用直接属性访问来访问类成员。例如,假设您有一个 Foo 类型的对象 foo,它有一个成员 num。然后你可以简单地用 num = foo.num 得到这个成员。随着项目的增长,您可能会觉得需要对简单的属性访问进行一些检查或调试。然后你可以在类中使用@property 来做到这一点。数据访问接口保持不变,因此无需修改客户端代码。引用自 PEP-8:对于简单的公共数据属性,最好只公开属性名称,而不需要复杂的访问器/修改器方法。请记住,如果您发现简单的数据属性需要增加功能行为,Python 提供了一条通往未来增强的简单途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法之后。

在 Python 中使用 @property 进行数据访问被认为是 Pythonic:它可以增强您作为 Python(而非 Java)程序员的自我认同。如果你的面试官认为 Java 风格的 getter 和 setter 是反模式,它可以帮助你的工作面试。

它可以增强您作为 Python(而非 Java)程序员的自我认同感。

如果你的面试官认为 Java 风格的 getter 和 setter 是反模式,它可以帮助你的工作面试。

传统 getter 和 setter 的优点

传统的 getter 和 setter 允许比简单的属性访问更复杂的数据访问。例如,当您设置一个类成员时,有时您需要一个标志来指示您希望在哪里强制执行此操作,即使某些东西看起来并不完美。虽然如何增加像 foo.num = num 这样的直接成员访问并不明显,但您可以使用额外的 force 参数轻松增加传统的 setter:def Foo: def set_num(self, num, force=False): ...

传统的 getter 和 setter 明确表示类成员访问是通过方法进行的。这意味着:您得到的结果可能与该类中确切存储的内容不同。即使访问看起来像一个简单的属性访问,性能也会有很大的不同。除非您的班级用户期望隐藏在每个属性访问语句后面的 @property,否则明确这些内容可以帮助减少班级用户的意外。

您得到的结果可能与该类中确切存储的内容不同。

即使访问看起来像一个简单的属性访问,性能也会有很大的不同。

正如@NeilenMarais 在这篇文章中提到的,在子类中扩展传统的getter 和setter 比扩展属性更容易。

传统的 getter 和 setter 已经在不同的语言中广泛使用了很长时间。如果您的团队中有来自不同背景的人,他们看起来比@property 更熟悉。此外,随着项目的增长,如果您可能需要从 Python 迁移到另一种没有 @property 的语言,使用传统的 getter 和 setter 将使迁移更加顺畅。

注意事项

@property 和传统的 getter 和 setter 都不会使类成员私有,即使您在其名称前使用双下划线: class Foo: def __init__(self): self.__num = 0 @property def num(self): return self.__num @num.setter def num(self, num): self.__num = num def get_num(self): return self.__num def set_num(self, num): self.__num = num foo = Foo() print(foo.num ) # 输出:0 print(foo.get_num()) # 输出:0 print(foo._Foo__num) # 输出:0


o
otognan

在复杂的项目中,我更喜欢使用带有显式 setter 函数的只读属性(或 getter):

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

在长期存在的项目中,调试和重构比编写代码本身需要更多的时间。使用 @property.setter 有几个缺点,使调试更加困难:

1) python 允许为现有对象创建新属性。这使得以下印刷错误很难追踪:

my_object.my_atttr = 4.

如果您的对象是一个复杂的算法,那么您将花费相当长的时间试图找出它不收敛的原因(注意上面一行中的额外“t”)

2) setter 有时可能会演变成一种复杂而缓慢的方法(例如访问数据库)。另一个开发人员很难弄清楚为什么下面的函数很慢。他可能会在分析 do_something() 方法上花费大量时间,而 my_object.my_attr = 4. 实际上是导致速度变慢的原因:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()

V
Vlad Bezden

这是 "Effective Python: 90 Specific Ways to Write Better Python" 的摘录(很棒的书。我强烈推荐它)。

需要记住的事情 ✦ 使用简单的公共属性定义新的类接口,避免定义 setter 和 getter 方法。 ✦ 如有必要,使用@property 定义访问对象属性时的特殊行为。 ✦ 遵循最小意外规则并避免在您的 @property 方法中产生奇怪的副作用。 ✦ 确保@property 方法快速;对于缓慢或复杂的工作——尤其是涉及 I/O 或引起副作用的工作——请改用普通方法。 @property 的一个高级但常见的用途是将曾经简单的数字属性转换为动态计算。这非常有用,因为它允许您迁移类的所有现有用法以具有新行为,而无需重写任何调用站点(如果存在您无法控制的调用代码,这一点尤其重要)。 @property 还为随着时间的推移改进界面提供了重要的权宜之计。我特别喜欢@property,因为它可以让您随着时间的推移逐步向更好的数据模型迈进。 @property 是一种工具,可帮助您解决在实际代码中遇到的问题。不要过度使用它。当您发现自己反复扩展 @property 方法时,可能是时候重构您的类,而不是进一步铺平代码的糟糕设计。 ✦ 使用@property 为现有实例属性赋予新功能。 ✦ 使用@property 在更好的数据模型方面取得进展。 ✦ 当你发现自己过度使用@property 时,考虑重构一个类和所有调用站点。


如果您将类用作带有点符号的字典,或者更好地考虑重构。如果您的成员与您的方法没有紧密耦合,为什么您首先要使用一个类。