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

当然还有许多其他变体。