
在开发基于sqlalchemy的应用程序时,尤其是在自定义数据库列并结合模型继承时,开发者可能会遇到一些关于列初始化行为的困惑。一个常见的问题是,自定义列的__init__方法在模型启动时被多次调用,并且在后续调用中,其kwargs参数不再是空的,而是包含了某些预期的或非预期的值。
考虑以下一个使用Flask-SQLAlchemy构建的最小示例,其中定义了一个自定义列CustomColumn,它继承自sqlalchemy.Column,并在其__init__方法中设置了default值和comment属性:
from flask import Flask
from sqlalchemy import Column, INTEGER
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////tmp/test.db"
db = SQLAlchemy(app)
class CustomColumn(Column):
def __init__(self, *args, **kwargs):
# 在这里设置默认值,将覆盖可能传入的同名参数
kwargs["default"] = 0
super().__init__(*args, **kwargs)
self.comment = "My comment"
class BaseModel(db.Model):
__abstract__ = True
my_column = CustomColumn()
class MyModel(BaseModel):
id = Column("id", INTEGER(), primary_key=True)
if __name__ == '__main__':
app.run(debug=True) # 通常在开发环境使用debug=True在运行上述应用时,开发者可能会观察到CustomColumn.__init__方法被调用了两次。第一次调用时,kwargs参数如预期是空的。然而,第二次调用时,kwargs中却包含了"default": 0和"comment": "My comment"等值,这与开发者期望的每次都接收空kwargs的行为不符,也引发了对为何会重复调用的疑问。
要理解这种行为,我们需要深入了解SQLAlchemy的ORM(对象关系映射)映射过程以及它如何处理继承的模型和列。
第一次调用:基类定义时 当Python解释器处理BaseModel类的定义时,my_column = CustomColumn()这行代码会立即执行,实例化一个CustomColumn对象。此时,CustomColumn.__init__被首次调用,kwargs通常是空的(除非在my_column = CustomColumn(...)处显式传递了参数)。这个实例是BaseModel类级别的,用于定义抽象基类的结构。
第二次调用:子类映射时 当MyModel类被定义并由SQLAlchemy的ORM进行映射时,SQLAlchemy会处理模型的继承关系。对于从BaseModel继承的列(如my_column),SQLAlchemy不会直接使用BaseModel中定义的同一个CustomColumn实例。相反,为了确保每个子类拥有独立的列定义和配置,SQLAlchemy会为MyModel生成一个my_column的 副本。这个复制过程(或称为“克隆”/“重新实例化”)会再次触发CustomColumn.__init__方法的调用。
这种机制确保了即使父类和子类共享相同的列名,它们也可以拥有独立的配置,例如子类可以覆盖父类列的某些属性。
在第二次调用CustomColumn.__init__时,kwargs中包含的值,例如"default": 0和"comment": "My comment",实际上是SQLAlchemy在进行列复制时,从父类列定义中提取并传递给新实例的参数。
具体来说:
因此,第二次调用时的kwargs并非随机,而是SQLAlchemy为了确保继承的列能够正确地复制其原有配置而有意传递的参数。
理解了上述机制后,我们可以更好地管理自定义列的参数:
接受并利用这种行为: 这种重复调用和kwargs传递是SQLAlchemy设计的正常部分,旨在提供灵活的列继承。通常情况下,无需尝试阻止这种行为。
覆盖或合并参数:
class CustomColumn(Column):
def __init__(self, *args, **kwargs):
if "default" not in kwargs:
kwargs["default"] = 0
super().__init__(*args, **kwargs)
# ... 其他逻辑这种方式允许外部调用者通过传入default参数来覆盖你的自定义默认值,同时在你没有显式指定时提供一个回退。
避免在__init__中进行昂贵的副作用操作: 由于__init__可能会被多次调用,应避免在其中执行昂贵的数据库操作、网络请求或任何具有全局副作用的代码。__init__的主要职责应该是初始化对象的状态。
回到最初的示例代码:
class CustomColumn(Column):
def __init__(self, *args, **kwargs):
kwargs["default"] = 0 # 这里的赋值会覆盖任何传入或从父类继承的'default'值
super().__init__(*args, **kwargs)
self.comment = "My comment" # 这是一个自定义属性在这个实现中,kwargs["default"] = 0语句会确保无论kwargs在第二次调用时是否已经包含default键,它的值都将被强制设置为0。这正是覆盖父类或SQLAlchemy内部默认值的有效方式。self.comment = "My comment"则是一个在CustomColumn实例上设置的自定义属性,它与kwargs的传递机制相对独立,但如果SQLAlchemy在内部处理时也将其视为需要复制的元数据,它也可能以某种形式出现在后续的kwargs中。
SQLAlchemy中自定义列的__init__方法在继承场景下被多次调用,并且kwargs参数在第二次调用时非空,是ORM映射过程中的正常行为。第一次调用发生在基类定义时,第二次调用发生在子类映射时,目的是为子类创建独立的列副本。第二次调用时的kwargs包含了SQLAlchemy从父列定义中提取的默认参数。开发者应理解这一机制,并通过在__init__中显式赋值来覆盖或条件合并参数,以确保自定义逻辑的正确执行。避免在__init__中执行具有昂贵副作用的操作,以维护代码的健壮性和性能。
以上就是深入理解SQLAlchemy自定义列的初始化行为与kwargs处理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号