使用@property与getter和setter


727

这是一个纯Python特定的设计问题:

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

    def set_my_attr(self, value):
        ...

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

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

Python让我们可以以任何一种方式来做。如果要设计Python程序,将使用哪种方法,为什么?

Answers:


613

首选属性。这就是他们在那里的目的。

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

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


90
带双下划线的属性名称由Python专门处理;这不仅仅是一个约定。参见docs.python.org/py3k/tutorial/classes.html#private-variables
6502

63
它们的处理方式不同,但这并不能阻止您访问它们。PS:AD 30 C0
kindall 2011年

4
并且因为“ @”字符在python代码中很丑陋,并且取消引用@decorators会产生类似于意大利面条代码的感觉。
Berry Tsakala

18
我不同意 结构化代码如何等同于意大利面条代码?Python是一门美丽的语言。但是,如果能够更好地支持简单的事情(例如适当的封装和结构化类),那就更好了。

69
尽管我在大多数情况下都同意,但要小心将@方法属性装饰器后面的慢方法隐藏起来。API的用户期望属性访问的性能类似于变量访问,而偏离该期望的距离太长会使您的API使用起来不愉快。
defrex

153

在Python中,您不会仅仅为了获得乐趣而使用getter,setter或属性。首先,您只使用属性,然后,仅在需要时才最终将其迁移到属性,而不必使用类更改代码。

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

那不是Python代码。


46
@ 6502,当您说“ […]无意义的类到处都是简单的元组时”:类优于元组的优点是,类实例提供了显式名称来访问其各部分,而元组则没有。与元组下标相比,名称在可读性和避免错误方面更胜一筹,尤其是在将其传递到当前模块之外时。
Hibou57 2012年

15
@ Hibou57:我并不是说课堂没有用。但是有时候一个元组是绰绰有余的。但是问题是,来自Java或C ++的人别无选择,只能为所有内容创建类,因为在这些语言中使用其他可能性很烦人。使用Python进行Java / C ++编程的另一个典型症状是无缘无故地创建抽象类和复杂的类层次结构,而在Python中,由于鸭子类型的原因,您只能使用独立的类。
6502

39
@ Hibou57表示,您也可以使用namedtuple
hugo24年

5
@JonathonReinhart:它IS在自2.6标准库...看到 docs.python.org/2/library/collections.html
6502

1
当“最终根据需要迁移到属性”时,很可能确实使用类破坏了代码。属性通常会引入限制-没想到这些限制的任何代码都会在您引入限制后立即失效。
yaccob

118

使用属性可以使您从普通的属性访问开始,然后在必要时使用getter和setter备份它们


3
@GregKrsak似乎很奇怪,因为是。“同意成人的事情”是添加属性之前的python meme。这是对那些抱怨缺少访问修饰符的人们的回应。当添加特性时,突然封装变得合乎需要。抽象基类也发生了同样的事情。“ Python一直在与封装破坏打交道。自由是奴隶制。Lambda仅应放在一行上。”
johncip '16

71

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

有时有时需要吸气剂和吸气剂,但即使那样,我仍会将其“隐藏”到外界。有很多方法在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)

这是一篇简短的文章,介绍Python中的getter和setter主题。


1
@BasicWolf-我以为我很清楚我在篱笆的那一边!:)但我在回答中添加了一个段落来澄清这一点。
mac

9
提示:“始终”一词表示作者试图用断言而不是论点说服您。粗体字体的存在也是如此。(我的意思是,如果您看到的是CAPS,那么-哇-一定是正确的。)您看,“属性”功能恰好不同于Java(由于某种原因Python的事实克星),因此Python的社区团体认为宣布它会更好。实际上,属性违反了“显式优于隐式”规则,但没人愿意承认。它成为了语言,因此现在通过重言式参数将其声明为“ Pythonic”。
Stuart Berg

