使用@property与使用getter和setter方法的区别

796

@property标记与经典的getter+setter相比有哪些优势?在哪些特定情况下程序员应选择使用其中之一?

使用属性:

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
13个回答

9

在复杂项目中,我更喜欢使用只读属性(或者getter)和明确的setter函数:

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

"是一个重要的观点,也是我通常避免在更复杂的代码中使用setter的主要原因。" - 101

8

无论是 @property 还是传统的getter和setter都有各自的优势。这取决于你的用例。

@property的优点

  • 更改数据访问实现时,不需要更改接口。当项目很小时,您可能想使用直接属性访问来访问类成员。例如,假设您有一个类型为Foo的对象foo,它具有一个成员num。然后,您可以使用num = foo.num简单地获取此成员。随着项目的增长,您可能会感到需要对简单属性访问进行一些检查或调试。然后,您可以在类内部使用一个@property来实现。数据访问接口保持不变,因此无需修改客户端代码。

    引自PEP-8:

    对于简单的公共数据属性,最好仅公开属性名称,而无需复杂的访问器/变异器方法。请记住,Python提供了一条通向未来增强的简便途径,如果您发现简单的数据属性需要增加功能行为。在这种情况下,可以使用属性隐藏功能实现。

  • 在Python中使用@property进行数据访问被认为是符合Python习惯的:

    • 它可以加强您的自我认知,作为一名Python(而不是Java)程序员。

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

传统getter和setter的优点

  • Traditional getters and setters allow for more complicated data access than simple attribute access. For example, when you are setting a class member, sometimes you need a flag indicating where you would like to force this operation even if something doesn't look perfect. While it is not obvious how to augment a direct member access like foo.num = num, You can easily augment your traditional setter with an additional force parameter:

    def Foo:
        def set_num(self, num, force=False):
            ...
    
  • Traditional getters and setters make it explicit that a class member access is through a method. This means:

    • What you get as the result may not be the same as what is exactly stored within that class.

    • Even if the access looks like a simple attribute access, the performance can vary greatly from that.

    Unless your class users expect a @property hiding behind every attribute access statement, making such things explicit can help minimize your class users surprises.

  • As mentioned by @NeilenMarais and in this post, extending traditional getters and setters in subclasses is easier than extending properties.

  • Traditional getters and setters have been widely used for a long time in different languages. If you have people from different backgrounds in your team, they look more familiar than @property. Also, as your project grows, if you may need to migrate from Python to another language that doesn't have @property, using traditional getters and setters would make the migration smoother.

注意事项

  • Neither @property nor traditional getters and setters makes the class member private, even if you use double underscore before its name:

    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)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0
    

7

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

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

1
或者更好的方法是,如果您正在使用类作为具有点符号的字典,请考虑重构。如果您的成员与您的方法没有紧密耦合,那么您为什么要首先使用类呢? - Tarynn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接