专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
51好读  ›  专栏  ›  Python开发者

SQLAlchemy 和其他的 ORM 框架

Python开发者  · 公众号  · Python  · 2017-04-01 20:00

正文

请到「今天看啥」查看全文


(点击 上方蓝字 ,快速关注我们)


译文:开源中国

www.oschina.net/translate/sqlalchemy-vs-orms

如有好文章投稿,请点击 → 这里了解详情


Python ORM 概览


作为一个美妙的语言,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 sqlobject

Downloading / unpacking sqlobject

Downloading SQLObject - 1.5.1.tar.gz ( 276kB ) : 276kB downloaded

Running 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 : sqlobject

Running setup . py install for sqlobject

changing mode of build / scripts - 2.7 / sqlobject - admin from 644 to 755

changing 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 755

changing mode of / Users / xiaonuogantan / python2 - workspace / bin / sqlobject - convertOldURI to 755

Successfully installed sqlobject

Cleaning 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

< address >


为了获得或检索新记录, 我们用神奇的 q 对象关联到 Person 和 Address 类:


>>> persons = Person . select ( Person . q . name == 'person' )

>>> persons

>>> list ( persons )

[]

>>> p1 = persons [ 0 ]

>>> p1 == p

True

>>> addresses = Address . select ( Address . q . person == p1 )

>>> addresses

>>> list ( addresses )

[

< address > ]

>>> a1 = addresses [ 0 ]

>>> a1 == a

True


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 == person

True

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

Creating tables ...

Creating table django_admin_log

Creating table auth_permission

Creating table auth_group_permissions

Creating table auth_group

Creating table auth_user_groups

Creating table auth_user_user_permissions

Creating table auth_user

Creating table django_content_type

Creating 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 ) : no

Installing 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 syncdb

Creating tables ...

Creating table demo_person

Creating table demo_address

Installing 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

[

< address > ]

>>> a = addresses [ 0 ]

>>> print "%r, %r" % ( a . id , a . address )

1 , u 'address'


peewee


peewee 是一个小的,表达式的 ORM。相比其他的 ORM,peewee 主要专注于极简主义,其API简单,并且其库容易使用和理解。


pip install peewee

Downloading / unpacking peewee

Downloading peewee - 2.1.7.tar.gz ( 1.1MB ) : 1.1MB downloaded

Running setup . py egg_info for package peewee

Installing collected packages : peewee

Running setup . py install for peewee

changing mode of build / scripts - 2.7 / pwiz . py from 644 to 755

changing mode of / Users / xiaonuogantan / python2 - workspace / bin / pwiz . py to 755

Successfully installed peewee

Cleaning 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"

1 , 'address'


请留意到目前为止,我们还没有提交任何对数据库的更改,所以新的person和address对象实际上还没存储在数据库中。 调用 s.commit() 将会提交更改,比如,插入一个新的person和一个新的address到数据库中。


>>> s . commit ()

>>> s . close ()


Python ORM 之间对比


对于在文章里提到的每一种 Python ORM ,我们来列一下他们的优缺点:


SQLObject


优点:


  1. 采用了易懂的ActiveRecord 模式

  2. 一个相对较小的代码库


缺点:


方法和类的命名遵循了Java 的小驼峰风格

不支持数据库session隔离工作单元


Storm


优点:


  1. 清爽轻量的API,短学习曲线和长期可维护性

  2. 不需要特殊的类构造函数,也没有必要的基类


缺点:


  1. 迫使程序员手工写表格创建的DDL语句,而不是从模型类自动派生

  2. Storm的贡献者必须把他们的贡献的版权给Canonical公司


Django’s ORM


优点:


  1. 易用,学习曲线短

  2. 和Django紧密集合,用Django时使用约定俗成的方法去操作数据库


缺点:


  1. 不好处理复杂的查询,强制开发者回到原生SQL

  2. 紧密和Django集成,使得在Django环境外很难使用


peewee


优点:


  1. Django式的API,使其易用

  2. 轻量实现,很容易和任意web框架集成


缺点:


  1. 不支持自动化 schema 迁移

  2. 多对多查询写起来不直观


SQLAlchemy


优点:


  1. 企业级 API,使得代码有健壮性和适应性

  2. 灵活的设计,使得能轻松写复杂查询


缺点:


  1. 工作单元概念不常见

  2. 重量级 API,导致长学习曲线


总结和提示


相比其他的ORM, SQLAlchemy 意味着,无论你何时写SQLAlchemy代码, 都专注于工作单元的前沿概念 。DB Session 的概念可能最初很难理解和正确使用,但是后来你会欣赏这额外的复杂性,这让意外的时序提交相关的数据库bug减少到0。在SQLAlchemy中处理多数据库是棘手的, 因为每个DB session 都限定了一个数据库连接。但是,这种类型的限制实际上是好事, 因为这样强制你绞尽脑汁去想在多个数据库之间的交互, 从而使得数据库交互代码很容易调试。


在未来的文章中,我们将会完整地披露更高阶的SQLAlchemy用例, 真正领会无限强大的API。


看完本文有收获?请转 发分享给更多人

关注「Python开发者」,提升Python技能







请到「今天看啥」查看全文