2017-04-18 327 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)