Python 異步編程再添一利器

隨着 Tornado 和 asyncio 等框架的陸續涌現,Python 異步編程這個話題也在逐漸升溫。在這個燒腦的異步世界裏,有沒有辦法可以既方便快捷、又簡單明瞭地訪問數據庫呢?GitHub 千星項目 GINO 瞭解一下!

1. GINO 是誰

GINO 是一個“輕量級”異步 ORM 框架,它的全稱是 GINO Is Not ORM,借鑑了 GNU is Not Unix 的遞歸定義手法。所以,GINO 一定要全!部!大!寫!如果像這樣“Gino”就變成了人名,你肯定要問一句“這是誰”。

ORM,即關係對象映射(Object-Relational Mapping),是一類開發人員喜聞樂見的效率工具,它們"極大地"提升了寫代碼的幸福指數。GINO 是用來訪問數據庫的,也提供了對象映射的工具,那爲什麼非說 GINO 不是 ORM 呢?

因爲物極必反,ORM 在帶來生活便利的同時,也是 bug 生長的溫牀 —— 傳統 ORM 往往會選擇犧牲明確性(explicitness)來換取便捷性(convenience),再加上 Python 得天獨厚的靈活性(flexibility),創造出了一種爆炸式的化學反應。一旦代碼初具規模,項目或多或少都會遇到 ORM 反噬的情景:性能莫名其妙的差、出問題找不到原因、爲了雞毛蒜皮的小事大動干戈。隨便一句 current_user.name 都有可能觸發一大堆意想不到的數據庫調用,這代碼你讓我怎麼調試?

傳統 ORM 的學習曲線前平後陡,能在快速原型開發中大展身手,但應用到大型項目中卻十分考驗開發人員的平均水平。

所有這些問題如果再放進異步編程的環境裏,那就是 O(n2) 的複雜度了 —— 哦不,是 O(2n)。這對於一款優秀的異步 ORM 框架來說是不可接受的,所以 GINO 是 ORM 但不是一個傳統的 ORM,正猶如 GNU 不是一個傳統的 Unix 一樣,形似而神不似。

所以在 2017 年創作之初,我就給 GINO 定下了兩個業績目標:1) 方便快捷,2) 簡單明瞭。三年後的今天,我索性在 1.0 穩定版發佈的前夕做個年終總結。

2. 先說"方便快捷"

"方便快捷"主要說的是開發效率。

重視開發效率的概念對於寫 Python 的同學來說可能並不陌生,某些場景下,開發人員的時間確實比機器的時間值錢。所以,傳統 ORM 裏的對象映射不能丟。

from gino import Gino
db = Gino()class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)

這麼定義表結構甚至讓人有點小興奮。咦,爲什麼這麼眼熟?

沒有錯,這就是 SQLAlchemy ORM 的定義風格。GINO 並不是從頭造輪子,而是在 SQLAlchemy core(SQLAlchemy 中負責構建 SQL 的底層核心)的基礎上開發的。這麼做除了能保持熟悉的味道(以節省學習和遷移成本),更重要的是帶來了整個 SQLAlchemy 的生態環境:開箱即用的數據庫變更管理工具 Alembic、各種 SQLAlchemy 的增強插件、專業領域的 PostGIS/geoalchemy 等,GINO 全都兼容。

是不是十分方便、十分快捷?不止這樣。

GINO 一站式地解決了常用 CRUD 快捷方式、上下文管理(aiocontextvars)、數據庫事務封裝和嵌套、連接池管理和懶加載等多項便捷功能,無額外依賴關係,即裝即用。

daisy = await User.create(name="daisy")await daisy.update(name="Daisy").apply()

GINO 還提供了各大流行異步 Web 框架的定製版插件,能叫上名字的像 Tornado、aiohttp、Sanic、FastAPI/Starlette、Quart 什麼的都有,從簡單示範到生產環境的各種例子品種齊全,媽媽再也不用擔心我不會集成 Web 框架了。

爲了讓不同應用場景下的用戶體驗到最大的善意,GINO 目前支持三種不同程度的用法,成功實現了對同期競品 asyncpgsa 的降維打擊:

  1. 最少侵入型:SQLAlchemy core 原教旨主義者,只有異步執行時纔用到 GINO。

  2. 終身不婚型:天生厭惡"對象",只願定義"表",空手接 SQL。

  3. 火力全開型:最大程度的便利,非典型異步 ORM。

最後,雖然是 Python(絕不是黑哈),但 GINO 在執行效率上也沒落下。基於 MagicStack 出品必屬精品的、一秒可讀百萬行的 asyncpg,以及 uvloop(可選)的強力加持,GINO 跑起來也是可以飛快的,被廣泛應用於諸如實時匯率、聊天機器人、在線遊戲等高併發領域,深受俄羅斯和烏克蘭人民的愛戴。

3. 再說"簡單明瞭"

Explicit is better than implicit. Simple is better than complex. -- The Zen of Python, PEP 20

Python 之禪完美表達了 GINO 的立場 —— 明確性(explicitness)對於上了規模的異步工程項目來說尤爲重要,因此 GINO 的很多設計都受到了明確性的影響。

比如說,GINO 的 Model 是完全無狀態的普通 Python 對象(POPO)—— 例如前面的 User 類,它的實例 daisy 就是內存裏面的常規對象,你可以用 daisy.name 訪問屬性,也可以用 daisy.name = "DAISY" 來修改屬性,或者用 u = User() 來創建新的實例,這些操作都不會訪問數據庫,絕對綠色環保無毒副作用。

等到需要操作數據庫的時候,你一定會有感知的。比如執行 INSERT 要用 u = await User.create(),而 UPDATE 則是 await u.update(name="Daisy").apply()