3
没有感觉受到伤害。:-P我只是想指出在这种情况下,“ Pythonic”约定是不一致的:“显式比隐式更好”与使用property。直接冲突。(它看起来像一个简单的赋值,但它调用了一个函数。)因此,“ Pythonic”本质上是没有意义的术语,除非是重言式定义:“ Pythonic约定是我们定义为Pythonic的东西。”
Stuart Berg

1
现在,拥有遵循主题的一系列约定的想法很棒。如果确实存在这样的约定,那么您可以将其用作一组指导您思考的公理,而不仅仅是冗长的要记住的窍门清单,这显然用处不大。公理可用于推断,并帮助您解决尚未见过的问题。令人遗憾的是,该property功能可能使Pythonic公理的概念几乎一文不值。因此,我们剩下的只是一个清单。
斯图尔特·伯格2015年

1
我不同意 在大多数情况下,我更喜欢使用属性,但是当您要强调设置某些东西而不是修改self对象时,会产生副作用,因此显式设置器可能会有所帮助。例如,user.email = "..."看起来好像不会引发异常,因为它看起来像只是在设置属性,而user.set_email("...")明确表明可能会产生类似异常的副作用。
bluenote10

65

[ TL; DR? 您可以跳到最后一个代码示例。]

实际上,我更喜欢使用另一种习惯用法,这是一个单独的习惯,但是如果您有一个更复杂的用例,那就很好了。

首先有一些背景知识。

属性是有用的,因为它们允许我们以编程方式处理设置和获取值,但仍允许将属性作为属性进行访问。我们可以(基本上)将“获取”转换为“计算”,并且可以将“设置”转换为“事件”。假设我们有以下类,我已经使用类似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()

你可能会奇怪,为什么我没有打电话defaultX,并defaultY在对象的__init__方法。原因是,对于我们的情况,我想假设这些someDefaultComputation方法返回的值会随时间变化,例如时间戳记,以及未设置(xy)的时间(在本示例中,“未设置”的意思是“已设置”到无“)我想的值x的(或y的)默认计算。

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

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] 那是使代码混乱的额外噪音。

