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 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 <address>
為了獲得或檢索新記錄, 我們用神奇的 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)[ <address>]>>> a1 = addresses[0]>>> a1 == aTrueStorm
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[ <address>]>>> 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
優點:
缺點:
Storm
優點:
缺點:
Django's ORM
優點:
缺點:
peewee
優點:
缺點:
SQLAlchemy
優點:
缺點:
總結和提示
相比其他的ORM, SQLAlchemy 意味著,無論你何時寫SQLAlchemy代碼, 都專注于工作單元的前沿概念 。DB Session 的概念可能最初很難理解和正確使用,但是后來你會欣賞這額外的復雜性,這讓意外的時序提交相關的數據庫bug減少到0。在SQLAlchemy中處理多數據庫是棘手的, 因為每個DB session 都限定了一個數據庫連接。但是,這種類型的限制實際上是好事, 因為這樣強制你絞盡腦汁去想在多個數據庫之間的交互, 從而使得數據庫交互代碼很容易調試。
在未來的文章中,我們將會完整地披露更高階的SQLAlchemy用例, 真正領會無限強大的API。
新聞熱點
疑難解答
圖片精選