GAE NDB PickleProperty for dict:新实体占用旧属性?
GAE NDB PickleProperty for dict: new entity taking old property?
对我来说看起来像是一个错误,但也许有人会对此做出合理的解释。
考虑以下代码:
class Test(ndb.Model):
a= ndb.IntegerProperty()
p = ndb.PickleProperty(default={})
现在执行以下操作:
>> t1 = Test()
>> t1.p['a'] = 1
>> t1.p['b'] = 2
{'a': 1, 'b': 2}
到目前为止,一切都很好。但是现在:
>> t2 = Test()
>> t2.p
{'a': 1, 'b': 2}
全新的 t2 实例已被分配了 t1 的 p 值 ?!!
除了错误之外还有什么解释吗?
请注意,执行 t1 的 put() 不会改变行为。
在 Python 中,默认参数计算 一次 -- 所以你使用的是 single dict
(你的 default={}
是每个进程一个字典,而不是每个实体一个字典!)跨越所有种类 Test
的实体,它们恰好在同一个进程中,并且 p
是未明确设置。
如果你做 t=Test(p={})
那么 t
就可以了,有它自己的 dict
。如果您执行 t=Test()
然后 t.p = {}
,您也可以。但是,如果您不以某种方式设置实体的特定 p
,它将使用相同的默认值 dict
,这恰好是同一进程中所有此类实体所使用的默认值,没有明确设置 p
。
当你 put
一个 Test
实体时,进入数据存储区的是当时 p
的腌制 "snapshot" -- 当你 get
它回来,它将恢复到那个状态,现在与 default
单进程 - dict
- 每个进程的其他可能使用断开连接。但这些只是这种可疑用法中的更多异常。
简而言之,可变默认值在Python中不是一个好主意——人们几乎不会正确使用它们。这适用于对 ndb.PickleProperty
的调用至少与对其他 Python 可调用对象的任何其他调用一样多!
补充:如果你需要一个 PickleProperty
来专门保存一个字典,并且发现每次实例化那种实体时显式添加 p={}
太麻烦,子类化 PickleProperty
可能有帮助。即:
class DictPickleProperty(ndb.PickleProperty):
def __init__(self, **kwds):
kwds['default'] = kwds.get('default', {})
super(DictPickleProperty, self).__init__(**kwds)
如果 default
(A) 未指定,或 (B) 指定为非 dict
,您想要做什么当然取决于您。这个简单的例子在情况 (B) 中没有做任何特别的事情——(所以例如 default=[]
仍然会导致问题)——但是在情况 (A) 中确实使用了一个新的 empy dict
.
或者,您可以尝试将任何提供的 default
值转换为新的 dict
(因此 []
或 {}
会生成一个新的空值 dict
,但许多其他值会引发异常):
kwds['default'] = dict(kwds.get('default', ()))
当然还有许多其他变体。
对我来说看起来像是一个错误,但也许有人会对此做出合理的解释。 考虑以下代码:
class Test(ndb.Model):
a= ndb.IntegerProperty()
p = ndb.PickleProperty(default={})
现在执行以下操作:
>> t1 = Test()
>> t1.p['a'] = 1
>> t1.p['b'] = 2
{'a': 1, 'b': 2}
到目前为止,一切都很好。但是现在:
>> t2 = Test()
>> t2.p
{'a': 1, 'b': 2}
全新的 t2 实例已被分配了 t1 的 p 值 ?!!
除了错误之外还有什么解释吗? 请注意,执行 t1 的 put() 不会改变行为。
在 Python 中,默认参数计算 一次 -- 所以你使用的是 single dict
(你的 default={}
是每个进程一个字典,而不是每个实体一个字典!)跨越所有种类 Test
的实体,它们恰好在同一个进程中,并且 p
是未明确设置。
如果你做 t=Test(p={})
那么 t
就可以了,有它自己的 dict
。如果您执行 t=Test()
然后 t.p = {}
,您也可以。但是,如果您不以某种方式设置实体的特定 p
,它将使用相同的默认值 dict
,这恰好是同一进程中所有此类实体所使用的默认值,没有明确设置 p
。
当你 put
一个 Test
实体时,进入数据存储区的是当时 p
的腌制 "snapshot" -- 当你 get
它回来,它将恢复到那个状态,现在与 default
单进程 - dict
- 每个进程的其他可能使用断开连接。但这些只是这种可疑用法中的更多异常。
简而言之,可变默认值在Python中不是一个好主意——人们几乎不会正确使用它们。这适用于对 ndb.PickleProperty
的调用至少与对其他 Python 可调用对象的任何其他调用一样多!
补充:如果你需要一个 PickleProperty
来专门保存一个字典,并且发现每次实例化那种实体时显式添加 p={}
太麻烦,子类化 PickleProperty
可能有帮助。即:
class DictPickleProperty(ndb.PickleProperty):
def __init__(self, **kwds):
kwds['default'] = kwds.get('default', {})
super(DictPickleProperty, self).__init__(**kwds)
如果 default
(A) 未指定,或 (B) 指定为非 dict
,您想要做什么当然取决于您。这个简单的例子在情况 (B) 中没有做任何特别的事情——(所以例如 default=[]
仍然会导致问题)——但是在情况 (A) 中确实使用了一个新的 empy dict
.
或者,您可以尝试将任何提供的 default
值转换为新的 dict
(因此 []
或 {}
会生成一个新的空值 dict
,但许多其他值会引发异常):
kwds['default'] = dict(kwds.get('default', ()))
当然还有许多其他变体。