类型提示 sqlalchemy 查询结果

Type hinting sqlalchemy query result

我无法弄清楚 sqlalchemy 查询的对象类型 returns。

entries = session.query(Foo.id, Foo.date).all()

条目中每个对象的类型似乎是 sqlalchemy.util._collections.result,但 python 解释器中的快速 from sqlalchemy.util._collections import result 会引发 ImportError。

我最终要做的是键入提示此函数:

def my_super_function(session: Session) -> ???:
    entries = session.query(Foo.id, Foo.date).all()
    return entries

我应该用什么来代替 ???? mypy(在这种情况下)似乎可以使用 List[Tuple[int, str]] 因为是的,我确实可以访问我的条目,就像它们是元组一样,但我也可以使用 entry.date 访问它们。

我也很奇怪 class 无法导入。答案很长,因为我已经向您介绍了我是如何计算出来的,请耐心等待。

Query.all()Query 对象本身上调用 list()

def all(self):
    """Return the results represented by this ``Query`` as a list.
    This results in an execution of the underlying query.
    """
    return list(self)

... 其中列表将遍历对象,因此 Query.__iter__():

def __iter__(self):
    context = self._compile_context()
    context.statement.use_labels = True
    if self._autoflush and not self._populate_existing:
        self.session._autoflush()
    return self._execute_and_instances(context)

... returns Query._execute_and_instances() 方法的结果:

def _execute_and_instances(self, querycontext):
    conn = self._get_bind_args(
        querycontext, self._connection_from_session, close_with_result=True
    )

    result = conn.execute(querycontext.statement, self._params)
    return loading.instances(querycontext.query, result, querycontext)

执行查询,returns sqlalchemy.loading.instances() function. In that function there is this line 的结果适用于非单一实体查询:

keyed_tuple = util.lightweight_named_tuple("result", labels)

... 如果我在该行之后粘贴 print(keyed_tuple),它会打印 <class 'sqlalchemy.util._collections.result'>,这是您上面提到的类型。所以无论那个对象是什么,它都来自 sqlalchemy.util._collections.lightweight_named_tuple() 函数:

def lightweight_named_tuple(name, fields):
    hash_ = (name,) + tuple(fields)
    tp_cls = _lw_tuples.get(hash_)
    if tp_cls:
        return tp_cls

    tp_cls = type(
        name,
        (_LW,),
        dict(
            [
                (field, _property_getters[idx])
                for idx, field in enumerate(fields)
                if field is not None
            ]
            + [("__slots__", ())]
        ),
    )

    tp_cls._real_fields = fields
    tp_cls._fields = tuple([f for f in fields if f is not None])

    _lw_tuples[hash_] = tp_cls
    return tp_cls

所以关键部分是this statement:

tp_cls = type(
    name,
    (_LW,),
    dict(
        [
            (field, _property_getters[idx])
            for idx, field in enumerate(fields)
            if field is not None
        ]
        + [("__slots__", ())]
    ),
)

... 根据文档调用内置的 type() class:

With three arguments, return a new type object. This is essentially a dynamic form of the class statement.

这就是为什么您不能导入 class sqlalchemy.util._collections.result - 因为 class 仅在查询时构建。我会说这样做的原因是列名(即命名的元组属性)在执行查询之前是未知的。

来自 python docs type 的签名是:type(name, bases, dict) 其中:

The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the __dict__ attribute.

如您所见,在lightweight_named_tuple()中传递给type()bases参数是(_LW,)。因此,任何动态创建的命名元组类型都继承自 sqlalchemy.util._collections._LW,这是一个可以导入的 class:

from sqlalchemy.util._collections import _LW

entries = session.query(Foo.id, Foo.date).all()
for entry in entries:
    assert isinstance(entry, _LW)  # True

...所以我不确定将您的函数键入带有前导下划线的内部 class 是否是一种好的形式,但是 _LW 继承自 sqlalchemy.util._collections.AbstractKeyedTuple,它本身继承自 tuple。这就是为什么您当前键入的 List[Tuple[int, str]] 有效,因为它 一个元组列表。因此,请选择,_LWAbstractKeyedTupletuple 都是函数返回内容的正确表示。

只需打印或记录 type(entries) 即可查看使用了哪种类型。无需通读模块代码。

如果不进行检查,return 可能与您使用 cursor.fetchall() 获得的典型记录相同。然后,类型就是 tuple - Python built-in tuple。您甚至不需要导入 tuple 即可在输入模块中使用 tuple

在没有测试的情况下写这个,然而,主要的技巧还是别的东西:使用 type(my_return_var) 查看类型提示的类型。

请注意,必须首先导入带有模块路径的class。

如何使用“技巧”的另一个示例:游标对象的类型提示,取自 。当type(...)的输出是<class 'MySQLdb.cursors.Cursor'>,那么你需要

  • from MySQLdb.cursors import CursorCursor 作为类型提示或
  • from MySQLdb import cursorscursors.Cursor 作为类型提示或
  • import MySQLdbMySQLdb.cursors.Cursor 作为类型提示。

获得正确打字类型的“技巧”也在