作为一个美妙的语言,python 除了 sqlalchemy 外还有很多orm库。在这篇文章里,我们将来看看几个流行的可选orm 库,以此更好地窥探到python orm 境况。通过写一段脚本来读写2个表 ,person 和 address 到一个简单的数据库,我们能更好地理解每个orm库的优缺点。
sqlobject
sqlobject 是一个介于sql数据库和python之间映射对象的python orm。得益于其类似于ruby on rails的activerecord模式,在编程社区变得越来越流行。首个 sqlobject在2002年十月发布。它遵循lgpl许可。
在 sqlobject 中,数据库概念是通过与 slqalchemy 非常类似的的一种方式映射到python的,表映射成类,行作为实例而字段作为属性。它同时提供一种基于python对象的查询语言,这使得sql 更加抽象, 从而为应用提供了数据库不可知性(译注:应用和数据库分离)
$ pip install sqlobjectdownloading/unpacking sqlobjectdownloading sqlobject-1.5.1.tar.gz (276kb): 276kb downloadedrunning setup.py egg_info for package sqlobject warning: no files found matching '*.html'warning: no files found matching '*.css'warning: no files found matching 'docs/*.html'warning: no files found matching '*.py' under directory 'tests'requirement already satisfied (use --upgrade to upgrade): formencode>=1.1.1 in /users/xiaonuogantan/python2-workspace/lib/python2.7/site-packages (from sqlobject)installing collected packages: sqlobjectrunning setup.py install for sqlobjectchanging mode of build/scripts-2.7/sqlobject-admin from 644 to 755changing mode of build/scripts-2.7/sqlobject-convertolduri from 644 to 755 warning: no files found matching '*.html'warning: no files found matching '*.css'warning: no files found matching 'docs/*.html'warning: no files found matching '*.py' under directory 'tests'changing mode of /users/xiaonuogantan/python2-workspace/bin/sqlobject-admin to 755changing mode of /users/xiaonuogantan/python2-workspace/bin/sqlobject-convertolduri to 755successfully installed sqlobjectcleaning up... >>> from sqlobject import stringcol, sqlobject, foreignkey, sqlhub, connectionforuri>>> sqlhub.processconnection = connectionforuri('sqlite:/:memory:')>>>>>> class person(sqlobject):... name = stringcol()...>>> class address(sqlobject):... address = stringcol()... person = foreignkey('person')...>>> person.createtable()[]>>> address.createtable()[]
上面的代码创建了2个简单的表:person 和 address 。为了创建和插入记录到这2个表,我们简单实例化一个person 实例和 一个 address 实例:
>>> p = person(name='person')>>> a = address(address='address', person=p)>>> p >>> a
为了获得或检索新记录, 我们用神奇的 q 对象关联到 person 和 address 类:
>>> persons = person.select(person.q.name == 'person')>>> persons >>> list(persons)[]>>> p1 = persons[0]>>> p1 == ptrue>>> addresses = address.select(address.q.person == p1)>>> addresses >>> list(addresses)[ ]>>> a1 = addresses[0]>>> a1 == atrue
storm
storm 是一个介于 单个或多个数据库与python之间 映射对象的 python orm 。为了支持动态存储和取回对象信息,它允许开发者构建跨数据表的复杂查询。它由ubuntu背后的公司 canonical公司用python开发的,用在 launchpad 和 landscape 应用中,后来在2007年作为自由软件发布。这个项目在lgpl许可下发布,代码贡献者必须受让版权给canonical公司。
像 sqlalchemy 和 sqlobject 那样, storm 也映射表到类,行到实例和字段到属性。相对另外2个库, stom中 table class 不需要是框架特定基类 的子类 。在 sqlalchemy中,每个 table class 是 sqlalchemy.ext.declarative.declarative_bas 的一个子类。 而在sqlojbect中,每个table class是 的 sqlobject.sqlobject 的子类。
类似于 sqlalchemy, storm 的 store 对象对于后端数据库就像一个代理人, 所有的操作缓存在内存,一当提交方法在store上被调用就提交到数据库。每个 store 持有自己的python数据库对象映射集合,就像一个 sqlalchemy session 持有不同的 python对象集合。
指定版本的 storm 可以从 下载页面 下载。在这篇文章里,示例代码是使用 0.20 版本的storm写的。
>>> from storm.locals import int, reference, unicode, create_database, store>>>>>>>>> db = create_database('sqlite:')>>> store = store(db)>>>>>>>>> class person(object):... __storm_table__ = 'person'... id = int(primary=true)... name = unicode()...>>>>>> class address(object):... __storm_table__ = 'address'... id = int(primary=true)... address = unicode()... person_id = int()... person = reference(person_id, person.id)...
上面的代码创建了一个 sqlite 内存数据库,然后用 store 来引用该数据库对象。一个storm store 类似 sqlalchemy的 dbsession对象,都管理 附属于其的实例对象 的生命周期。例如,下面的代码创建了一个 person 和 一个 address, 然后通过刷新 store 都插入记录。
>>> store.execute(create table person ... (id integer primary key, name varchar)) >>> store.execute(create table address ... (id integer primary key, address varchar, person_id integer, ... foreign key(person_id) references person(id))) >>> person = person()>>> person.name = u'person'>>> print person >>> print %r, %r % (person.id, person.name)none, u'person' # notice that person.id is none since the person instance is not attached to a valid database store yet.>>> store.add(person) >>> print %r, %r % (person.id, person.name)none, u'person' # since the store hasn't flushed the person instance into the sqlite database yet, person.id is still none.>>> store.flush()>>> print %r, %r % (person.id, person.name)1, u'person' # now the store has flushed the person instance, we got an id value for person.>>> address = address()>>> address.person = person>>> address.address = 'address'>>> print %r, %r, %r % (address.id, address.person, address.address)none, , 'address'>>> address.person == persontrue>>> store.add(address) >>> store.flush()>>> print %r, %r, %r % (address.id, address.person, address.address)1, , 'address'
为了获得或检索已插的 person 和 address 对象, 我们调用 store.find() 来查询:
>>> person = store.find(person, person.name == u'person').one()>>> print %r, %r % (person.id, person.name)1, u'person'>>> store.find(address, address.person == person).one() >>> address = store.find(address, address.person == person).one()>>> print %r, %r % (address.id, address.address)1, u'address'
django 的 orm
django 是一个免费开源的紧嵌orm到其系统的web应用框架。在它首次发布后,得益于其易用为web而备的特点,django越来越流行。它在2005年七月在bsd许可下发布。因为django的orm 是紧嵌到web框架的,所以就算可以也不推荐,在一个独立的非django的python项目中使用它的orm。
django,一个最流行的python web框架, 有它独有的 orm。 相比 sqlalchemy, django 的 orm 更吻合于直接操作sql对象,操作暴露了简单直接映射数据表和python类的sql对象 。
$ django-admin.py startproject demo$ cd demo$ python manage.py syncdbcreating tables ...creating table django_admin_logcreating table auth_permissioncreating table auth_group_permissionscreating table auth_groupcreating table auth_user_groupscreating table auth_user_user_permissionscreating table auth_usercreating table django_content_typecreating table django_session you just installed django's auth system, which means you don't have any superusers defined.would you like to create one now? (yes/no): noinstalling custom sql ...installing indexes ...installed 0 object(s) from 0 fixture(s)$ python manage.py shell
因为我们在没有先建立一个项目时不能够执行django代码,所以我们在前面的shell创建一个django demo 项目,然后进入django shell来测试我们写的 orm 例子。
# demo/models.py>>> from django.db import models>>>>>>>>> class person(models.model):... name = models.textfield()... class meta:... app_label = 'demo'...>>>>>> class address(models.model):... address = models.textfield()... person = models.foreignkey(person)... class meta:... app_label = 'demo'...
上面的代码声明了2个python 类,person 和 address,每一个都映射到数据库表。在执行任意数据库操作代码之前,我们需要先在本地的sqlite数据库创建表。
python manage.py syncdbcreating tables ...creating table demo_personcreating table demo_addressinstalling custom sql ...installing indexes ...installed 0 object(s) from 0 fixture(s)
为了插入一个 person 和一个 address 到数据库,我们实例化相应对象并调用这些对象的save() 方法。
>>> from demo.models import person, address>>> p = person(name='person')>>> p.save()>>> print %r, %r % (p.id, p.name)1, 'person'>>> a = address(person=p, address='address')>>> a.save()>>> print %r, %r % (a.id, a.address)1, 'address'
为了获得或检索 person 和 address 对象, 我们用model类神奇的对象属性从数据库取得对象。
>>> persons = person.objects.filter(name='person')>>> persons[]>>> p = persons[0]>>> print %r, %r % (p.id, p.name)1, u'person'>>> addresses = address.objects.filter(person=p)>>> addresses[ ]>>> a = addresses[0]>>> print %r, %r % (a.id, a.address)1, u'address'
peewee
peewee 是一个小的,表达式的 orm。相比其他的 orm,peewee 主要专注于极简主义,其api简单,并且其库容易使用和理解。
pip install peeweedownloading/unpacking peeweedownloading peewee-2.1.7.tar.gz (1.1mb): 1.1mb downloadedrunning setup.py egg_info for package peewee installing collected packages: peeweerunning setup.py install for peeweechanging mode of build/scripts-2.7/pwiz.py from 644 to 755 changing mode of /users/xiaonuogantan/python2-workspace/bin/pwiz.py to 755successfully installed peeweecleaning up...
为了创建数据库模型映射,我们实现了一个person 类 和一个address类 来映射对应的数据库表。
>>> from peewee import sqlitedatabase, charfield, foreignkeyfield, model>>>>>> db = sqlitedatabase(':memory:')>>>>>> class person(model):... name = charfield()... class meta:... database = db...>>>>>> class address(model):... address = charfield()... person = foreignkeyfield(person)... class meta:... database = db...>>> person.create_table()>>> address.create_table()
为了插入对象到数据库,我们实例化对象并调用了它们的save() 方法。从视图的对象创建这点来看,peewee类似于django。
>>> p = person(name='person')>>> p.save()>>> a = address(address='address', person=p)>>> a.save()
为了从数据库获得或检索对象, 我们select 了类各自的对象。
>>> person = person.select().where(person.name == 'person').get()>>> person >>> print '%r, %r' % (person.id, person.name)1, u'person'>>> address = address.select().where(address.person == person).get()>>> print '%r, %r' % (address.id, address.address)1, u'address'
sqlalchemy
sqlalchemy 是python编程语言里,一个在mit许可下发布的开源工具和sql orm。它首次发布于2006年二月,由michael bayer写的。它提供了 “一个知名企业级的持久化模式的,专为高效率和高性能的数据库访问设计的,改编成一个简单的python域语言的完整套件”。它采用了数据映射模式(像java中的hibernate)而不是active record模式(像ruby on rails的orm)。
sqlalchemy 的工作单元 主要使得 有必要限制所有的数据库操作代码到一个特定的数据库session,在该session中控制每个对象的生命周期 。类似于其他的orm,我们开始于定义declarative_base()的子类,以映射表到python类。
>>> from sqlalchemy import column, string, integer, foreignkey>>> from sqlalchemy.orm import relationship>>> from sqlalchemy.ext.declarative import declarative_base>>>>>>>>> base = declarative_base()>>>>>>>>> class person(base):... __tablename__ = 'person'... id = column(integer, primary_key=true)... name = column(string)...>>>>>> class address(base):... __tablename__ = 'address'... id = column(integer, primary_key=true)... address = column(string)... person_id = column(integer, foreignkey(person.id))... person = relationship(person)...
在我们写任何数据库代码前,我们需要为数据库session创建一个数据库引擎。
>>> from sqlalchemy import create_engine>>> engine = create_engine('sqlite:///')
一当我们创建了数据库引擎,可以继续创建一个数据库会话,并为所有之前定义的 person和address 类创建数据库表。
>>> from sqlalchemy.orm import sessionmaker>>> session = sessionmaker()>>> session.configure(bind=engine)>>> base.metadata.create_all(engine)
现在,session 对象对象变成了我们工作单元的构造函数,将和所有后续数据库操作代码和对象关联到一个通过调用它的 __init__() 方法构建的数据库session上。
>>> s = session()>>> p = person(name='person')>>> s.add(p)>>> a = address(address='address', person=p)>>> s.add(a)
为了获得或检索数据库中的对象,我们在数据库session对象上调用 query() 和 filter() 方法。
>>> p = s.query(person).filter(person.name == 'person').one()>>> p >>> print %r, %r % (p.id, p.name)1, 'person'>>> a = s.query(address).filter(address.person == p).one()>>> print %r, %r % (a.id, a.address)1, 'address'
请留意到目前为止,我们还没有提交任何对数据库的更改,所以新的person和address对象实际上还没存储在数据库中。 调用 s.commit() 将会提交更改,比如,插入一个新的person和一个新的address到数据库中。
>>> s.commit()>>> s.close()
python orm 之间对比
对于在文章里提到的每一种 python orm ,我们来列一下他们的优缺点:
sqlobject
优点:
采用了易懂的activerecord 模式 一个相对较小的代码库缺点:
方法和类的命名遵循了java 的小驼峰风格 不支持数据库session隔离工作单元
storm
优点:
清爽轻量的api,短学习曲线和长期可维护性 不需要特殊的类构造函数,也没有必要的基类缺点:
迫使程序员手工写表格创建的ddl语句,而不是从模型类自动派生 storm的贡献者必须把他们的贡献的版权给canonical公司
django's orm
优点:
易用,学习曲线短 和django紧密集合,用django时使用约定俗成的方法去操作数据库缺点:
不好处理复杂的查询,强制开发者回到原生sql 紧密和django集成,使得在django环境外很难使用
peewee
优点:
django式的api,使其易用 轻量实现,很容易和任意web框架集成缺点:
不支持自动化 schema 迁移 多对多查询写起来不直观
sqlalchemy
优点:
企业级 api,使得代码有健壮性和适应性 灵活的设计,使得能轻松写复杂查询缺点:
工作单元概念不常见 重量级 api,导致长学习曲线
总结和提示
相比其他的orm, sqlalchemy 意味着,无论你何时写sqlalchemy代码, 都专注于工作单元的前沿概念 。db session 的概念可能最初很难理解和正确使用,但是后来你会欣赏这额外的复杂性,这让意外的时序提交相关的数据库bug减少到0。在sqlalchemy中处理多数据库是棘手的, 因为每个db session 都限定了一个数据库连接。但是,这种类型的限制实际上是好事, 因为这样强制你绞尽脑汁去想在多个数据库之间的交互, 从而使得数据库交互代码很容易调试。
在未来的文章中,我们将会完整地披露更高阶的sqlalchemy用例, 真正领会无限强大的api。
