[ 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()
你可能会奇怪,为什么我没有打电话defaultX
,并defaultY
在对象的__init__
方法。原因是,对于我们的情况,我想假设这些someDefaultComputation
方法返回的值会随时间变化,例如时间戳记,以及未设置(x
或y
)的时间(在本示例中,“未设置”的意思是“已设置”到无“)我想的值x
的(或y
的)默认计算。
因此,由于上述多种原因,这是la脚的。我将使用属性重写它:
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示例中做到了这一点。基本上,我们正在运行一个函数主体以在未设置任何值时获取默认值。这是非常常见的模式。
但是,我们正在失去什么,我们不能做什么?
在我看来,主要的烦恼是,如果您定义了一个吸气剂(就像我们在这里所做的那样),那么您还必须定义一个setter。[1] 那是使代码混乱的额外噪音。
另一个烦人的地方是,我们仍然必须初始化中的x
和y
值__init__
。(当然,我们可以使用添加它们,setattr()
但这是更多的额外代码。)
第三,与类似Java的示例不同,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]
并且当然要确保我们的设置者知道如何提取前三个值作为字典的键并将其值设置为数字或其他内容。
但是即使这样做,我们仍然不能用属性来支持它,因为没有办法获取值,因为我们根本无法将参数传递给getter。因此,我们必须返回所有内容,并引入了不对称性。
Java风格的getter / setter确实可以解决这个问题,但我们又回到了需要getter / setter的地方。
在我看来,我们真正想要的是满足以下要求的东西:
在代码方面,我们需要一种编写方式:
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
可能是真实值,这使我们可以采用一种干净的方式来声明确实没有“值”。另一种方式是某种形式的哨兵。
基本上,这给了我们一个可以做我们想要的对象的对象,但是实际上如何将它放在我们的类上呢?好吧,属性使用装饰器;我们为什么不能呢?让我们看看它的外观(从这里开始,我将坚持只使用一个'attribute',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__
,老兄。
让我们写下我们希望我们的搜索结果为第一的内容。我们将an绑定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__
并更新属性值是type的所有属性UberProperty
。
现在,uber-properties很酷,我们可能会想大量使用它们,因此仅创建一个对所有子类都执行此操作的基类是有意义的。我认为您知道将要调用的基类。
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 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()
[1]对于是否仍然如此,我可能会落后。