当尝试从作为实例属性的类继承时,就会出现问题。这个 mcve 重现了它,我将把下面问题的其余部分留给后代:
class A:
class SubA:
pass
a = A()
class B(a.SubA):
pass
mypy
输出:
Name 'a.SubA' is not defined
这通过了:
class A:
class SubA:
pass
class B(A.SubA):
pass
此相关问题中的示例几乎与
Flask-SQLAlchemy
在db
命名空间下提供声明性基类的作用完全相同。在该问题中,mypy
维护者声称他们不会支持该模式。
我的问题是,上述模式有什么不正确之处,导致
mypy
不支持它?特别是在大型项目(例如Flask-SQLAlchemy
)使用它的情况下。
此外,
Flask-SQLAlchemy
和mypy
的用户在其项目中管理此内容的最佳方式是什么?
这个问题与缺少
Flask-SQLAlchemy
存根无关。接受这一点,我遇到了这个问题。
请帮助我理解为什么以下内容不能按我的预期工作。
在我的环境中,我仅安装了
Flask-SQLAlchemy
和 mypy
。
我已将
mypy
配置为 ignore_missing_imports = True
。
A
mypy
跳过以下内容:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Widget(db.Model):
id = db.Column(db.Integer, primary_key=True)
揭示:
error: Name 'db.Model' is not defined
因此,我尝试对
SQLAlchemy
进行子类化,为 Model
提供注释,该注释显示在对象上的 __annotations__
中,但模块的 mypy
分析并没有改变:
from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import DefaultMeta
class TypedSQLAlchemy(SQLAlchemy):
Model: DefaultMeta
db = TypedSQLAlchemy()
print(db.__annotations__) # {'Model': <class 'flask_sqlalchemy.model.DefaultMeta'>}
class Widget(db.Model):
id = db.Column(db.Integer, primary_key=True)
当我执行文件时,
print(db.__annotations__)
命令显示{'Model': <class 'flask_sqlalchemy.model.DefaultMeta'>}
,但仍然mypy
有相同的错误:
error: Name 'db.Model' is not defined
我希望为
db.Model
提供注释应该可以消除该错误。
我最初误解了该错误,因为它并不表明属性
Model
不存在于 db
上,它表明名称 db.Model
不存在于命名空间中。但为什么它将 db.Model
视为全名,而不将 db
视为本地定义的名称,并将 Model
视为其属性?这与尝试从类变量继承有关吗?
另外,我的注释不正确,应该是:
class TypedSQLAlchemy(SQLAlchemy):
Model: Type[DefaultMeta]
您应该使用
A.SubA
。
据我所知,从
mypy
的角度来看,不允许通过实例变量访问嵌套类。因为从 A
派生的类可以覆盖嵌套类,并且 mypy 无法识别这种情况,如下所示:
class A:
class SubA:
pass
class C(A):
class SubA:
pass
def foo(a: A):
class B(a.SubA): # What SubA here ?
pass
foo(C())
更新:
对于 Flask-SQLAlchemy,在 this 讨论中建议了以下解决方法:
from app import db
from sqlalchemy.ext.declarative import DeclarativeMeta
BaseModel: DeclarativeMeta = db.Model
class MyModel(BaseModel): ...
如果您使用flask_sqlalchemy,那么您可以使用 from
而不是flask_sqlalchemy.model import DefaultMeta
。DeclarativeMeta