另一个烦人的地方是,我们仍然必须初始化中的xy__init__。(当然,我们可以使用添加它们,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这里是一个类方法,value是的值UberProperty,并且我添加了它,isSet因为它None可能是真实值,这使我们可以采用一种干净的方式来声明确实没有“值”。另一种方式是某种形式的哨兵。

基本上,这给了我们一个可以做我们想要的对象的对象,但是实际上如何将它放在我们的类上呢?好吧,属性使用装饰器;我们为什么不能呢?让我们看看它的外观(从这里开始,我将坚持只使用一个'attribute',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__,老兄。

让我们写下我们希望我们的搜索结果为第一的内容。我们将an绑定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__并更新属性值是type的所有属性UberProperty

现在,uber-properties很酷,我们可能会想大量使用它们,因此仅创建一个对所有子类都执行此操作的基类是有意义的。我认为您知道将要调用的基类。

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

(老兄,我迟到了。)

请注意,我已经使用getValuesetValue以及clearValue在这里。这是因为我还没有链接自动返回这些值的方法。

但是我认为这是一个停止的好地方,因为我累了。您还可以看到我们所需的核心功能已经到位。其余的是橱窗装饰。重要的可用性窗口修饰,但是可以等到我进行更改以更新帖子。

我将通过解决以下问题来完成下一个示例中的示例:

  • 我们需要确保UberObject __init__始终由子类调用。

    • 因此,我们要么强制在某个地方调用它,要么阻止其实现。
    • 我们将看到如何使用元类来做到这一点。
  • 我们需要确保能够处理某些人将函数“别名”为其他事物的常见情况,例如:

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
  • 我们需要默认e.x返回e.x.getValue()

    • 我们实际上将看到的是模型失败的领域。
    • 事实证明,我们始终需要使用函数调用来获取值。
    • 但是我们可以使其看起来像常规函数调用,而不必使用e.x.getValue()。(如果您还没有解决问题,那么这样做很明显。)
  • 我们需要支持设置e.x directly,如中所示e.x = <newvalue>。我们也可以在父类中执行此操作,但是我们需要更新__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]对于是否仍然如此,我可能会落后。


53
是的,这是'tldr'。您能在这里总结一下您要做什么吗?
poolie 2013年

9
@Adam return self.x or self.defaultX()这是危险代码。什么时候会发生什么self.x == 0
凯利·托马斯

仅供参考,您可以做到,这样就可以对吸气剂进行参数化。这将涉及使变量成为自定义类,而您已经重写了该__getitem__方法。不过这会很奇怪,因为您将拥有完全非标准的python。

2
@KellyThomas只是试图使示例简单。为此,您需要完全创建和删除x dict条目,因为甚至可能已经专门设置了None值。但是,是的,您绝对正确,在生产用例中需要考虑这一点。
Adam Donahue 2014年

类似Java的getter,您可以执行完全相同的计算,不是吗?
2014年

26

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

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

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

您可以访问getter和setter功能C.p.fgetC.p.fset,但你不能轻易使用正常方法继承(如超)设备来扩展他们。在深入研究了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的未绑定副本。


20

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

比较中

o.x = 5
ox = o.x

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

在我看来,这很容易阅读。属性也使私有变量变得更加容易。


12

在大多数情况下,我都不想使用两者。属性的问题在于它们使类不那么透明。特别是,如果您要向设置员提出例外情况,这将成为一个问题。例如,如果您具有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:

  • 因为预先为所有属性定义它们非常耗时,
  • 不必要地增加了代码量,使理解和维护代码更加困难,
  • 如果仅根据需要为属性定义它们,则类的界面将发生变化,从而损害该类的所有用户

我更喜欢在定义明确的位置(例如在验证方法中)执行复杂的逻辑,而不是使用属性和获取/设置方法:

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

或类似的Account.save方法。

请注意,我并不是想说在任何情况下属性都是有用的,只是如果您可以使类足够简单和透明以至于不需要它们,则可能会更好。


3
@ user2239734我认为您误解了属性的概念。尽管您可以在设置属性时验证值,但无需这样做。您可以validate()在类中同时拥有属性和方法。当您在简单obj.x = y分配后具有复杂逻辑时,可以简单地使用属性,并且取决于逻辑的含义。
Zaur Nasibov 2013年

12

我觉得属性是关于让您仅在实际需要时才编写getter和setter的开销。

Java编程文化强烈建议永远不要访问属性,而应通过getter和setter以及仅实际需要的属性进行访问。总是编写这些显而易见的代码片段有点冗长,请注意,有70%的时间从未将它们替换为一些非平凡的逻辑。

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

  • 如果不需要,首先不要使用getter和setter。
  • 使用@property予以实施而又不改变你的代码的其余部分的语法。

1
“请注意,有70%的时间从未将它们替换为某些非平凡的逻辑。” -这是一个相当具体的数字,是从某个地方来的,还是您打算将其用作手摇的“绝大多数”之类的东西(我并不是在幻想,如果有一项研究可以量化该数字,我会真正有兴趣阅读的人)
Adam Parkin 2012年

1
哦,对不起。听起来确实像我在研究备份此数字,但我只是将其表示为“大多数时间”。
fulmicoton 2012年

7
并不是人们在乎开销,而是在Python中您可以从直接访问更改为访问器方法,而无需更改客户端代码,因此,一开始直接公开属性就不会有任何损失。
尼尔·G

10

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

  • 验证
  • 修改数据
  • 鸭子类型(强制类型为其他类型)

这提供了一种隐藏实现细节和代码残废(例如正则表达式,类型强制转换,尝试..除了块,断言或计算值之外)的聪明方法。

通常,对一个对象执行CRUD通常可能很平凡,但请考虑将数据保存到关系数据库的示例。ORM可以在绑定到属性类中定义的fget,fset,fdel的方法中隐藏特定SQL语言的实现细节,该类将管理糟糕的OO代码中的.. elif .. else阶梯,从而暴露出简单易懂的优雅,self.variable = something并避免使用 ORM 为开发人员提供细节。

如果仅将属性视为束缚和纪律语言(即Java)的沉闷痕迹,那么他们就错过了描述符的要点。


6

在复杂的项目中,我更喜欢使用带有显式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()

5

无论@property与传统的getter和setter方法各有优点。这取决于您的用例。

优点 @property

  • 您无需在更改数据访问的实现时更改接口。当您的项目较小时,您可能希望使用直接属性访问来访问类成员。例如,假设您有一个foo类型为object的对象Foo,该对象具有一个member num。然后,您只需使用即可获得此成员num = foo.num。随着项目的发展,您可能会觉得需要对简单的属性访问进行一些检查或调试。然后,您可以@property 课程使用。数据访问接口保持不变,因此无需修改客户端代码。

    引用自PEP-8

    对于简单的公共数据属性,最好仅公开属性名称,而不使用复杂的访问器/更改器方法。请记住,如果您发现简单的数据属性需要增强功能行为,则Python为将来的增强提供了简便的方法。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法之后。

  • 使用@property在Python中的数据访问被认为是Python的

    • 它可以增强您作为Python(不是Java)程序员的自我认同。

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

传统吸气剂和吸气剂的优点

  • 与简单的属性访问相比,传统的getter和setter允许更复杂的数据访问。例如,当您设置一个类成员时,有时您需要一个标志来指示您希望在哪里强制执行此操作,即使某些情况看起来并不完美。虽然如何扩展直接成员访问权限(如)并不明显foo.num = num,但您可以通过附加force参数轻松扩展传统的setter :

    def Foo:
        def set_num(self, num, force=False):
            ...
  • 传统的getter和setter 明确表明,类成员访问是通过方法进行的。这表示:

    • 结果所得到的结果可能与该类中确切存储的结果不同。

    • 即使访问看起来像简单的属性访问,其性能也可能相差很大。

    除非您的班级用户希望@property在每个属性访问语句后都隐藏起来,否则将其明确表示可以最大程度地减少您的班级用户的意外情况。

  • @NeilenMarais本文所提到的,在子类中扩展传统的getter和setters比扩展属性更容易。

  • 长期以来,传统的吸气剂和吸气剂已以多种语言广泛使用。如果您的团队中有来自不同背景的人员,那么他们看起来比熟悉@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)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0

