SQLAlchemy

时间:2017-04-18 14:36:34

标签: python sqlalchemy

考虑以下代码创建一个非常简单的表(不使用SQLAlchemy),然后使用SQLAlchemy ORM向其添加一个条目并检索它:

import sqlite3
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DB_PATH = '/tmp/tst.db'

#create a DB
sqlite_conn = sqlite3.connect(DB_PATH)
sqlite_conn.execute('''CREATE TABLE tst (
    id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
    c0 INTEGER,
    c1 INTEGER
);''')
sqlite_conn.commit()

#intialize an SA engine/session/mapped class
engine = create_engine('sqlite:///{}'.format(DB_PATH))
Base = declarative_base()
Base.metadata.reflect(bind=engine)
Session = sessionmaker(bind=engine)

class Tst(Base):
    __table_name__ = 'tst'
    __table__ = Base.metadata.tables[__table_name__]
    columns = list(__table__.columns)
    field_names = [c.name for c in columns]

#add an entry to the table
session = Session()
inst = Tst()
session.add(inst)
session.commit()

#retrieve an entry from the table
session = Session()
inst = session.query(Tst).first()
print inst.c1

可以预期上面的代码只会打印'None',因为'c1'没有赋值。而不是它,我收到以下错误消息:

Traceback (most recent call last):
  File "...", line 39, in <module>
    print inst.c1
AttributeError: 'Tst' object has no attribute 'c1'

但是如果删除/评论以下行:

    field_names = [c.name for c in columns]

输出将按预期进行。

通常,类定义中的Table.columns上的迭代看起来会导致从类实例中省略最后一列。

关注this answer后,我实际上将代码更改为使用Inspector,并且工作正常。但是,AFAIK,访问Table.columns是完全合法的,所以我想了解它是否有错误的行为或我身边的错误。

使用SQLAlchemy 1.1.9测试

P.S。

P.P.S。这个问题似乎与特定的DB方言无关 - 用MySQL,sqlite转载。

1 个答案:

答案 0 :(得分:2)

这更像是一个Python版本问题,而不是SQLAlchemy问题。根本原因是Python 2中的leaking of the name c from the list-comprehension。它成为构造类的命名空间的一部分,因此SQLAlchemy将其视为explicitly naming the last column in the list columns in your class definition。您的类定义等同于:

class Tst(Base):
    __table_name__ = 'tst'
    __table__ = Base.metadata.tables[__table_name__]
    columns = list(__table__.columns)
    ...
    c = columns[-1]  # The last column of __table__

如果您将print语句更改为:

print inst.c

您将按预期获得None。如果您必须拥有field_names,则可以从名称空间中删除名称:

class Tst(Base):
    __table_name__ = 'tst'
    __table__ = Base.metadata.tables[__table_name__]
    columns = list(__table__.columns)
    field_names = [c.name for c in columns]
    del c

但是这在Python 2和3之间是不可移植的(并且很丑陋),因为该名称实际上不存在于3.您还可以解决attrgetter()的问题:

from operator import attrgetter

class Tst(Base):
    __table_name__ = 'tst'
    __table__ = Base.metadata.tables[__table_name__]
    columns = list(__table__.columns)
    field_names = list(map(attrgetter('name'), columns))

或使用生成器表达式:

    field_names = list(c.name for c in columns)