为什么str()与type()返回db.Column类型名不同?动态创建Enum为何报错?
问题:Flask SQLAlchemy 中
str(db.Column)与type(db.Column)返回结果差异原因 场景复现
我正在学习Flask SQLAlchemy,按常规方式完成SQLAlchemy初始化:
db = SQLAlchemy(app=app)
随后通过类定义的方式声明数据表,代码如下:
class Table1(db.Model): col1 = db.Column(db.Integer)
我通过如下代码查看db.Column的类型:
str(db.Column) # 输出: "<class 'sqlalchemy.sql.schema.Column'>" type(db.Column) # 输出: <class 'sqlalchemy.sql.visitors.TraversibleType'>
二者返回的类型结果为什么存在差异?
补充内容(2022年6月21日 12:17)
假设存在如下基础类定义:
class C: pass
我原本认为以下两段代码是等价的,仅语法形式不同:
class X(C): pass
以及:
X = type('X', (C,), {})
但当操作对象为Enum时,使用type动态创建类会触发错误,复现代码如下:
from enum import Enum class X1(Enum): # 该写法运行正常 pass X2 = type('X2', (Enum,), {}) # 该写法触发错误
具体报错信息为:
... File "/usr/lib/python3.8/enum.py", line 175, in __new__ enum_members = {k: classdict[k] for k in classdict._member_names} AttributeError: 'dict' object has no attribute '_member_names'
上述报错的产生原因是什么?
解答
1. str(db.Column)与type(db.Column)返回值差异原因
这是Python元类的基础特性,和SQLAlchemy本身的逻辑没有冲突:
- 首先明确:
db.Column本身是一个类,不是实例。你写col1 = db.Column(db.Integer)的时候才是在实例化这个类,生成实际的列对象。 str(类对象)返回的是类的可读字符串表示。SQLAlchemy对Column类的字符串输出做了定制,返回它逻辑上的模块路径sqlalchemy.sql.schema.Column,方便开发者识别。type(类对象)返回的是创建这个类的元类。举个最基础的例子:你执行type(int)、type(str),返回的都是<class 'type'>,因为Python内置的int、str类都是由默认元类type创建的。SQLAlchemy为了实现SQL语法树的遍历访问能力,给所有SQL结构相关的类都实现了自定义元类TraversibleType,Column类就是由这个元类创建的,所以type(db.Column)会返回这个自定义元类。
2. 动态创建Enum子类报错的原因
你之前认为class定义类和直接调用type创建类完全等价,这个结论只在父类使用默认元类type时成立。一旦父类使用了自定义元类,这个等价关系就不成立:
Enum类本身使用自定义元类EnumMeta实现所有枚举逻辑。当你用class关键字定义Enum子类时,Python不会死板地调用默认type创建类,而是会自动查找所有父类用到的元类,选择优先级最高的元类(这里就是EnumMeta)来执行类创建流程。EnumMeta在创建类时会自动生成带_member_names属性的特殊命名空间,用来存储枚举成员,所以正常class定义的Enum不会报错。- 你直接调用原生
type()创建Enum子类时,完全绕过了EnumMeta的类创建逻辑,传入的是普通Python字典,没有EnumMeta要求的_member_names属性,自然会触发属性错误。 - 如果需要动态创建Enum子类,要么显式调用它的元类,要么用Enum官方提供的函数式API:
from enum import Enum, EnumMeta # 方式1:显式调用元类 X2 = EnumMeta('X2', (Enum,), {}) # 方式2:使用Enum函数式API X2 = Enum('X2', {})
内容的提问来源于stack exchange,提问作者smwikipedia
相关产品推荐
相关产品推荐

