可以在SQLalchemy中使用命名元组吗?

7 投票
2 回答
4840 浏览
提问于 2025-04-17 03:34

我一直在尝试让一个命名元组(namedtuple)与SQLalchemy一起工作,但一直没有成功。网上搜索也没有找到有用的信息,而且我对Python和SQLalchemy还很陌生,所以我不太确定自己是不是在做无用功。基本的想法是我有一个命名元组,比如:

Point=namedtuple('Point',['x','y'])

这基本上创建了一个名为Point的类(tuple),如果我没理解错的话。最开始这没问题,我可以创建像这样的对象:

p=Point(3,4)

但是在我创建了数据库引擎等并调用映射器(mapper)之后,我就无法再创建任何对象了,系统会报错:

Traceback (most recent call last):
  File "<pyshell#62>", line 1, in <module>
    f=Point(3,4)
TypeError: __init__() takes exactly 1 argument (3 given)

有没有人知道为什么会这样?有没有人知道怎么让命名元组与SQLalchemy一起使用?当然,我可以自己定义一个Point类,但我现在就是想让命名元组能用起来。

我使用的是Python 2.7,SQLalchemy 0.6.6(sqlite引擎)。

示例:

我尝试做这样的事情:

from sqlalchemy import *
from sqlalchemy.orm import *
from collections import namedtuple

Point=namedtuple('Point',['x','y'],verbose=True)
p=Point(3,4)


db=create_engine('sqlite:///pointtest.db')
metadata=MetaData()
pointxy=Table('pointxy',metadata,
              Column('no',Integer,primary_key=True),
              Column('x',Integer),
              Column('y',Integer),
              sqlite_autoincrement=True)
metadata.create_all(db)
m=mapper(Point, pointxy)
Session=sessionmaker(bind=db)
session=Session()
f=Point(3,4)

我的主要想法是我想要一个可以轻松存储在数据库中的命名集合。所以这个:

class Bunch:
    __init__ = lambda self, **kw: setattr(self, '__dict__', kw)

我觉得是不能和SQLalchemy一起用的。我可以创建一个Bunch类,但我不知道我想在集合中存储多少个整数……我会在创建数据库之前设置好这个数量。希望我说得清楚。

2 个回答

1

这个映射器好像会添加一个 _init_ 方法。所以在映射器语句之后做以下操作就能让它再次正常工作:

del Point.__init__

我不太确定用映射器来处理这种情况是否合适。因为映射器很可能需要主键('no')才能正常工作,而你现在的命名元组里没有这个空间。

5

命名元组有一些特性,使得它们不太适合用来和sqlalchemy进行映射。最重要的一点是,命名元组在创建后是不能被修改的。这意味着在进行数据库插入操作后,你不能用命名元组来跟踪数据库中某一行的状态。通常你想做的事情是这样的:

class MyDataHolder(namedtuple('MyDataHolder', ('id', 'my_value')):
    pass

mapper(MyDataHolder, MyDataMeta)

...

newRow = MyDataHolder(None, 'AAA')

...

session.add(newRow)

当会话代码执行SQL语句将新数据添加到数据库时,它会想要更新newRow,使得newRow.id对应数据库分配给你这一行的id。但是因为newRow是一个不可变的元组,id就无法被更改为数据库返回的主键。这使得命名元组在映射器中基本上不太适用。

__init__的问题发生在命名元组是在__new__中初始化的,并且不期望它们会改变。__init__()是在对象创建后被调用的,因此没有效果。因此,命名元组的__init__只定义了一个参数:self。我猜测映射器假设__init__()处理类的初始化,而不知道__new__和不可变类型的存在。看起来他们在创建时调用classname.__init__(),并传入创建时的参数。你可以通过指定自己的初始化函数来“修复”这个问题:__init__(self, *args),但这样你又会遇到弱引用的问题。

弱引用错误发生是因为命名元组使用__slots__来存储它们的值,而不是可变的__dict__。我知道使用__slots__是一种内存优化,这样你可以高效地存储大量的命名元组。我假设命名元组在创建后不会改变,这包括添加属性,因此使用__slots__是值得的内存优化。不过,我并不理解为什么命名元组类的作者没有支持弱引用。这并不是特别困难,但可能有我没想到的很好的理由。

今天早上我遇到了这个问题,并通过定义自己的数据映射类来解决,它是用一个_fieldset属性初始化的。这个属性指定了我感兴趣的字段子集。然后我有时间阅读了命名元组的实现文档以及其他一些Python内部的内容。我想我大部分理解了为什么这样不行,但我相信有一些Python专家对这个问题的理解会比我更深入。

-- Chris

撰写回答