SQLAlchemy很复杂吗?

35

这可能听起来有点争论,但我刚刚完成了SQLAlchemy的ORM教程,最终得到了以下代码:

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)

metadata = MetaData()
users_table = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('fullname', String),
    Column('password', String)
)

metadata.create_all(engine)

Base = declarative_base()
class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

    def __repr__(self):
       return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

users_table = User.__table__
metadata = Base.metadata

Session = sessionmaker(bind=engine)
Session = sessionmaker()
Session.configure(bind=engine)  # once engine is available
session = Session()

# actually using the ORM isn't too bad..
ed_user = User('ed', 'Ed Jones', 'edspassword')
session.add(ed_user)

our_user = session.query(User).filter_by(name='ed').first() 
print our_user

session.add_all([
    User('wendy', 'Wendy Williams', 'foobar'),
    User('mary', 'Mary Contrary', 'xxg527'),
    User('fred', 'Fred Flinstone', 'blah')])

ed_user.password = 'f8s7ccs'

print session.dirty
print session.new
session.commit()

for instance in session.query(User).order_by(User.id): 
    print instance.name, instance.fullname

for name, fullname in session.query(User.name, User.fullname): 
    print name, fullname

这似乎对于一个基本的“Hello World”表格来说过于复杂了,特别是与大致相似的SQLObject代码相比:
from sqlobject import SQLObject, StringCol, sqlhub, connectionForURI

sqlhub.processConnection = connectionForURI('sqlite:/:memory:')

class Person(SQLObject):
    fname = StringCol()
    mi = StringCol(length=1, default=None)
    lname = StringCol()

Person.createTable()

p = Person(fname="John", lname="Doe")
p.mi = 'Q'
p2 = Person.get(1)
print p2
print p2 is p

我了解SQLAlchemy更为强大,但这种强大似乎是有代价的,或者我漏掉了什么?


8
权力是有代价的?你在说什么? - S.Lott
17
SQLAlchemy的强大之处在于它的简单易用性付出了代价吗? - dbr
4
尝试如下所述的Elixir,你的Hello World将非常类似于SQLObject,并且仍然可以访问SQLAlchemy层。我曾使用过SQLObject并对其限制感到沮丧,但目前为止我非常满意Elixir(尽管它缺乏一些文档和支持,但不幸的是用户基础似乎有限)。 - Luper Rouch
不能简单地在语句后面添加一个问号。 - bukzor
6个回答

88

你可能忽略了一件事情:你提到的教程并没有“构建”一个完整的示例,不同的代码片段并不意味着要连接成一个源文件。相反,它们描述了库可以使用的不同方式。不需要自己一遍又一遍地尝试做同样的事情。

如果从你的示例中省略实际使用ORM部分,代码可能如下所示:

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base(bind=engine)
Session = scoped_session(sessionmaker(engine))

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

Base.metadata.create_all()
"声明式"扩展会定义表格并将其映射到你的类上,因此你不需要自己声明users_table。User类还允许使用关键字参数来实例化,比如User(name="foo")(但不能使用位置参数)。我还增加了对scoped_session的使用,这意味着你可以直接使用Session而无需实例化它(如果当前线程中不存在会实例化一个新的session,否则会重用现有的session) 。"

