2017-04-18 260 views
1

考虑下面的代码创建了一个非常简单的表(不使用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 

人们可以想到的是上面的代码只会打印'无',因为'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.用SQLAlchemy测试1.1.9

P.P.S.这个问题似乎没有涉及到特定的DB方言 - 使用MySQL,sqlite转载。

回答

2

这是一个比SQLAlchemy问题更多的Python版本问题。根本原因是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。如果你改变你的print语句

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 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)