微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

过滤方法的行为异常

如何解决过滤方法的行为异常

我正在尝试将类型提示引入现有代码库,但在尝试键入查询时遇到了问题。

from sqlalchemy.orm.query import Query

class DbContext:
    def __init__(self,db_host,db_port,db_name,db_user,db_password):

        engine = create_engine(...)

        session = sessionmaker(bind=engine)
        self.Session: Session = session(bind=engine)

...

def fetch(context: DbContext,filters: ...):
    sub_query: Query = context.Session.query(...)

在我添加类型提示之前,动态过滤很简单:

if filters.name is not None:
    sub_query = sub_query.filter(
        Person.name.ilike(f"%{filters.name}%"))

但是,现在提示我收到此错误

“None”类型的表达式不能分配给声明的“Query”类型

果然,filter 似乎返回了 None

方法)过滤器:(*标准:未知)-> 无

我导航到 the source,看起来该方法确实没有返回任何内容

def filter(self,*criterion):
    for criterion in list(criterion):
        criterion = expression._expression_literal_as_text(criterion)

        criterion = self._adapt_clause(criterion,True,True)

        if self._criterion is not None:
            self._criterion = self._criterion & criterion
        else:
            self._criterion = criterion

某处显然存在断开连接,因为将 None 分配给 sub_query 应该会导致提示警告的错误,但我需要执行分配以使过滤真正起作用:>

# Does NOT work,filtering is not applied
if filters.name is not None:
  sub_query.filter(
               Person.name.ilike(f"%{filters.name}%"))

# Works but Pylance complains
if filters.name is not None:
  sub_query = sub_query.filter(
               Person.name.ilike(f"%{filters.name}%"))

这是我第一次接触 Python,希望得到一些关于这里发生了什么的指导!

解决方法

您缺少两件事:

  • 您需要为 SQLAlchemy 安装 typing stubs
  • Query.filter() 方法有一个装饰器,用于定义返回的内容。

SQLAlchemy 的打字存根

您要安装 sqlalchemy-stubs project,它为 SQLAlchemy API 提供存根。

请注意,即使安装了这个存根,您仍然会看到 Pyright(支持 Pylance 扩展的检查工具)的问题,因为静态存根不能完全代表 SQLAlchemy API 某些部分的动态特性,例如模型列定义(例如,如果您的 Person 模型有一个名为 name 的列,用 name = Column(String) 定义,那么存根不能告诉 Pyright name一个字符串)。 sqlalchemy-stubs 项目包含一个用于 mypy 类型检查器的插件以更好地处理动态部分,但此类插件不能与其他类型检查器一起使用。

安装存根后,Pylance 可以告诉您filter

Visual Studio code screenshot with Query.filter intellisense information window

Query.filter() 装饰器详细信息

Query.filter() 方法实现实际上并不是对原始实例对象进行操作;它已用 decorator:

注释
    @_generative(_no_statement_condition,_no_limit_offset)
    def filter(self,*criterion):
        ...

@_generative(...) 部分在这里很重要; definition of the decorator factory 表明 filter() 方法基本上被这个包装方法取代:

    def generate(fn,*args,**kw):
        self = args[0]._clone()
        for assertion in assertions:
            assertion(self,fn.__name__)
        fn(self,*args[1:],**kw)
        return self

这里,fn 是原始的 filter() 方法定义,args[0] 是对 self 的引用,初始 Query 实例。所以 self 被调用 self._clone() 替换(基本上,创建一个新实例并复制属性),它运行声明的断言(这里,_no_statement_condition_no_limit_offset 是这样的断言),在在克隆上运行原始函数之前。

所以,filter() 函数的作用是改变克隆的实例,因此不必返回任何内容;这由 generate() 包装器处理。正是这种用实用程序包装器换出方法的技巧让 Pyright 误以为返回了 None,但安装存根后,它知道返回的是另一个 Query 实例。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。