SQLAlchemy 动态映射

13 投票
2 回答
20080 浏览
提问于 2025-04-15 21:13

我遇到了以下问题:

我有一个类:


class Word(object):

    def __init__(self):
        self.id = None
        self.columns = {}

    def __str__(self):
        return "(%s, %s)" % (str(self.id), str(self.columns))

self.columns 是一个字典,用来存储 (列名:列值) 的值。列的名字在运行时就已经知道了,它们会被加载到一个叫 wordColumns 的列表中,比如说:


wordColumns = ['english', 'korean', 'romanian']

wordTable = Table('word', metadata,
                  Column('id', Integer, primary_key = True)
                  )
for columnName in wordColumns:
    wordTable.append_column(Column(columnName, String(255), nullable = False))

我甚至创建了一个明确的映射属性,想要“强制”把表的列映射到 word.columns[columnName],而不是 word.columnName。我在映射时没有遇到任何错误,但似乎这并没有奏效。


mapperProperties = {}
for column in wordColumns:
    mapperProperties['columns[\'%']' % column] = wordTable.columns[column]

mapper(Word, wordTable, mapperProperties)

当我加载一个 word 对象时,SQLAlchemy 会创建一个对象,这个对象有 word.columns['english']、word.columns['korean'] 等属性,而不是把它们加载到 word.columns 字典里。所以对于每一列,它都会创建一个新的属性。此外,word.columns 字典甚至根本不存在。

同样,当我尝试保存一个 word 时,SQLAlchemy 期待在名为 word.columns['english'](字符串类型)的属性中找到列值,而不是在字典 word.columns 中。

我得说,我对 Python 和 SQLAlchemy 的经验相当有限,也许我尝试做的事情根本就不可能。

任何帮助都非常感谢,

提前谢谢你。

2 个回答

3

我用了nosklo的解决方案(谢谢他!),但我在CSV文件的第一行已经有一个主键(通过pk_col传入)。所以我想分享一下我的修改。我用了一个三元运算符。

table = Table(tablename, metadata,
    *((Column(pk_col, Integer, primary_key=True)) if rowname == pk_col else (Column(rowname, String())) for rowname in row.keys()))
table.create()
33

看起来你可以直接使用属性,而不是用 columns dict

考虑以下的设置:

from sqlalchemy import Table, Column, Integer, Unicode, MetaData, create_engine
from sqlalchemy.orm import mapper, create_session

class Word(object):
    pass

wordColumns = ['english', 'korean', 'romanian']
e = create_engine('sqlite://')
metadata = MetaData(bind=e)

t = Table('words', metadata, Column('id', Integer, primary_key=True),
    *(Column(wordCol, Unicode(255)) for wordCol in wordColumns))
metadata.create_all()
mapper(Word, t)
session = create_session(bind=e, autocommit=False, autoflush=True)

有了这个空的类,你可以这样做:

w = Word()
w.english = u'name'
w.korean = u'이름'
w.romanian = u'nume'

session.add(w)
session.commit()

当你想要访问数据时:

w = session.query(Word).filter_by(english=u'name').one()
print w.romanian

这就是 sqlalchemy 的 ORM 的全部意义,你不需要用 tupledict 来访问数据,而是可以在你自己的类上使用 像属性一样的访问方式

所以我在想,为什么你还想用 dict 呢?也许是因为你有语言名称的字符串。其实你可以用 Python 的 getattrsetattr,就像在任何 Python 对象上一样:

language = 'korean'
print getattr(w, language)

这样应该能解决你所有的问题。


不过,如果你还是想用 dict 风格来访问列,也是可以的。你只需要实现一个 dict 风格的对象。我现在会提供代码来实现这个,尽管我觉得这完全是多余的,因为属性访问方式非常简洁。如果你用上面的方法已经解决了问题,就不要使用下面的代码。

你可以在 Word 类上这样做:

class Word(object):
    def __getitem__(self, item): 
        return getattr(self, item)
    def __setitem__(self, item, value):
        return setattr(self, item, value)

其他的设置和上面一样。然后你可以这样使用:

w = Word()
w['english'] = u'name'

如果你想要一个 columns 属性,那么你需要一个 dict 风格的对象。

class AttributeDict(DictMixin):
    def __init__(self, obj):
        self._obj = obj
    def __getitem__(self, item):
        return getattr(self._obj, item)
    def __setitem__(self, item, value):
        return setattr(self._obj, item, value)

class Word(object):
    def __init__(self):
        self.columns = AttributeDict(self)

然后你就可以像你想的那样使用:

w = Word()
w.columns['english'] = u'name' 

我想你会同意,这一切都显得不必要地复杂,而且没有任何额外的好处。

撰写回答