Python SQLAlchemy`Session.is_modified`返回`True`,即使对象列未被修改

Python SQLAlchemy`Session.is_modified`返回`True`,即使对象列未被修改,python,sqlalchemy,Python,Sqlalchemy,我正在尝试检测中的对象何时被修改。从文档中: 检测对象上基于列的属性是否具有 更改,并因此生成UPDATE语句,请使用 对象会话(实例)。已修改(实例, include_collections=False) 但是,我看到is_modified正在返回True,即使对象的列没有被修改,也没有UPDATE语句被发送到数据库。下面是一个工作示例和我正在使用的SQLAlchemy版本。你知道为什么会这样吗?我看了一眼会话的SQLAlchemy源代码。它被修改了,似乎使用了对象状态和历史记录 SQLAlc

我正在尝试检测中的对象何时被修改。从文档中:

检测对象上基于列的属性是否具有 更改,并因此生成UPDATE语句,请使用 对象会话(实例)。已修改(实例, include_collections=False)

但是,我看到
is_modified
正在返回
True
,即使对象的列没有被修改,也没有
UPDATE
语句被发送到数据库。下面是一个工作示例和我正在使用的SQLAlchemy版本。你知道为什么会这样吗?我看了一眼
会话的SQLAlchemy源代码。它被修改了
,似乎使用了
对象状态和历史记录

SQLAlchemy版本:

$ pip freeze | grep SQLAlchemy
SQLAlchemy==1.1.9
资料来源:

import logging
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
    backref,
    class_mapper,
    relationship,
    scoped_session,
    sessionmaker)
from sqlalchemy.orm.session import Session
from sqlalchemy import (
    create_engine,
    inspect,
    Column,
    Integer,
    ForeignKey,
    MetaData)


logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


Base = declarative_base()


class Parent(Base):
    __tablename__ = "parents"

    id = Column(Integer, primary_key=True)


class Child(Base):
    __tablename__ = "children"

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey("parents.id"))
    parent = relationship(
        "Parent",
        backref=backref("child", uselist=False),
        single_parent=True,
        cascade="all")


engine = create_engine("sqlite://", echo=True)
metadata = MetaData()
with engine.begin() as connection:
    metadata = Base.metadata
    metadata.create_all(connection)

session = scoped_session(sessionmaker(bind=engine))


@listens_for(Base, "before_update", propagate=True)
def before_update(mapper, connection, target):
    session = Session.object_session(target)

    if session.is_modified(target, include_collections=False):
        logger.info(
            "target of type %s with id %s was modified",
            type(target).__name__,
            getattr(target, "id", None))

        inspr = inspect(target)
        attrs = class_mapper(target.__class__).column_attrs
        for attr in attrs:
            hist = getattr(inspr.attrs, attr.key).history
            logger.info(
                "attribute %s has changes: %s",
                attr.key,
                hist.has_changes())


parent = Parent()
session.add(parent)
session.flush()
child = Child(parent=parent)
session.add(child)
session.flush()
相关输出:

2017-11-29 18:07:28,471 INFO sqlalchemy.engine.base.Engine COMMIT
INFO:sqlalchemy.engine.base.Engine:COMMIT
2017-11-29 18:07:28,476 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
2017-11-29 18:07:28,477 INFO sqlalchemy.engine.base.Engine INSERT INTO parents DEFAULT VALUES
INFO:sqlalchemy.engine.base.Engine:INSERT INTO parents DEFAULT VALUES
2017-11-29 18:07:28,478 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2017-11-29 18:07:28,480 INFO sqlalchemy.engine.base.Engine SELECT children.id AS children_id, children.parent_id AS children_parent_id 
FROM children 
WHERE ? = children.parent_id
INFO:sqlalchemy.engine.base.Engine:SELECT children.id AS children_id, children.parent_id AS children_parent_id 
FROM children 
WHERE ? = children.parent_id
2017-11-29 18:07:28,480 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
INFO:__main__:target of type Parent with id 1 was modified
INFO:__main__:attribute id has changes: False
2017-11-29 18:07:28,483 INFO sqlalchemy.engine.base.Engine INSERT INTO children (parent_id) VALUES (?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO children (parent_id) VALUES (?)
2017-11-29 18:07:28,484 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)

编辑:将
Child
的backref重命名为
Parent
Parent
重命名为
Child

Parent
的列未被修改,但其关系已被修改,即
Parent.Parent
[sic]现在包含
Child
,当您将
child
添加到会话中时,我明白了--我认为
include\u collections=False
参数可以解释这一点。话虽如此,检查是否向数据库发送“更新”的最佳方法是什么?检查对象并迭代其所有列属性?
include_collections=False
表示它不查看集合,但
parent.child
不是集合,因为您指定了
uselist=False
。您只检查列属性的历史记录的方法应该有效——不管怎样,SQLAlchemy就是这样做的。@univerio您能写下来作为答案吗?