2

这是“有效的Python:编写更好的Python的90种特定方法”的摘录(很棒的书。我强烈推荐它)。

要记住的事情

using使用简单的公共属性定义新的类接口,并避免定义setter和getter方法。

necessary必要时,使用@property定义在对象上访问属性时的特殊行为。

@在您的@property方法中遵循最小惊喜规则,并避免出现奇怪的副作用。

✦确保@property方法是快速的;对于缓慢或复杂的工作(尤其是涉及I / O或引起副作用的工作),请改用常规方法。

@property的一种高级但通用的用法是将曾经简单的数字属性转换为即时计算。这非常有用,因为它使您可以将类的所有现有用法迁移到新行为,而无需重写任何调用站点(如果您无法控制调用代码,这尤其重要)。@property还提供了一个重要的权宜之计,用于随着时间的推移改进接口。

我特别喜欢@property,因为它可以让您随着时间的推移逐步向更好的数据模型发展。
@property是一个工具,可帮助您解决在实际代码中遇到的问题。不要过度使用它。当您发现自己反复扩展@property方法时,可能是时候重构您的类,而不是进一步讨论代码的不良设计了。

✦使用@property为现有实例属性赋予新功能。

using通过使用@property,逐步朝着更好的数据模型发展。

find当您过多地使用@property时,请考虑重构一个类和所有调用站点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.