与经典的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

当前回答

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

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

其他回答

我觉得属性是为了让您在真正需要getter和setter时才编写它们。

Java编程文化强烈建议永远不要授予对属性的访问权,相反,只访问实际需要的getter和setter。 总是编写这些显而易见的代码段有点啰嗦,请注意,70%的情况下,它们从未被一些重要的逻辑所取代。

在Python中,人们实际上关心这种开销,因此您可以采用以下实践:

不使用getter和setter在第一次,如果他们不需要 使用@property来实现它们,而不改变其余代码的语法。

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

你可以像C.p.fset和C.p.fset一样访问getter和setter函数,但是你不能很容易地使用普通的方法继承(例如super)工具来扩展它们。在深入研究了super的复杂性之后,你确实可以这样使用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()是相当笨拙的,因为必须重新定义属性,并且必须使用稍微违反直觉的super(cls,cls)机制来获得p的未绑定副本。

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

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

比较

o.x = 5
ox = o.x

vs.

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

对我来说很明显,更容易理解。此外,属性允许私有变量更容易。

以下是摘自《有效的Python: 90种具体方法来编写更好的Python》(一本令人惊叹的书)的节选。我强烈推荐)。

Things to Remember ✦ Define new class interfaces using simple public attributes and avoid defining setter and getter methods. ✦ Use @property to define special behavior when attributes are accessed on your objects, if necessary. ✦ Follow the rule of least surprise and avoid odd side effects in your @property methods. ✦ Ensure that @property methods are fast; for slow or complex work—especially involving I/O or causing side effects—use normal methods instead. One advanced but common use of @property is transitioning what was once a simple numerical attribute into an on-the-fly calculation. This is extremely helpful because it lets you migrate all existing usage of a class to have new behaviors without requiring any of the call sites to be rewritten (which is especially important if there’s calling code that you don’t control). @property also provides an important stopgap for improving interfaces over time. I especially like @property because it lets you make incremental progress toward a better data model over time. @property is a tool to help you address problems you’ll come across in real-world code. Don’t overuse it. When you find yourself repeatedly extending @property methods, it’s probably time to refactor your class instead of further paving over your code’s poor design. ✦ Use @property to give existing instance attributes new functionality. ✦ Make incremental progress toward better data models by using @property. ✦ Consider refactoring a class and all call sites when you find yourself using @property too heavily.