与经典的getter+setter相比,@property表示法有什么优点?在哪些特定的情况下,程序员应该选择使用其中一种而不是另一种?

属性:

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

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

没有属性:

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

    def set_my_attr(self, value):
        self._my_attr = value

当前回答

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

其他回答

简单的答案是:properties轻松获胜。总是这样。

有时需要getter和setter,但即使这样,我也会将它们“隐藏”到外部世界。在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)

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

在大多数情况下,我宁愿两者都不使用。属性的问题是它们使类不那么透明。特别是,如果要从setter引发异常,这是一个问题。例如,如果您有一个帐户。电子邮件属性:

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.')

或类似帐户。保存方法。

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

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

(TL,博士?你可以跳过最后的代码示例。

实际上,我更喜欢使用不同的习语,对于一次性使用来说有点复杂,但如果您有一个更复杂的用例,这是很好的。

先讲一点背景知识。

属性很有用,因为它们允许我们以编程的方式处理设置和获取值,但仍然允许将属性作为属性访问。我们可以将“gets”转换为“computation”(本质上),我们可以将“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)没有设置时(这里,为了本例的目的,“not set”意味着“设置为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.

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

当然,属性的真正强大之处在于,我们通常希望这些方法除了获取和设置值之外还能做一些事情(否则使用属性就没有意义了)。我在getter例子中做了这个。我们基本上是在运行一个函数体,以便在未设置值时获取默认值。这是一个很常见的模式。

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

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

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

第三,与类java的示例不同,getter不能接受其他参数。现在我听到你在说,如果它接受参数,它就不是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]

当然,还要确保setter知道如何提取前三个值作为字典的键并将其值设置为数字之类的。

但即使我们这样做了,我们仍然不能用属性来支持它,因为没有办法得到值,因为我们根本不能把参数传递给getter。所以我们必须返回所有的东西,引入不对称。

java风格的getter/setter确实允许我们处理这个问题,但是我们又需要getter/setter。

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

Users define just one method for a given attribute and can indicate there whether the attribute is read-only or read-write. Properties fail this test if the attribute writable. There is no need for the user to define an extra variable underlying the function, so we don't need the __init__ or setattr in the code. The variable just exists by the fact we've created this new-style attribute. Any default code for the attribute executes in the method body itself. We can set the attribute as an attribute and reference it as an attribute. We can parameterize the attribute.

在代码方面,我们想要这样写:

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

等等。

我们还想要一种方法来为可参数属性的特殊情况做到这一点,但仍然允许默认的赋值情况工作。您将在下面看到我如何处理这个问题。

现在言归正传(耶!点!)我提出的解决方案如下。

我们创建一个新对象来替换属性的概念。该对象用于存储设置给它的变量的值,但也维护知道如何计算默认值的代码句柄。它的任务是存储已设置的值,或者在未设置该值时运行该方法。

让我们称之为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可能是一个真实的值,这允许我们以一种干净的方式声明真的“没有值”。另一种方法是某种哨兵。

这基本上给了我们一个可以做我们想做的事情的对象,但是我们如何把它放到我们的类中呢?属性使用装饰器;为什么我们不能?让我们看看它会是什么样子(从这里开始,我将坚持只使用一个“属性”x)。

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

当然,这实际上还不能工作。我们必须实现uberProperty和 确保它同时处理get和set。

让我们从gets开始。

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

def uberProperty(f):
    return UberProperty(f)

当然,我很快发现这是行不通的:Python从不将可调用对象绑定到对象上,而我需要对象才能调用函数。即使在类中创建装饰器也不能工作,因为尽管现在我们有了类,但仍然没有可以使用的对象。

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

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__调用,伙计。

让我们写下我们想要的第一个查找结果。我们将一个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__并更新属性值为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

(哎呀,我要工作到很晚。)

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

但我觉得现在就到这里吧,因为我有点累了。您还可以看到,我们想要的核心功能已经到位;剩下的都是装点门面。重要的可用性窗口装饰,但那可以等到我有一个变化更新后。

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

We need to make sure UberObject's __init__ is always called by subclasses. So we either force it be called somewhere or we prevent it from being implemented. We'll see how to do this with a metaclass. We need to make sure we handle the common case where someone 'aliases' a function to something else, such as: class Example(object): @uberProperty def x(self): ... y = x We need e.x to return e.x.getValue() by default. What we'll actually see is this is one area where the model fails. It turns out we'll always need to use a function call to get the value. But we can make it look like a regular function call and avoid having to use e.x.getValue(). (Doing this one is obvious, if you haven't already fixed it out.) We need to support setting e.x directly, as in e.x = <newvalue>. We can do this in the parent class too, but we'll need to update our __init__ code to handle it. Finally, we'll add parameterized attributes. It should be pretty obvious how we'll do this, too.

下面是它目前存在的代码:

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()

我可能落后于现在的情况了。

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

@property的优点

You don't have to change the interface while changing the implementation of data access. When your project is small, you probably want to use direct attribute access to access a class member. For example, let's say you have an object foo of type Foo, which has a member num. Then you can simply get this member with num = foo.num. As your project grows, you may feel like there needs to be some checks or debugs on the simple attribute access. Then you can do that with a @property within the class. The data access interface remains the same so that there is no need to modify client code. Cited from PEP-8: For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods. Keep in mind that Python provides an easy path to future enhancement, should you find that a simple data attribute needs to grow functional behavior. In that case, use properties to hide functional implementation behind simple data attribute access syntax. Using @property for data access in Python is regarded as Pythonic: It can strengthen your self-identification as a Python (not Java) programmer. It can help your job interview if your interviewer thinks Java-style getters and setters are anti-patterns.

传统getter和setter的优点

Traditional getters and setters allow for more complicated data access than simple attribute access. For example, when you are setting a class member, sometimes you need a flag indicating where you would like to force this operation even if something doesn't look perfect. While it is not obvious how to augment a direct member access like foo.num = num, You can easily augment your traditional setter with an additional force parameter: def Foo: def set_num(self, num, force=False): ... Traditional getters and setters make it explicit that a class member access is through a method. This means: What you get as the result may not be the same as what is exactly stored within that class. Even if the access looks like a simple attribute access, the performance can vary greatly from that. Unless your class users expect a @property hiding behind every attribute access statement, making such things explicit can help minimize your class users surprises. As mentioned by @NeilenMarais and in this post, extending traditional getters and setters in subclasses is easier than extending properties. Traditional getters and setters have been widely used for a long time in different languages. If you have people from different backgrounds in your team, they look more familiar than @property. Also, as your project grows, if you may need to migrate from Python to another language that doesn't have @property, using traditional getters and setters would make the migration smoother.

警告

Neither @property nor traditional getters and setters makes the class member private, even if you use double underscore before its name: 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