考虑以下代码创建一个非常简单的表(不使用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
是完全合法的,所以我想了解它是否有错误的行为或我身边的错误。
P.S。
P.P.S。这个问题似乎与特定的DB方言无关 - 用MySQL,sqlite转载。
答案 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)