其中, u.update(name="Daisy") 與 u.name = "Daisy" 類似,都是隻在內存裏修改對象的屬性,不同的是 u.update() 還會返回一個包含本次變更的中間結果,對其執行 await xxx.apply() 則會將這些變更應用到數據庫裏。

這裏的 await 就是明確性的關鍵,意味着我們要跳出去執行數據庫操作了。換句話說,沒有 await 就沒有數據庫操作。

另一方面,對於如何將數據庫查詢結果組裝成內存對象及其屬性,GINO 也有一套精妙的顯式機制 —— 可定製化的加載器 loaders。對於簡單直觀的一對一加載,GINO 自然是伺候到家的,比如用 u = await User.get(1) 可以直接獲取到 ID 爲 1 的用戶對象。但是對於更復雜的查詢,GINO 不會去無端猜測主人的意圖,而全權交給用戶來明確地定義。加載器的用法也是很簡單的,比如一個用戶可能寫了很多本書:

class Book(db.Model):
    __tablename__ = "books"
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String)
    author_id = db.ForeignKey("users.id")

然後這樣來加載這種多對一關係,以同時獲取所有的書和他們的作者:

query = Book.outerjoin(User).select()
loader = Book.load(author=User)async for book in query.gino.load(loader).iterate():
    print(book.title, "written by", book.author.name)

很簡單的一個外連接查詢 Book.outerjoin(User),配合一個直觀的加載器 Book.load(author=User),就實現了:

  1. 執行 SELECT * FROM books LEFT JOIN users ON ...;

  2. 將數據庫返回結果的每一行中,屬於 books 的字段加載成一個 Book 實例;

  3. 然後將該行中剩下的屬於 users 的字段加載成一個 User 實例;

  4. 最後將 User 實例設置到 Book 實例的 author 屬性上。鄭州市不孕不育醫院:http://www.03913882333.com/

既簡單又明瞭有沒有!你甚至可以手寫任何 SQL,然後定製加載器自動加載成期望的對象關係,精準控制加載行爲,指哪兒打哪兒。GINO 還有很多類似的特性,在這裏就不一一列舉了。看治不孕不育鄭州醫院哪家好:http://jbk.39.net/yiyuanfengcai/tsyl_zztjyy/991/

4. 優勢與不足

隨着這幾年 GINO 不斷演進成熟,Python 開源社區裏也相繼出現了像 Tortoise ORM、ORM(是的,這個項目就叫 ORM......我真 ORZ。出品方是 Encode,Starlette 就是他們的作品)等優秀的異步 ORM 框架。它們關注的重點與 GINO 稍有不同,但都是同行就不多評價了。

GINO 的最大優勢還是在於充分平衡了開發效率和明確性之間的辯證矛盾關係,用 GINO 開發應用程序的時候不用擔心會被意料之外的行爲所驚嚇到,同時也不需要爲這種明確性付出過大的工程代價,上手後依然可以快速、快樂地編程。同時,大量的成功案例也證明了 GINO 已經初步具備發佈 1.0 穩定版的各種條件,可以謹慎地用於生產環境了。

以下是近來統計到的關於 GINO 的應用案例:

另外,GINO 還貼心地提供了中文文檔,從上手教程到原理說明應有盡有(雖然文檔還在努力編寫中!): 

GINO 目前的不足之處還有一些,比如沒有照顧到 Python 3 的類型提示,因此還不能完全發揮 IDE 的潛能(上面那個 gino-stubs 就是有人受不了了自己寫了一個類型註解)。MySQL 目前也是不支持的,但 GINO 從比較早就解耦了不同 SQL 方言和驅動的集成,所以這些功能會陸續在 1.1 和 1.2 版本中跟上。

5. 建設社會主義

GINO 是一個開源項目,所以歡迎大家一起來建設!長期活躍的貢獻者還能獲贈價值 4888 元 的 PyCharm 專業版全家桶 License 一枚。 目前急需幫助的有:

  1. 各個 Web 框架插件的維護工作需要多人認領;

  2. 更多的例子和文檔,以及中文、俄文的翻譯;

  3. MySQL 的支持。

以及下面這些一直需要的幫助:

  1. 用 GINO,找 bug,提建議;

  2. 修 bug,做功能,提 PR;

  3. 維護社區,回答問題,參與討論;

  4. 最後也是最重要的:去 GitHub 上給 GINO 加一顆星星!

6. 關於作者

I'm a software architect, fan of coding for over 20 years, and now focusing on software engineering, high concurrency and development performance. Python is my chief language, and I worked on Linux for 10 years, managing development teams up to 20 people for 8 years. Big fan of open source, created project GINO with 1000+ GitHub stars.

OPEN SOURCE

Python / 2018-2019 I started to contribute to Python programming language with MagicStack fellows, focusing on the asyncio library.

GINO / 2017-2019 GINO is an ORM library for Python asyncio. It is an integration of SQLAlchemy core and asyncpg.

aioh2 / 2016 This is an integration of an HTTP/2 protocol library and Python 3 asyncio.

tulipcore / 2015 I've implemented the most part of the event loop core of Gevent with pure Python 3 asyncio (tulip) code.

zmq.rs / 2014 It was my attempt to implement the ZeroMQ stack in pure Rust.

ArchLinux / 2013 - 2016 I maintained the packages of a dozen of build toolchains and base libraries of ArchLinux for x32-ABI, e.g. GCC, glibc, openssl, curl, util-linux, etc.

Gevent / 2011 - 2013 I contributed the initial port of Gevent to Python 3, which was later merged into Gevent 1.1 by its new maintainer. I've also ported Greenlet to x32-ABI.

Translations / 2008 - 2015 I was involved/started several translation projects, e.g. Ubuntu, libexif, Twisted, Python-beginners, ZeroMQ, etc.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章