Python @property与getters和setter

这是一个纯Python特有的devise问题:

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可以让我们做到这一点。 如果你要devise一个Python程序,你会使用哪种方法,为什么?

喜欢属性 。 这是他们在那里。

原因是所有的属性在Python中是公共的。 用下划线或两个名字开始名称只是一个警告,即给定的属性是一个实现细节,可能在未来的代码版本中保持不变。 这并不妨碍你实际获取或设置该属性。 因此,标准属性访问是正常的,Pythonic访问属性的方式。

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

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

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

这不是Python代码。

使用属性可以让你从普通属性访问开始, 然后根据需要用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) 

这里有一个简短的文章 ,介绍了Python中getter和setter的主题。

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

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

先有一点背景

属性是有用的,因为它们允许我们以编程方式处理设置和获取值,但仍然允许属性作为属性被访问。 我们可以把'gets'变成'计算'(本质上),我们可以把'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方法中调用defaultX和defaultY。 原因是,对于我们的情况,我想假设someDefaultComputation方法返回随时间变化的值,比如时间戳,并且每当x(或y)没有被设置时(就本例而言,“未设置”意思是“设为None”)我想要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. 

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

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

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

在我看来,主要的烦恼是如果你定义了一个getter(就像我们在这里所做的那样),你还必须定义一个setter。 这是杂乱的代码。

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

第三,与Java类似的例子不同,getter不能接受其他参数。 现在我可以听到你说了,好吧,如果它正在参数,它不是一个getter! 从官方的angular度来说,这是事实。 但从实际意义上说,我们没有理由不能参数化一个命名的属性 – 比如x – 并为某些特定的参数设置它的值。

如果我们能做到这样的话,那会很好。

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

例如。 我们可以得到的最接近的是重写赋值来暗示一些特殊的语义:

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

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

但是,即使我们这样做,我们仍然不能支持它的属性,因为没有办法得到的价值,因为我们不能传递所有的参数给吸气剂。 所以我们必须把所有东西都归还,引入不对称。

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

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

  • 用户只为给定属性定义一个方法,并可以在那里指示该属性是只读还是读写。 如果属性是可写的,属性将会失败。

  • 用户不需要为函数定义一个额外的variables,所以我们不需要代码中的init或setattr。 这个variables只是由于我们创build了这个新样式属性而存在的。

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

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

  • 我们可以参数化属性。

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

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

并能够做到:

 print ex -> The default at time T0 ex = 1 print ex -> 1 ex = None print ex -> The default at time T1 

等等。

我们也希望有一种方法来处理可参数化属性的特殊情况,但仍然允许默认分配情况下工作。 你会看到我如何解决这个下面。

现在到了这一点(耶!点!)。 我为此提出的解决scheme如下。

我们创造一个新的对象来取代财产的概念。 该对象旨在存储设置给它的variables的值,但还维护知道如何计算默认值的代码的句柄。 它的工作是存储设定值或运行该方法,如果该值没有设置。

我们称之为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 

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

这基本上给了我们一个可以做我们想要的东西的对象,但是我们怎么把它放到我们的class上? 那么,属性使用装饰器; 为什么我们不能? 让我们看看它可能看起来(从这里开始,我将坚持只使用一个'属性',x)。

 class Example(object): @uberProperty def x(self): return defaultX() 

当然,这实际上还没有工作。 我们必须实现uberProperty,并确保它处理获取和设置。

让我们开始获取。

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

def uberProperty(f):返回UberProperty(f)

当然,我很快发现,这样做并不奏效:Python从不将可调用对象绑定到对象,我需要该对象才能调用该函数。 即使在类中创build装饰器也是行不通的,因为虽然现在我们有了这个类,但是我们还没有一个可以使用的对象。

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

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

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

我们如何完成图片? 那么,当我们使用新的装饰器创build示例类时,我们会得到什么结果:

 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,这当然不是可调用的,所以这不是很有用。

我们需要的是,在创build类之后,将装饰器创build的UberProperty实例dynamic绑定到类的对象之后,才能将该对象返回给该用户使用。 恩,是的,这是一个初始化的电话,伙计。

让我们写下我们希望我们的查找结果是第一位的。 我们将一个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被调用时,我们的装饰器已经运行,所以只需要查看对象的字典并更新属性的值是UberPropertytypes的任何属性。

现在,超级属性是很酷的,我们可能会想要使用它们很多,所以只需创build一个基类来为所有的子类实现这一点是有道理的。 我想你知道基类将被称为什么。

 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) 