3
那看起来比我最终写出来的代码更合理。谢谢! - dbr
1
我对使用scoped_session非常犹豫; 除非你知道为什么需要线程本地存储,否则应该显式实例化会话并根据需要传递它。 - SingleNegationElimination
1
@TokenMacGuy 我完全不同意。scoped_session消除了使用会话时所有猜测和不必要的参数传递。你只需要在开始时定义你的scoped_session类,然后在需要访问会话的任何地方实例化它,系统就会完成剩下的工作。我发现它对我编写的所有Web应用程序都非常有用。 - coredumperror
5
对于Web应用程序而言,“scoped_session”可能非常适合;只要您的应用程序每个请求需要恰好一个会话,并且您正在使用单线程模型处理每个请求。如果这些条件不成立,您可能需要在请求上下文之外操作数据库;如果您需要跨多个数据库协调事务,如果您以异步方式执行工作以提高性能,那么这种假设就开始失效了。 - SingleNegationElimination
需要在模块范围内定义引擎、基础和会话吗?对于导入此模块的其他模块来说,这有点粗糙。他们不需要了解用户内部ORM成员的情况。我也不太明白为什么每个模型模块都需要重新实例化引擎和基础。 - Mark E. Haase
Sqlalchemy的问题在于它仍然很复杂。模块结构混乱不堪,文档对于没有经验的人来说非常不清晰,开发者被迫写大量的样板代码,这些代码似乎没有任何可辨认的原因,只是因为缺乏架构监督。如果这个包被良好地设计,那么这个例子只需要几行导入和一个类定义就可以了。不需要进一步的内容来定义一个表。 - Ninjakannon

10
你提供的代码示例并不完全相同。SQLAlchemy版本可以简化一下:

你提供的代码示例并不是完全相同的。SQLAlchemy版本可以稍微精简一下:

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)
    fullname = Column('fullname', String)
    password = Column('password', String)

    def __repr__(self):
       return "" % (self.name, self.fullname, self.password)

Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

# actually using the ORM isn't too bad..
ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')
session.add(ed_user)

our_user = session.query(User).filter_by(name='ed').first()

session.add_all([
    User(name='wendy', fullname='Wendy Williams', password='foobar'),
    User(name='mary', fullname='Mary Contrary', password='xxg527'),
    User(name='fred', fullname='Fred Flinstone', password='blah')])

ed_user.password = 'f8s7ccs'

session.flush()

for instance in session.query(User).order_by(User.id):
    print instance.name, instance.fullname

for name, fullname in session.query(User.name, User.fullname):
    print name, fullname

你可能会发现Elixir更像SQLObject(但由于我都没有使用过,这只是一个猜测)。

我完全没有使用过SQLObject,无法评论SA到底做得更好在哪里。但我在处理复杂的、现实的、遗留的架构时,使用SA的经验非常棒。它默认情况下可以很好地生成SQL查询,并有很多调整它们的方法。

我发现SQLAlchemy作者的电梯演讲在实践中表现得很好。


1

试试快速ORM,它更简单:

from quick_orm.core import Database
from sqlalchemy import Column, String

class User(object):
    __metaclass__ = Database.DefaultMeta
    name = Column(String(30))

if __name__ == '__main__':
    database = Database('sqlite://')
    database.create_tables()

    user = User(name = 'Hello World')
    database.session.add_then_commit(user)

    user = database.session.query(User).get(1)
    print 'My name is', user.name

Quick ORM是基于SQLAlchemy构建的,因此我们可以说SQLAlchemy可能就像SQLObject一样简单。


很抱歉,quick_orm不再维护了。 - Tyler Liu

1

好的,SQLAlchemy被分成不同的部分,主要核心部分仅处理数据库,将您的Python构建查询转换为底层数据库适当的SQL语言。然后有会话支持、ORM和新的声明性语法。

看起来像SQLObject(我不能确定,多年来没有使用过,即使那时只用过一次)跳过了大部分内容,直接进行ORM部分。这通常使得简单数据更容易(在大多数情况下可以轻松完成),但是SQLAlchemy允许更复杂的数据库布局,并且如果您确实需要它,可以深入了解数据库。


SQLObject似乎支持更高级的功能,比如一对多/多对一/多对多关系(涵盖了我见过的每种数据库布局)和事务处理。 - dbr

1

0

你说“复杂”……别人可能会说“灵活”。有时候你需要它,有时候你不需要。拥有选择的权利难道不是很棒吗?


2
当然可以,但是我说“复杂”是因为它似乎强制你使用很多它的功能来完成基本操作。灵活性很好,但如果要花费五行导入语句才能开始,就不适用了! - dbr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接