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

当前回答

我认为两者都有各自的地位。使用@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引发异常,这是一个问题。例如,如果您有一个帐户。电子邮件属性:

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

或类似帐户。保存方法。

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

使用属性可以让您从普通的属性访问开始,然后在必要时使用getter和setter对其进行备份。

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

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

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

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

这提供了一种聪明的方法来隐藏实现细节和代码cruft,如正则表达式,类型转换,尝试..块、断言或计算值除外。

一般来说,在对象上执行CRUD可能相当简单,但请考虑将持久化到关系数据库的数据示例。ORM可以在属性类中定义的绑定到fget, fset, fdel的方法中隐藏特定SQL白话的实现细节,该属性类将管理可怕的if ..elif . .在OO代码中是如此丑陋的梯子——暴露了简单而优雅的自我。variable = something,为使用ORM的开发人员消除细节。

如果有人认为属性只是束缚和纪律语言(即Java)的一些沉闷的残余,那么他们就没有理解描述符的意义。

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