我们添加这个,改变我们的例子从UberObjectinheritance,并…

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

将x修改为:

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

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

 print exgetValue() print exgetValue() exsetValue(datetime.date(2013, 5, 31)) print exgetValue() exclearValue() print exgetValue() 

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

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

(哎,我工作很晚)

请注意,我在这里使用了getValue,setValue和clearValue。 这是因为我还没有链接的手段有这些自动返回。

但是我觉得这是一个暂时停下来的好地方,因为我越来越累了。 你也可以看到我们想要的核心function已经到位, 其余的是穿衣。 重要的可用性窗口打扮,但是可以等到我有一个更改后更新。

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

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

    • 所以我们要么迫使它被称为某个地方,要么阻止它被实施。
    • 我们将看到如何用元类来做到这一点。
  • 我们需要确保我们处理一个常见的情况,就是某个人把某个function“别名”给别的东西,比如:

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

    • 我们真正看到的是这是模型失败的一个领域。
    • 事实certificate,我们将始终需要使用函数调用来获取值。
    • 但是我们可以使它看起来像一个普通的函数调用,并避免使用exgetValue()。 (做这个很明显,如果你还没有解决的话。)
  • 我们需要直接支持设置ex,如ex = <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]我可能还在后面,这是否仍然如此。

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

你实际上可以获得这些function,例如

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

你可以像CpfgetCpfset那样访问getter和setter函数,但是你不能轻易使用普通的方法inheritance(例如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 = Cp @p.setter def p(self, val): print 'Implement extra functionality here for E' Cpfset(self, val) 

然而,使用super()会非常笨重,因为必须重新定义属性,而且必须使用稍微违反直觉的超级(cls,cls)机制来获取p的未绑定副本。

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

对比

 ox = 5 ox = ox 

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

对我来说比较明显,比较容易阅读。 另外属性允许私有variables更容易。

我宁愿在大多数情况下都不使用。 属性的问题是,他们使类不透明。 特别是,如果你想从一个setter中引发一个exception,这是一个问题。 例如,如果你有一个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 

那么该类的用户不会指望为该属性赋值可能会导致exception:

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

因此,这个exception可能会被解决,或者在调用链中传播得太高而不能正确处理,或者导致给程序用户一个非常无益的回溯(这在python和java的世界中是很常见的) )。

我也会避免使用getter和setter:

  • 因为事先为所有的属性定义它们是非常耗时的,
  • 使得代码量不必要的更长,这使得对代码的理解和维护更加困难,
  • 如果您只是根据需要定义属性,那么该类的接口将会改变,从而伤害该类的所有用户

取而代之的是属性和getter / setter,我更喜欢在定义好的地方(比如validation方法)中执行复杂的逻辑:

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

或类似的Account.save方法。

请注意,我没有试图说没有什么情况下属性是有用的,只是如果你可以使你的课程简单和透明以至于你不需要它们,你可能会变得更好。

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

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

在Python中,人们真正关心这种开销,所以你可以采取以下做法:

  • 一开始不要使用getter和setter,如果不需要的话
  • 使用@property来实现它们,而不改变其他代码的语法。

令我惊讶的是,没有人提到属性是描述符类的约束方法, Adam Donohue和NeilenMarais在他们的文章中正是如此 – getters和setter是函数,可以用来:

  • validation
  • 改变数据
  • 鸭型(强制型为另一种型号)

这提供了一个聪明的方式来隐藏实现细节和代码cruft像正则expression式,types强制转换,尝试..除了块,断言或计算值。

一般来说,在一个对象上执行CRUD往往是相当平凡的事情,但考虑一下将会持久化到关系数据库的数据的例子。 ORM的可以隐藏特定的SQL白洞的实现细节在绑定到fget,fset,fdel的方法绑定到一个属性类中定义,将pipe理糟糕的如果.. elif ..其他OO代码丑陋的梯子 – 揭露简单和优雅self.variable = something并避免使用 ORM的开发人员的细节。

如果只把财产视为一种束缚和规训语言(即Java)的沉闷遗迹,那么他们就忽略了描述的重点。

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

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

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

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

 my_object.my_atttr = 4. 

如果你的对象是一个复杂的algorithm,那么你将花费相当多的时间来试图找出为什么它不会收敛(注意上面一行中的额外't')

2)制定者有时可能演变成一个复杂而缓慢的方法(例如打一个数据库)。 另一个开发人员很难弄清楚为什么下面的函数很慢。 他可能花费大量时间来分析do_something()方法:

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