使用@property与getter和setter

时间:2011-07-07 22:42:26

标签: python properties getter-setter

这是一个纯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程序,你会使用哪种方法?为什么?

13 个答案:

答案 0 :(得分:574)

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

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

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

答案 1 :(得分:144)

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

确实有很多扩展名为.py的代码,它们使用getter和setter以及继承和无意义的类,例如一个简单的元组会做,但它是使用Python用C ++或Java编写的人的代码。

那不是Python代码。

答案 2 :(得分:114)

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

答案 3 :(得分:68)

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

有时需要吸气剂和制定者,但即便如此,我也会将它们“隐藏”到外面的世界。在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主题。

答案 4 :(得分:58)

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

我实际上更喜欢使用不同的习惯用法,这有点像一次性使用,但如果你有一个更复杂的用例,那就很好。

首先是背景。

属性很有用,因为它们允许我们以编程方式处理设置和获取值,但仍允许将属性作为属性进行访问。我们可以转向“获取”#39;进入'计算' (基本上)我们可以转而设置'进入'事件'。所以,我们假设我们有以下课程,我已经用类似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方法返回随时间变化的值,例如时间戳,以及何时未设置x(或y)(其中) ,就本例而言,"未设置"表示"设置为无")我想要x的值(或{{1} }' s)默认计算。

因为上面描述的一些原因,这是蹩脚的。我将使用属性重写它:

y

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

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

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

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

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

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

如果我们可以做类似的事情,那就太好了。

x
例如,

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

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

当然要确保我们的setter知道如何将前三个值提取为字典的键并将其值设置为数字等。

但即使我们这样做,我们仍然无法通过属性来支持它,因为我们无法获取值,因为我们无法将参数传递给getter。所以我们必须归还所有东西,引入不对称。

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

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

  • 用户只为给定属性定义一种方法,并可在此处指明 属性是只读还是读写。属性未通过此测试 如果属性可写。

  • 用户无需定义功能底层的额外变量,因此我们不需要代码中的e.x = [a,b,c,10] e.x = [d,e,f,30] __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 是一种类方法,methodvalue的值,我添加了UberProperty,因为isSet可能是真正的价值,这使我们有一个干净的方式来宣布真的没有价值"。另一种方式是某种哨兵。

这基本上为我们提供了一个可以做我们想要的对象的对象,但是我们如何将它真正地放在我们的课堂上呢?那么,属性使用装饰器;为什么我们不能?让我们看一下它的外观(从这里开始,我将坚持只使用一个属性',None)。

x

当然,这还没有真正奏效。我们必须实施class Example(object): @uberProperty def x(self): return defaultX() 和 确保它处理获取和设置。

让我们从获取开始。

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

uberProperty

当然,我很快发现,这种做法很有效:Python永远不会将可调用绑定到对象,我需要该对象才能调用该函数。即使在课堂上创建装饰工作也没有用,因为虽然现在我们有了课程,但我们仍然没有一个可以使用的对象。

所以我们需要在这里做更多的事情。我们知道一个方法只需要一次表示,所以让我们继续保留装饰器,但修改def uberProperty(f): return UberProperty(f) 只存储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__绑定到一个实例,因此返回的一个显而易见的事情是BoundUberProperty。这是我们实际维护UberProperty属性状态的地方。

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 getValuesetValue。这是因为我还没有将这些自动返回的方式联系起来。

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

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

  • 我们需要确保子类始终调用UberObject的clearValue

    • 所以我们要么强迫它在某个地方被召唤,要么我们阻止它被实施。
    • 我们将看到如何使用元类进行此操作。
  • 我们需要确保我们处理某人&#39;别名&#39; 对其他东西的函数,例如:

    __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__

[1]关于是否仍然如此,我可能会落后。

答案 5 :(得分:24)

我认为两者都有自己的位置。使用@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

您可以访问getter和setter函数C.p.fgetC.p.fset,但您无法轻松使用常规方法继承(例如超级)工具来扩展它们。在深入研究超级复杂之后,可以确实以这种方式使用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()非常笨重,因为必须重新定义属性,并且必须使用稍微反直觉的超级(cls,cls)机制来获取p的未绑定副本。

答案 6 :(得分:20)

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

比较

o.x = 5
ox = o.x

VS

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

对我来说非常明显,更容易阅读。此外,属性允许私有变量更容易。

答案 7 :(得分:12)

我更愿意在大多数情况下都不使用。属性的问题在于它们使类不那么透明。特别是,如果您要从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方法。

请注意,我并不是说没有属性有用的情况,只是如果你可以使你的课程简单透明到不需要它们,你可能会更好。

答案 8 :(得分:9)

我觉得属性就是让你只有在实际需要时才能获得编写getter和setter的开销。

Java编程文化强烈建议永远不要授予对属性的访问权限,而是通过getter和setter,只有那些实际需要的东西。 总是编写这些明显的代码片段有点冗长,并注意到70%的时间它们永远不会被一些非平凡的逻辑所取代。

在Python中,人们实际上关心这种开销,以便您可以接受以下实践:

  • 如果不需要,请不要使用getter和setter
  • 使用@property来实现它们,而无需更改其余代码的语法。

答案 9 :(得分:9)

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

  • 验证
  • 更改数据
  • 鸭子类型(强制类型为另一种类型)

这提供了一种智能方式来隐藏实现细节和代码,如正则表达式,类型转换,尝试..除了块,断言或计算值。

一般来说,对某个对象进行CRUD通常可能相当普通,但请考虑将持久保存到关系数据库的数据示例。 ORM可以在绑定到fget,fset,fdel的方法中隐藏特定SQL vernaculars的实现细节,这些方法将在一个属性类中定义,该属性类将管理在OO代码中如此丑陋的糟糕的if .. elif .. else梯形图 - 公开简单而优雅的self.variable = something,并使用 ORM来消除开发人员的详细信息。

如果人们认为属性仅仅是一种束缚和纪律语言(即Java)的沉闷残余,那么他们就会忽略描述符。

答案 10 :(得分:5)

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

答案 11 :(得分:3)

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

@property

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

    引自PEP-8

      

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

  • 在Python中使用@property进行数据访问被视为 Pythonic

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

    • 如果您的面试官认为Java风格的getter和setter是anti-patterns,则可以帮助您进行工作面试。

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

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

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

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

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

    除非您的班级用户希望每个属性访问语句后面都隐藏一个@property,否则将其明确显示可以帮助最大程度地减少您的班级用户的惊讶。

  • @NeilenMaraisthis post所述,在子类中扩展传统的getter和setter比扩展属性更容易。

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

注意事项

  • @property和传统的getters和setters都不将类成员设为私有,即使您在其名称前使用双下划线也是如此:

    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
    

答案 12 :(得分:3)

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

要记住的事情

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

✦使用@property定义属性为 如有必要,可以访问您的对象。

✦遵循最少惊奇的规则,避免奇怪的副作用 @property方法。

✦确保@property方法快速;用于慢速或复杂 工作-特别是涉及I / O或引起副作用的工作-使用正常 方法代替。

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

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

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

✦逐步取得更好的数据 使用@property建立模型。

✦考虑重构一个类和所有调用 您发现自己过度使用@property的网站。