[简而言之? 你可以直接跳到结尾查看代码示例。]
实际上,我更喜欢使用不同的习语,如果您有更复杂的用例,这种方法会更好一些,但是如果您只需要使用一次,那么这种方法可能过于繁琐。
首先,让我们来了解一下背景。
属性很有用,因为它们允许我们以编程方式处理设置和获取值,但仍允许像属性一样访问属性。 我们可以将“获取”转换为“计算”(本质上),并将“设置”转换为“事件”。 因此,假设我们有以下类,我已经使用类似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
)未设置时(在本例中,“未设置”意味着“设置为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
我们获得了什么?我们获得了能够将这些属性称为属性的能力,即使在幕后我们最终运行的是方法。
当然,属性的真正威力在于,我们通常希望这些方法除了获取和设置值之外还要做一些其他事情(否则使用属性就没有意义了)。我在getter示例中就是这样做的。我们基本上在运行函数体以便在未设置值时拾取默认值。这是一个非常常见的模式。
但是我们失去了什么,我们不能做什么呢?
我认为主要的烦恼是,如果您定义一个getter(就像我们在这里所做的那样),您还必须定义一个setter。[1] 这是额外的噪音,会使代码变得混乱。
另一个烦恼是我们仍然需要在__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 的情况。
在我看来,我们真正想要的是捕获以下要求的东西:
用户只为给定属性定义一个方法,并可以指示该属性是只读还是可写。如果属性是可写的,则属性失败了。
用户无需定义底层函数的额外变量,因此我们不需要代码中的 __init__ 或 setattr。该变量只是由于我们创建了这个新风格的属性而存在。
任何属性的默认代码都在方法体内执行。
我们可以将属性设置为属性并将其作为属性引用。
我们可以将属性参数化。
就代码而言,我们希望有一种编写方式:
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
等等,还有其他的内容。
我们还希望有一种方法来处理可参数化属性的特殊情况,但仍然允许默认分配的情况工作。你将看到我如何解决这个问题。
现在进入重点(耶!重点!)。 我提出的解决方案如下。
我们创建一个新对象来替换属性的概念。该对象旨在存储设置为其值的变量的值,并维护了一个知道如何计算默认值的代码的句柄。它的工作是存储设置的value
,或者如果未设置该值,则运行method
。
让我们称之为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
并确保它处理了获取和设置。
让我们从获取开始。
我的第一次尝试是简单地创建一个新的 UberProperty 对象并将其返回:
def uberProperty(f):
return UberProperty(f)
当然,我很快发现这样做行不通:Python从未将可调用项绑定到对象上,而我需要对象才能调用函数。即使在类中创建装饰器也不起作用,因为尽管现在我们有类,但我们仍然没有对象可以使用。
所以我们需要在这里做更多的工作。我们知道一个方法只需要被表示一次,因此让我们继续保留我们的装饰器,但修改UberProperty只存储method引用:
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
。这是因为我还没有链接自动返回这些的方式。
但我认为现在停下来是个好时机,因为我有点累了。你也可以看到我们想要的核心功能已经就位。其余的只是装饰性的东西。重要的用法装饰性的东西,但是在我有机会更新帖子之前,这可以等待。
在下一篇文章中,我将通过解决以下问题来完成示例:
我们需要确保UberObject的__init__
始终由子类调用。
- 所以我们要么在某个地方强制其被调用,要么防止其被实现。
- 我们将看到如何使用元类来做到这一点。
我们需要确保处理常见情况,即有人将函数“别名”为其他内容,例如:
class Example(object):
@uberProperty
def x(self):
...
y = x
我们需要默认情况下e.x
返回e.x.getValue()
。
- 实际上,这是模型失败的一个领域。
- 结果我们总是需要使用函数调用来获取值。
- 但我们可以让它看起来像常规的函数调用,避免使用
e.x.getValue()
。(如果你还没有修复它,那么这很明显。)
我们需要支持直接设置e.x
,例如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()
[1] 我可能对这是否仍然是事实落后了。