【问题标题】:Fields of SQLAlchemy models filled after creating new object创建新对象后填充的 SQLAlchemy 模型的字段
【发布时间】:2020-05-25 07:57:38
【问题描述】:

我正在使用带有 SQLAlchemy 的 Flask 框架创建一个 Python 应用程序,现在在创建新模型对象时遇到一个奇怪的行为。

假设我有一个模型 Offer,其中包含一些字段、一个私有属性 __validation_errors 和一个公共方法 validate

from app import db

class Offer(db.Model):
    currency = db.Column(db.String(255))
    price = db.Column(db.Numeric(20, 5))
    __validation_errors = []

    def validate(self, errors: List[Exception]) -> bool:
        self.validate_not_empty()
        self.validate_price()
        for err in self.__validation_errors:
            errors.append(err)
        if len(self.__validation_errors) > 0:
            return False
        return True

对象本身是通过调用在服务类中创建的

offer = Offer(currency, price)

并提供在路由器中通过 POST 请求接收到的数据。 如果验证失败,则会向用户返回一条错误消息,并且该对象不会写入 DB。

从这里开始奇怪的部分。如果接收到包含无效数据的请求并发送另一个包含正确数据的请求,则 __validation_errors 列表中的值不会在 offer 对象中消失,就像该对象被重用且未重置一样。如果发出另一个无效请求,则错误只会附加到列表中已经存在的错误。当然,可以通过每次调用validate()方法时设置__validation_errors = []来修复,但我想了解这里发生了什么。我错过了一些 SQLAlchemy 的特定功能吗? app.db 对象和 Session 都是在 app 上创建的,并且一直存在。

【问题讨论】:

  • 我不完全确定这是否是你的问题,但因为__validation_errors 是类声明的一部分,并且它没有被替换(只是appended to),@987654329 @ 将与Offer.__validation_errors 相同(即它在类的所有实例之间共享)。您可能希望在 __init__ 方法中创建它,例如:def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs); self.__validation_errors = []
  • @ChristianReall-Fluharty 感谢您的回复。我对 Python 很陌生,可能对类声明还不够熟悉。如果确实如此,那么如果没有明确设置类中声明的属性是共享的,那么它应该是它......我通过在调用验证方法之前将其设置为 [] 来解决它。在__init__ 中将其设置为 [] 可能也可以。
  • 是的,所以在 class ...: 的主体中声明/创建的任何内容都是类本身的一部分,就像 C / Java 中的静态变量一样。而对于 C / Java,您要声明每个实例将具有的属性(因为编译器需要知道每个类在内存中的大小)。当您考虑到函数是 python 中的对象这一事实时,这是有道理的,因此通过将它们放在类声明中,它们是所有实例之间共享的方法。
  • 而且您并没有明确地设置它们,而是您正在用对实例属性的引用替换对类属性的引用(您应该查找 python pass-by -value vs pass-by-reference 了解为什么重置 __validation_errors 修复它[使它成为实例属性而不是类属性],如果您还不熟悉它)
  • @ChristianReall-Fluharty,谢谢。最后两件事是已知的,但不知道如何共享实例之间共享的方法和属性。如果您将其添加为答案,我会接受。

标签: python flask sqlalchemy


【解决方案1】:

所以这里的麻烦在于__validation_errors绑定到Offer,而不是Offer的实例,这意味着除非每个实例都设置自己的self.__validation_errorsself.__validation_errors 只是为每个实例映射到 Offer.__validation_errors(有点像访问仅存在于父类中的子类的属性/函数)。

举个例子:

>>> class SomeClass:
...     some_list = []
... 
>>> a = SomeClass()
>>> b = SomeClass()
>>> a.some_list.append(5)
>>> a.some_list
[5]
>>> b.some_list
[5]
>>> a.some_list is b.some_list
True

这是因为类主体中的任何内容都是类本身的属性(考虑到方法也是类属性这一事实,这更有意义 - 与基于原型的语言不同),而不是每个实例的值创建后赋值

>>> class SomeClass:
...     x = [1, 2, 3, 4, 5]
...
>>> SomeClass.x
[1, 2, 3, 4, 5]
>>> SomeClass().x
[1, 2, 3, 4, 5]
>>> SomeClass.x is SomeClass().x
True

如果要将属性绑定到实例而不是类,请将其附加到self,最好是__init__

class SomeClass:
    def __init__(self):
        self.x = 5

PS,值得注意的是,只有在使用可变属性时才会真正注意到这一点。使用不可变属性,任何“更改”都会在对象上创建一个新值,并且由于只要存在<instance>.<attribute> 而不是<instance>.__class__.<attribute>,就会检索到它,因此看起来该实例与开始的类是分开的。


所以,回到你的例子,我建议在初始化 __validation_errors 的同时 __init__ialize 对象的其余部分:

class Offer(db.Model):
    ...
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__validation_errors = []

    def validate(self, errors: List[Exception]) -> bool:
        ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-26
    • 2010-12-22
    • 1970-01-01
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 2017-05-19
    相关资源
    最近更新 更多