使用@property与getter和setter

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,以防您以后需要更好地控制访问.

  • 具有双下划线的属性名称由Python专门处理; 这不仅仅是一个惯例.请参阅http://docs.python.org/py3k/tutorial/classes.html#private-variables (87认同)
  • 它们的处理方式不同,但这并不妨碍您访问它们.PS:AD 30 C0 (61认同)
  • 虽然我在大多数情况下都同意,但要小心隐藏@property装饰器后面的慢速方法.API的用户期望属性访问执行变量访问,并且偏离预期太远可能会使您的API使用起来不愉快. (61认同)
  • 我不同意.结构化代码如何等同于意大利面条代码?Python是一种美妙的语言.但是对于诸如适当的封装和结构化类之类的简单事物的更好支持会更好. (17认同)
  • 因为"@"字符在python代码中是丑陋的,并且解除引用@decorators给出了像意大利面条代码一样的感觉. (4认同)
  • 问题不在于直接属性访问与属性.很明显,执行代码的代码比代码少得多. (4认同)
  • @PiotrKamoda不,`@ property`在每次访问时调用getter.但是,您可以使用[不同的装饰器](http://stackoverflow.com/a/35328997/416467)来做您正在考虑的事情. (2认同)

650*_*502 149

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

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

那不是Python代码.

  • @ 6502,当你说"[......]无处不在的类,例如一个简单的元组会做的事情":类相对于元组的优点是类实例提供显式名称来访问它的部分,而元组则不.与元组下标相比,名称在可读性和避免错误方面更好,特别是当它要传递到当前模块之外时. (43认同)
  • @ Hibou57你也可以使用namedtuple:http://www.doughellmann.com/PyMOTW/collections/namedtuple.html (37认同)
  • @ Hibou57:我不是说上课没用.但有时一个元组绰绰有余.然而问题是,来自Java或C++的人别无选择,只能为所有内容创建类,因为其他可能性只是在这些语言中使用时很烦人.使用Python进行Java/C++编程的另一个典型症状是创建抽象类和复杂的类层次结构,因为在Python中你可以使用鸭子类型来使用独立的类. (14认同)
  • @JonathonReinhart:自从2.6以来它在标准库中是****...请参阅http://docs.python.org/2/library/collections.html (5认同)

Ign*_*ams 115

使用属性可以让您从正常的属性访问开始,然后根据需要使用getter和setter备份它们.

  • @GregKrsak看起来很奇怪,因为它是.在获得属性之前,"同意成年人的东西"是一个蟒蛇模因.这是对那些抱怨缺乏访问修饰符的人的股票反应.当添加属性时,突然封装变得合乎需要.抽象基类也发生了同样的事情."Python始终处于封装破坏之中.自由是奴隶制.Lambdas应该只适合一条线." (3认同)

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主题.

  • 提示:"总是"这个词暗示着作者试图用断言来说服你,而不是争论.粗体字体的存在也是如此.(我的意思是,如果你看到CAPS,那么 - 哇 - 它必须是正确的.)看,"属性"特征恰好与Java不同(由于某种原因,Python的事实上的克星),因此Python的社区组织思考声明它更好.实际上,属性违反了"明确比隐含更好"的规则,但没有人愿意承认.它使它成为语言,所以现在它通过重言式论证被宣布为"Pythonic". (7认同)
  • 没有感觉受到伤害。:-P我只是想指出在这种情况下,“ Pythonic”约定是不一致的:“显式优于隐式”与使用“属性”直接冲突。(它看起来像一个简单的赋值,但它调用一个函数。)因此,“ Pythonic”本质上是一个无意义的术语,除非是重言式定义:“ Pythonic约定是我们定义为Pythonic的东西。” (2认同)
  • 现在,拥有一组遵循主题的约定的*想法* *很棒*。如果确实存在这样一组约定,那么您可以将其用作一组公理来指导您的思考,而不仅仅是要记住的冗长的技巧清单,这显然不太有用。公理可用于*外推*,并帮助您解决尚未有人见过的问题。遗憾的是,`property` 特性可能会使 Pythonic 公理的想法几乎一文不值。所以我们只剩下一个清单。 (2认同)
  • 我不同意。在大多数情况下我更喜欢属性,但是当你想强调设置某些东西除了修改 self 对象之外还有**副作用**时,显式设置器可能会有所帮助。例如,`user.email = "..."` 看起来不会引发异常,因为它看起来只是设置一个属性,而 `user.set_email("...")` 清楚地表明可能会出现异常等副作用。 (2认同)

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)

你可能想知道为什么我没有调用defaultXdefaultY对象的__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] 这是使代码混乱的额外噪音.

另一个烦恼是我们仍然需要初始化xy__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或运行methodif 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]我可能会支持这是否仍然如此.

  • 是的,这是'tldr'.你能否总结一下你在这里想做什么? (51认同)
  • @Adam`返回self.x或self.defaultX()`这是危险的代码.当`self.x == 0`时会发生什么? (9认同)
  • @KellyThomas只是想让这个例子变得简单.要做得对,你需要完全创建和删除x __dict__条目,因为即使是None值也可能是专门设置的.但是,是的,你是绝对正确的,这是你需要在生产用例中考虑的事情. (2认同)

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.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)
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方法.

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

  • @ user2239734我觉得你误解了属性的概念.虽然您可以在设置属性时验证该值,但不需要执行此操作.你可以在类中同时拥有属性和`validate()`方法.当你在一个简单的`obj.x = y`赋值后面有一个复杂的逻辑时,就可以使用一个属性,它取决于逻辑是什么. (3认同)

ful*_*ton 11

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

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

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

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

  • 并不是人们关心开销,而是在Python中您可以在不更改客户端代码的情况下从直接访问方法更改为访问方法,因此您最初不能直接暴露属性. (7认同)
  • “并且请注意,70% 的情况下,它们永远不会被一些重要的逻辑所取代。” ——这是一个相当具体的数字,它是来自某个地方,还是你打算将它作为一个手摇的“绝大多数”之类的东西(我不是在开玩笑,如果有一项研究可以量化这个数字,我会真正有兴趣阅读它) (2认同)
  • 哦,不抱歉。听起来我确实有一些研究来备份这个数字,但我的意思只是“大多数时候”。 (2认同)

fia*_*cre 10

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

  • 验证
  • 改变数据
  • 鸭子类型(胁迫类型到另一种类型)

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

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

如果人们认为属性只是作为束缚和纪律语言(即Java)的一些沉闷遗迹,那么他们就会忽略描述符的重点.


Cyk*_*ker 7

无论@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 的优点

  • 传统的 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)


oto*_*nan 5

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


Vla*_*den 5

这是“Effective Python: 90 Specific Ways to Write Better Python”的摘录(很棒的书。我强烈推荐它)。

要记住的事情

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

? 如有必要,当访问对象的属性时,使用 @property 定义特殊行为。

? 遵循最少意外的规则并避免@property 方法中出现奇怪的副作用。

? 确保@property 方法快速;对于缓慢或复杂的工作——尤其是涉及 I/O 或引起副作用的工作——改用普通方法。

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

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

? 使用@property 为现有实例属性提供新功能。

? 使用@property 逐步改进数据模型。

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