Zau*_*bov 711 python properties getter-setter
这是一个纯Python特定的设计问题:
class MyClass(object):
...
def get_my_attr(self):
...
def set_my_attr(self, value):
...
Run Code Online (Sandbox Code Playgroud)
和
class MyClass(object):
...
@property
def my_attr(self):
...
@my_attr.setter
def my_attr(self, value):
...
Run Code Online (Sandbox Code Playgroud)
Python让我们可以这样做.如果你要设计一个Python程序,你会使用哪种方法?为什么?
kin*_*all 598
喜欢属性.这就是他们的目的.
原因是Python中的所有属性都是公共的.带有下划线或下划线的起始名称只是警告,给定属性是一个实现细节,在将来的代码版本中可能不会保持不变.它不会阻止您实际获取或设置该属性.因此,标准属性访问是访问属性的正常,Pythonic方式.
属性的优点是它们在语法上与属性访问相同,因此您可以在不更改客户端代码的情况下从一个更改为另一个.您甚至可以使用一个版本的类使用属性(例如,用于逐个合同或调试)和一个不用于生产的类,而不更改使用它的代码.同时,您不必为所有内容编写getter和setter,以防您以后需要更好地控制访问.
650*_*502 149
在Python中,您不使用getter或setter或属性只是为了它的乐趣.您首先只使用属性,然后仅在需要时,最终迁移到属性,而不必使用类更改代码.
确实有很多扩展名.py的代码,它们使用getter和setter以及继承和无意义的类,例如一个简单的元组可以做到,但它是使用Python用C++或Java编写的代码.
那不是Python代码.
Ign*_*ams 115
使用属性可以让您从正常的属性访问开始,然后根据需要使用getter和setter备份它们.
mac*_*mac 69
简短的回答是:物业赢得了胜利.总是.
有时需要吸气剂和固定剂,但即便如此,我也会将它们"隐藏"到外面的世界.有很多方法在Python(要做到这一点getattr
,setattr
,__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)
Run Code Online (Sandbox Code Playgroud)
这是一篇简短的文章,介绍了Python中的getter和setter主题.
Ada*_*hue 60
[ 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()
Run Code Online (Sandbox Code Playgroud)
你可能想知道为什么我没有调用defaultX
和defaultY
对象的__init__
方法.原因是对于我们的情况,我想假设someDefaultComputation
方法返回随时间变化的值,例如时间戳,并且无论何时x
(或y
)未设置(其中,出于本示例的目的,"未设置"表示"设置"无")我想要x
's(或y
's)默认计算的值.
因此,由于上述多种原因,这是蹩脚的.我将使用属性重写它:
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.
Run Code Online (Sandbox Code Playgroud)
我们获得了什么?我们已经获得了将这些属性称为属性的能力,即使在幕后我们最终运行方法.
当然,属性的真正力量在于我们通常希望这些方法除了获取和设置值之外还要做一些事情(否则使用属性没有意义).我在我的getter例子中这样做了.我们基本上运行一个函数体来在没有设置值时获取默认值.这是一种非常常见的模式.
但是我们失去了什么,我们不能做什么?
在我看来,主要的烦恼是,如果你定义一个吸气剂(就像我们在这里做的那样),你还必须定义一个定位器.[1] 这是使代码混乱的额外噪音.
另一个烦恼是我们仍然需要初始化x
和y
值__init__
.(当然,我们可以使用它们添加它们,setattr()
但这是更多的额外代码.)
第三,与类似Java的示例不同,getter不能接受其他参数.现在我已经听到你说了,好吧,如果它采取参数它不是一个吸气剂!从官方意义上讲,这是事实.但实际上,我们没有理由不能参数化命名属性 - 比如x
- 并为某些特定参数设置其值.
如果我们可以这样做,那就太好了:
e.x[a,b,c] = 10
e.x[d,e,f] = 20
Run Code Online (Sandbox Code Playgroud)
例如.我们可以得到的最接近的是覆盖赋值以暗示一些特殊的语义:
e.x = [a,b,c,10]
e.x = [d,e,f,30]
Run Code Online (Sandbox Code Playgroud)
当然,确保我们的setter知道如何提取前三个值作为字典的键,并将其值设置为数字或其他值.
但即使我们这样做,我们仍然无法使用属性来支持它,因为无法获取值,因为我们无法将参数传递给getter.所以我们必须归还所有东西,引入不对称性.
Java风格的getter/setter确实让我们处理了这个问题,但我们又回到了需要getter/setter的问题.
在我看来,我们真正想要的是能够满足以下要求的东西:
用户只为给定属性定义一个方法,并可以指示该属性是只读还是读写.如果属性可写,则属性将失败.
用户无需在函数下定义额外的变量,因此我们不需要代码中的__init__
或setattr
.变量只是因为我们创建了这个新风格的属性.
属性的任何默认代码都在方法体本身中执行.
我们可以将属性设置为属性并将其作为属性引用.
我们可以参数化属性.
在代码方面,我们想要一种写作方式:
def x(self, *args):
return defaultX()
Run Code Online (Sandbox Code Playgroud)
然后能够:
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
Run Code Online (Sandbox Code Playgroud)
等等.
我们还想要一种方法来实现可参数化属性的特殊情况,但仍然允许默认的赋值情况起作用.你会看到我在下面解决这个问题.
现在到了这一点(耶!重点!).我为此提出的解决方案如下.
我们创建一个新对象来替换属性的概念.该对象旨在存储变量集的值,但也维护一个知道如何计算默认值的代码句柄.它的工作是存储集合value
或运行method
if if not value not set.
我们称之为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
Run Code Online (Sandbox Code Playgroud)
我假设method
这里是一个类方法,value
是值的UberProperty
,我已经添加isSet
因为None
可能是一个真正的值,这使我们有一个干净的方式来声明那里真的是"没有价值".另一种方式是某种哨兵.
这基本上为我们提供了一个可以做我们想要的事情的对象,但是我们如何将它真正地放在我们的课堂上呢?那么,属性使用装饰器; 为什么我们不能?让我们看看它的外观(从这里开始,我将坚持只使用一个'属性' x
).
class Example(object):
@uberProperty
def x(self):
return defaultX()
Run Code Online (Sandbox Code Playgroud)
当然,这实际上还不起作用.我们必须实现uberProperty
并确保它处理gets和sets.
让我们从获取开始吧.
我的第一个尝试是简单地创建一个新的UberProperty对象并返回它:
def uberProperty(f):
return UberProperty(f)
Run Code Online (Sandbox Code Playgroud)
当然,我很快发现这不起作用:Python永远不会将可调用绑定到对象,我需要该对象才能调用该函数.即使在类中创建装饰器也不起作用,因为虽然现在我们有了类,但我们仍然没有可以使用的对象.
所以我们需要在这里做更多的事情.我们知道一个方法只需要一次表示,所以让我们继续保持我们的装饰器,但修改UberProperty
为只存储method
引用:
class UberProperty(object):
def __init__(self, method):
self.method = method
Run Code Online (Sandbox Code Playgroud)
它也不可调用,所以目前没有任何工作.
我们如何完成图片?那么,当我们使用新的装饰器创建示例类时,我们最终得到了什么:
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>
Run Code Online (Sandbox Code Playgroud)
在这两种情况下我们都会回来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
Run Code Online (Sandbox Code Playgroud)
现在我们代表; 如何将这些物品放到物体上?有一些方法,但最容易解释的__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)
Run Code Online (Sandbox Code Playgroud)
我们添加这个,改变我们继承的例子UberObject
,并且......
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
Run Code Online (Sandbox Code Playgroud)
修改x
为:
@uberProperty
def x(self):
return *datetime.datetime.now()*
Run Code Online (Sandbox Code Playgroud)
我们可以运行一个简单的测试:
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()
Run Code Online (Sandbox Code Playgroud)
我们得到了我们想要的输出:
2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310
Run Code Online (Sandbox Code Playgroud)
(哎呀,我工作到很晚.)
请注意,我已经使用getValue
,setValue
以及clearValue
在这里.这是因为我还没有联系方式让这些自动返回.
但我认为这是一个暂停的好地方,因为我累了.您还可以看到我们想要的核心功能已经到位; 其余的是橱窗装饰.重要的可用性橱窗装饰,但可以等到我有更改来更新帖子.
我将在下一篇文章中通过解决这些问题来完成示例:
我们需要确保UberObject __init__
始终由子类调用.
我们需要确保我们处理一个常见的情况,即某人将一个函数别名化为其他东西,例如:
class Example(object):
@uberProperty
def x(self):
...
y = x
Run Code Online (Sandbox Code Playgroud)我们需要默认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()
Run Code Online (Sandbox Code Playgroud)
[1]我可能会支持这是否仍然如此.
Nei*_*ais 26
我认为两者都有自己的位置.使用的一个问题@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
Run Code Online (Sandbox Code Playgroud)
您可以访问getter和setter功能C.p.fget
和C.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)
Run Code Online (Sandbox Code Playgroud)
但是,使用super()非常笨重,因为必须重新定义属性,并且必须使用稍微反直觉的超级(cls,cls)机制来获取p的未绑定副本.
Hob*_*lin 20
使用属性对我来说更直观,更适合大多数代码.
对比
o.x = 5
ox = o.x
Run Code Online (Sandbox Code Playgroud)
与
o.setX(5)
ox = o.getX()
Run Code Online (Sandbox Code Playgroud)
对我来说很明显哪个更容易阅读.此外,属性允许私有变量更容易.
小智 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
Run Code Online (Sandbox Code Playgroud)
然后该类的用户不期望为该属性赋值可能导致异常:
a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.
Run Code Online (Sandbox Code Playgroud)
因此,异常可能无法处理,并且在调用链中传播得太高而无法正确处理,或者导致向程序用户呈现非常无用的回溯(这在python和java的世界中很常见) ).
我也会避免使用getter和setter:
我更喜欢在明确定义的位置(例如验证方法)中执行复杂逻辑,而不是属性和getter/setter:
class Account(object):
...
def validate(self):
if '@' not in self.email:
raise ValueError('Invalid email address.')
Run Code Online (Sandbox Code Playgroud)
或类似的Account.save方法.
请注意,我并不是说没有属性有用的情况,只是如果你可以使你的类简单透明到不需要它们,那么你可能会更好.
ful*_*ton 11
我觉得属性就是让你只有在你真正需要时才能获得编写getter和setter的开销.
Java编程文化强烈建议永远不要访问属性,而是通过getter和setter,只有那些实际需要的东西.总是编写这些明显的代码片段有点冗长,并注意到70%的时间它们永远不会被一些非平凡的逻辑所取代.
在Python中,人们实际上关心这种开销,以便您可以接受以下实践:
@property
予以实施而又不改变你的代码的其余部分的语法.fia*_*cre 10
令我感到惊讶的是,没有人提到属性是描述符类的绑定方法,Adam Donohue和NeilenMarais在他们的帖子中得到了这个想法 - getter和setter是函数,可以用于:
这提供了一种隐藏实现细节和代码伪装的智能方法,如正则表达式,类型转换,尝试..除了块,断言或计算值.
通常,对对象执行CRUD通常可能相当普通,但请考虑将持久保存到关系数据库的数据示例.ORM可以在绑定到fget,fset,fdel的方法中隐藏特定SQL vernaculars的实现细节,这些方法将在一个属性类中定义,该属性类将管理在OO代码中如此丑陋的糟糕的if ... else梯形图 - 暴露简单和优雅self.variable = something
并使用 ORM 消除开发人员的详细信息.
如果人们认为属性只是作为束缚和纪律语言(即Java)的一些沉闷遗迹,那么他们就会忽略描述符的重点.
无论@property
与传统的getter和setter方法各有优点。这取决于您的用例。
@property
在更改数据访问的实现时,您不必更改接口。当您的项目较小时,您可能希望使用直接属性访问来访问类成员。例如,假设您有一个foo
类型为 的对象Foo
,它有一个成员num
。然后你可以简单地使用num = foo.num
. 随着项目的增长,您可能会觉得需要对简单属性访问进行一些检查或调试。然后你可以@property
在类中使用 a 来做到这一点。数据访问接口保持不变,无需修改客户端代码。
引自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):
...
Run Code Online (Sandbox Code Playgroud)传统的 getter 和 setter明确表示类成员访问是通过方法进行的。这意味着:
您得到的结果可能与该类中存储的内容不同。
即使访问看起来像一个简单的属性访问,其性能也可能相差很大。
除非您的类用户希望@property
隐藏在每个属性访问语句后面,否则将此类内容明确化有助于最大程度地减少类用户的意外情况。
正如@NeilenMarais和在这篇文章中提到的,在子类中扩展传统的 getter 和 setter 比扩展属性更容易。
传统的 getter 和 setter 已经在不同的语言中广泛使用了很长时间。如果您的团队中有来自不同背景的人,他们看起来比@property
. 此外,随着项目的发展,如果您可能需要从 Python 迁移到另一种没有@property
.
无论是@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
Run Code Online (Sandbox Code Playgroud)在复杂的项目中,我更喜欢使用带有显式setter函数的只读属性(或getter):
class MyClass(object):
...
@property
def my_attr(self):
...
def set_my_attr(self, value):
...
Run Code Online (Sandbox Code Playgroud)
在长期的项目中,调试和重构比编写代码本身要花费更多的时间。使用它有几个缺点@property.setter
,使调试更加困难:
1)python允许为现有对象创建新属性。这使得很难跟踪以下印刷错误:
my_object.my_atttr = 4.
Run Code Online (Sandbox Code Playgroud)
如果您的对象是一个复杂的算法,那么您将花费相当多的时间尝试找出为什么它无法收敛(请注意,在上面的行中有一个额外的“ t”)
2)setter有时可能会演变为复杂而缓慢的方法(例如,访问数据库)。对于另一个开发人员来说,很难弄清楚为什么以下功能非常慢。他可能在分析do_something()
方法上花了很多时间,而my_object.my_attr = 4.
实际上是导致速度下降的原因:
def slow_function(my_object):
my_object.my_attr = 4.
my_object.do_something()
Run Code Online (Sandbox Code Playgroud)
这是“Effective Python: 90 Specific Ways to Write Better Python”的摘录(很棒的书。我强烈推荐它)。
要记住的事情
? 使用简单的公共属性定义新的类接口,避免定义 setter 和 getter 方法。
? 如有必要,当访问对象的属性时,使用 @property 定义特殊行为。
? 遵循最少意外的规则并避免@property 方法中出现奇怪的副作用。
? 确保@property 方法快速;对于缓慢或复杂的工作——尤其是涉及 I/O 或引起副作用的工作——改用普通方法。
@property 的一种高级但常见的用法是将曾经是简单的数字属性转换为即时计算。这非常有用,因为它允许您迁移一个类的所有现有用法以具有新行为,而无需重写任何调用站点(如果存在您无法控制的调用代码,这一点尤其重要)。@property 还为随着时间的推移改进界面提供了一个重要的权宜之计。
我特别喜欢 @property,因为它可以让您随着时间的推移逐步朝着更好的数据模型前进。
@property 是一种工具,可帮助您解决在实际代码中会遇到的问题。不要过度使用它。当您发现自己反复扩展 @property 方法时,可能是时候重构您的类而不是进一步为代码的糟糕设计铺平道路了。? 使用@property 为现有实例属性提供新功能。
? 使用@property 逐步改进数据模型。
? 当您发现自己过多地使用 @property 时,请考虑重构一个类和所有调用站点。
归档时间: |
|
查看次数: |
405681 次 |
最近记录: |