【问题标题】:For ndb.JsonProperty, values leak from one instance to another对于 ndb.JsonProperty,值从一个实例泄漏到另一个实例
【发布时间】:2018-03-03 17:18:28
【问题描述】:

所以,我已经将我讨厌的问题缩小到这个......

class TestModel(ndb.Model):
    json1 = ndb.JsonProperty(default={})

entity1 = TestModel()
entity1.json1['val1'] = 'added via entity1'

entity2 = TestModel()
entity2.json1['val2'] = 'added via entity2'
logging.warn('entity2.json1 = {}'.format(entity2.json1))

在日志中,我看到了这个:

... entity2.json1 = {'val2': 'added via entity2', 'val1': 'added via entity1'}

令人惊讶且极其危险的是,我发现在第一个实例中设置的值 entity1 已泄漏到第二个实例 entity2 中。

我期望TestModel 的第二个实例化为我提供一个“干净”的实例是不合理的,特别是因为我有default={} 用于JsonProperty?我应该做一些我没有做的事情吗?或者这可能是 ndb 的一个错误?

更新:到目前为止我最好的解决方法:总是使用TestModel(json1={})。但我想我担心如果我们的一位开发人员忘记这样做,我们可能会将一位客户的数据泄露给另一位客户。

更新:似乎已经向 Google 报告了错误。 35898756 表明这种(错误)行为可能在请求之间发生。 3年前开业;仍在等待修复。

【问题讨论】:

  • 这是一个偷偷摸摸的 - 我只是在编写一些碰巧实例化和对象多次具有相似属性的测试时才注意到。这不是错误,它与任何其他具有可变默认值的函数 def 相同。 dict 似乎在请求之间自行重置,不确定这是 ndb 还是描述符创建方式的某些方面。我认为唯一的补救措施是保持警惕,或者可能是自定义 linter 规则。
  • 谢谢@snakecharmerb,很高兴听到我不是唯一一个。我是一个长期的 GAE 用户,直到最近才开始使用它。在过去,我必须一直在我的 JsonProperty 中设置相同的值。我没有意识到我正在替换值,而不是添加它们。
  • @snakecharmerb,我想了更多关于你的评论,它类似于函数 defs 中的 args 的默认值。这可能是它的方式,但不是必须的。理想情况下,当创建一个新实例时,ndb 应该分配copy.deepcopy(default)
  • 这很方便,但我仍然倾向于认为它不是一般情况下的错误。另一方面,我在远程 shell 中使用 JsonProperty 创建了一个模型,并在两个不同的命名空间中使用不同的键保存;第二个命名空间中的实体包含保存在第一个命名空间中的模型的键。我认为有一个更强(尽管不是 100%)的论点认为这是一个错误,因为跨命名空间泄漏的数据会破坏多租户合同。但如果谷歌不修复它,我猜你将不得不继承 JsonProperty 并自己实现 deepcopy :-)
  • 基本上无论你在哪里使用默认参数和可变数据结构,如字典或列表(例如default={}),你都会遇到麻烦。不仅与ndb。见docs.quantifiedcode.com/python-anti-patterns/correctness/…

标签: google-app-engine app-engine-ndb google-app-engine-python


【解决方案1】:

对于任何对此问题感兴趣的人,我已经确定了一种解决方法,可以让我晚上睡得更好。它不适合上面的评论,所以我正在回答我自己的问题(希望没关系)......

这似乎是一个已经存在 3 年的错误(请参阅 35898756),因此很可能不会很快得到修复。上面的解决方法包括执行TestModel(json1={}) always 或子类化JsonProperty 并使用我的自定义类always,而不是 ndb 的类(我将不得不重复所有其他类似的属性,如PickleProperty)。这些工作,但让我担心,因为项目中的每个开发人员都必须始终在代码库中的任何地方做正确的事情。哈!

因此,这里有一个解决方法,这意味着“做正确的事”仅本地化到我的模型(要担心的代码要少得多)。

class TestModel(ndb.Model):
    json1 = ndb.JsonProperty(default={})

    def __init__(self, **kwargs):
        kwargs.setdefault('json1', {})     # <---- ADDED THIS!
        super(TestModel, self).__init__(**kwargs)

在我的模型的构造函数中,如果它不存在,请添加关键字 arg 以将属性设置为 {}。这似乎可以防止值从一个实例泄漏到另一个实例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-06
    • 2021-03-24
    • 2012-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    相关资源
    最近更新 更多