国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Lunar, 一個Python網絡框架的實現

邱勇 / 1836人閱讀

摘要:核心的幾個組件模板引擎,框架,請求和應答的處理還是有一些難度,但是經過一步步的分析和編碼還是能夠完成功能。模板引擎模板引擎是另外一個比較大和的模塊。

前前后后,大概兩個月的時間,lunar這個項目終于達到了一個很高的完整度。

Lunar是一個Python語言的網絡框架,類似于Django,Flask,Tornado等當下流行的web framework。最初有這個想法是在大二下學期,當時接觸Python web編程有一段時間。最早接觸Python web編程或許是在大一下?自覺當時編程還沒有入門,第一個接觸的web框架是Django,很龐大的框架,當時很low逼的去看那本Django Book的中文譯本,翻譯的其實很不錯了,只是進度會落后于當前的版本,所以在跑example的時候會有一些問題,Django的龐大規模給我留下了很大的心理陰影,所以在之后,對于涉世未深的Pythoner,我可能都不會推薦Django作為第一個Python的網絡框架來學習。

整個框架的挑戰還是非常大的。核心的幾個組件(模板引擎,ORM框架,請求和應答的處理)還是有一些難度,但是經過一步步的分析和編碼還是能夠完成功能。項目受Flask非常大的影響,最初作為造輪子的初衷,幾乎完整的使用了Flask和SQLAlchemy的API接口。

項目同樣開源在Github上: https://github.com/jasonlvhit/lunar

也可以通過pip直接安裝:

$ pip install lunar

這里我大概的記述一下lunar整個項目的各個組件的設計和實現。

ORM framework

首先是ORM。

python圈子里面還是有很多很著名的orm框架,SQLAlchemy,peewee, pony orm各有特色,SQLAlchemy和peewee都已經是很成熟的框架,大量的被應用在商業環境中。回到Lunar,既然是造輪子,何不造個徹底,于是便擼了一個orm框架出來。

在ORM框架中,我們使用類的定義來表示數據庫中的表結構,使用類方法避免繁瑣的SQL語句,一個ORM類定義類似于下面這段代碼:

pyclass Post(db.Model):

    __tablename__ = "post"

    id = database.PrimaryKeyField()
    title = database.CharField(100)
    content = database.TextField()
    pub_date = database.DateField()

    author_id = database.ForeignKeyField("author")
    tags = database.ManyToManyField(rel="post_tag_re", to_table="tag")

    def __repr__(self):
        return "" % self.title

上面這段代碼取自Lunar框架的ORM測試和一個博客的example,這段代碼定義了一個Post類,代表了數據庫中的一張表,類中的一系列屬性分別對應著表中的列數據。

一個peewee或者SQLAlchemy類似語法的一個ORM框架語句類似下面這樣:

py    p = Post.get(id=1)

返回的結果是Post類的實例,這個實例,p.id返回的不是一個PrimaryKeyField,而是一個int類型值,其他的與數據庫關聯的類屬性也是同樣。

orm框架本質上是sql語句或者數據庫模式(schema)和python對象之間的轉換器或者翻譯器,這有些類似于編譯器結構。

在這里,Post是我們創建的一個orm類,post擁有若干數據操作方法,通過調用類似這樣的更加人性化或者直觀的api,代替傳統的sql語句和對象映射。orm框架將語句翻譯為sql語句,執行,并在最后將語句轉換為post類的實例。

可能從這個角度看來,實現orm框架并不是什么tough的任務,讓我們用上面提到的這個例子來看

py p = Post.get(id=1)

這條語句翻譯成的sql語句為

select * from post where id=1;

可以看到的是,get方法會使用一個select語句,翻譯程序將post類的表名和條件分別組合到select語句中,嗅覺靈敏的pythoner會發現這是一個典型的Post類的classmethod,直接通過類來調用這個方法,我們可以快速的寫出這個函數的偽代碼:

pyclass Model(Meta):

    ...

    @classmethod
    def get(cls, *args, **kwargs):
        # get method only supposes to be used by querying by id.
        # UserModel.get(id=2)
        # return a single instance.
        sql = "select * from %s"
        if kwargs:
            sql += "where %s"
        rs = db.execute(sql %(cls.__tablename__, " and ".join(["=".join([k, v]) 
            for k, v in kwargs.items()])))
        return make_instance(rs, descriptor) #descriptor describe the format of rs.

從本質上,所有的翻譯工作都可以這樣來完成。但是在重構后的代碼中可能會掩蓋掉很多細節。

其實這大概是實現一個orm框架的全部了,只是我們還需要一點python中很酷炫的一個編程概念來解決一個問題。

考慮實現ORM框架的create_all方法,創建所有ORM框架規范下類的實際數據庫表,這是熟悉SQLAlchemy的Pythoner都會比較熟悉的一個方法。

create_all方法要求所有繼承了db.Model類的子類全部注冊在db的一個屬性中,比如tabledict,這樣create_all方法在調用時可以使用db中的tabledict屬性,將所有注冊的類編譯為SQL語句并執行。

直觀的來看,我們需要控制類創建的行為。例如Post類,在這個類被創建的時候,將Post類寫入tabledict。

那么怎么控制一個類被創建的時候的行為?答案是使用元編程,Python中有多種實現元編程的方式,descriptor或者metaclass等方式都是實現元編程的方式,在這里,我們使用元類(metaclass)。關于metaclass,網絡上最經典的文章莫過于StackOverflow上的這篇回答,強烈推薦給所有的人看。這里我先直接給出偽碼:

pyclass MetaModel(type):
    def __new__(cls, name, bases, attrs):
        cls = super(MetaModel, cls).__new__(cls, name, bases, attrs)

        ...

        cls_dict = cls.__dict__
        if "__tablename__" in cls_dict.keys():
            setattr(cls, "__tablename__", cls_dict["__tablename__"])
        else:
            setattr(cls, "__tablename__", cls.__name__.lower())

        if hasattr(cls, "db"):
            getattr(cls, "db").__tabledict__[cls.__tablename__] = cls

        ...

        return cls

class Model(MetaModel("NewBase", (object, ), {})): #python3 compatibility
    def __init__(self, **kwargs):

        ...

        for k, v in kwargs.items():
            setattr(self, k, v))

        ...

這種方式定義的Model,在創建的時候,會由MetaModel控制創建過程,最后返回整個類,在創建過程中,我們將表名稱和類本身全部塞入了db的一個屬性中。這樣create_all方法便可以直接使用tabledict中的類屬性直接創建所有的表:

pyclass Database(threading.local):
    ...

    def create_all(self):
        for k, v in self.__tabledict__.items():
            if issubclass(v, self.Model):
                self.create_table(v)

OK,到這里,幾乎ORM的所有核心技術全部介紹完畢。ORM并不是一個很tough的工作,但是也并不是很簡單。ORM框架的實現是一個解決一系列問題的過程,其實思考的過程是最為激動人心的。

模板引擎

模板引擎是另外一個比較大和tough的模塊。Python同樣有很多出色的模板引擎,當下最為流行莫過于Mako和Jinja2,國外的Reddit和國內的豆瓣公司大量的使用了Mako作為模板引擎進行網頁渲染。Jinja2因為具有強大的性能支撐和沙箱模式,在Python社區中也很流行。

Python模板引擎的核心功能是把標記語言編譯成為可執行的代碼,執行一些邏輯或者操作,返回模板文件的渲染結果,往往是字符串。模板引擎的實現同樣類似于傳統的編譯器結構,模板引擎首先會使用一個詞法分析模塊分析出所有的token,并分類標記;在這之后,會使用一個類似于編譯器中的語法分析的模塊分析token序列,調用相應的操作,對于不同的token,我們需要多帶帶編寫一個處理程序(類似于SDT),來處理token的輸出。

最簡單的例子:

py    t = lunar.Template("Hello {{ name }}").render(name="lunar")

這段代碼,我們期待的輸出是"Hello lunar",name會被lunar替換掉。根據上面我提到的模板引擎的工作流程,首先,我們使用詞法分析程序對這段模板語言做模板編譯,分割所有的字符串(實際實現的時候并非如此),給每個單詞賦給一個屬性,例如上面這段模板語言經過最基礎的詞法分析會得到下面這個結果:

<"Hello" PlainText>
<" " Operator> # Blank Space
<"name" Variable> 

有了“詞法分析”得到的序列,我們開始遍歷這個序列中的所有token,對每一個token進行處理。

pyfor token in tokens:
    if isinstance(token, PlainText):
        processPlainText(token)
    elif isinstance(token, Variable):
        processVariable(token)

    ...

一些模板引擎將模板標記語言編譯為Python代碼,使用exec函數執行,最后將結果嵌套回來。例如上面這段代碼,我們可以依次對token進行類似下面這樣的處理:

pydef processPlainText(token):
    return "_stdout.append("" +token+ "")"

def processVariable(token):
    return "_stdout.append(" + token +")"

看到這里你可能會覺得莫名其妙,對于一連串的token序列,經過處理后的字符串類似于下面這樣,看完后你的狀態肯定還是莫名其妙:

pyintermediate = ""
intermediate += "_stdout.append("Hello")"
intermediate += "_stdout.append(" ")"
intermediate += "_stdout.append(name)"

回到上面提到的那個函數exec,我們使用exec函數執行上面的這段字符串,這在本質上其實是一種很危險的行為。exec函數接受一個命名空間,或者說上下文(context)參數,我們對這段代碼做類似下面的處理:

pycontext = {}
context["_stdout"] = []
context["name"] = "lunar"

exec(intermediate, context)

return "".join(context["_stdout"])

context是一個字典,在真正的模板渲染時,我們把所有需要的上下文參數全部update到context中,傳給exec函數進行執行,exec函數會在context中進行更改,最后我們可以取到context中經過修改后的所有的值。在這里,上面兩個代碼片段中的_stdout在context中作為一個空列表存在,所以在執行完exec后,context中的stdout會帶回我們需要的結果。

具體來看,將render中的context傳入exec,這里exec會執行一個變換:

_stdout.append(name) -> exec(intermediate, {name:"lunar"}) -> _stdout.append("lunar")

經過這個神奇的變化之后(搞毛,就是替換了一下嘛),我們就得到了模板渲染后需要的結果,一個看似是標記語言執行后的結果。

讓我們看一個稍微復雜一些的模板語句,比如if...else...,經過處理后的中間代碼會類似于下面這樣:


{% if a > 2 %}
    {{ a }}
{% else %}
    {{ a * 3 }}
{% endif %}

中間代碼:

pyintermediate = "_stdout.append("")
"
intermediate += "if a > 2 :
"
intermediate += "   _stdout.append(a)
"
intermediate += "else :
"
intermediate += "   _stdout.append(a * 3)
"
intermediate += "_stdout.append("")"

注意中間代碼中的縮進!這是這一類型的模板引擎執行控制流的所有秘密,這段代碼就是原生的Python代碼,可執行的Python代碼。模板引擎構建了一個從標記語言到Python原生語言的轉換器,所以模板引擎往往能夠做出一些看似很嚇人,其實很low的功能,比如直接在模板引擎中寫lambda函數:

pyfrom lunar.template import Template

rendered = Template(
            "{{ list(map(lambda x: x * 2, [1, 2, 3])) }}").render()

但是!深入優化后的模板引擎往往沒有這么簡單,也不會使用這么粗暴的實現方式,眾多模板引擎選擇了自己寫解釋程序。把模板語言編譯成AST,然后解析AST,返回結果。這樣做有幾點好處:

自定義模板規則

利于性能調優,比如C語言優化

當然,模板引擎界也有桑心病狂者,使用全部的C來實現,比如同樣很有名的Cheetah。

或許因為代碼很小的原因,我在Lunar中實現的這個模板引擎在多個benchmark測試下展現了還不錯的性能,具體的benchmark大家可以在項目的template測試中找到,自己運行一下,這里給出一個基于我的機器的性能測試結果:

第一個結果是Jonas BorgstrOm為SpitFire所寫的benchmarks:

Linux Platform
-------------------------------------------------------
Genshi tag builder                            239.56 ms
Genshi template                               133.26 ms
Genshi template + tag builder                 261.40 ms
Mako Template                                  44.64 ms
Djange template                               335.10 ms
Cheetah template                               29.56 ms
StringIO                                       33.63 ms
cStringIO                                       7.68 ms
list concat                                     3.25 ms
Lunar template                                 23.46 ms
Jinja2 template                                 8.41 ms
Tornado Template                               24.01 ms
-------------------------------------------------------

Windows Platform
-------------------------------------------------------
Mako Template                                 209.74 ms
Cheetah template                              103.80 ms
StringIO                                       42.96 ms
cStringIO                                      11.62 ms
list concat                                     4.22 ms
Lunar template                                 27.56 ms
Jinja2 template                                27.16 ms
-------------------------------------------------------

第二個結果是Jinja2中mitsuhiko的benchmark測試:

    Linux Platform:
    ----------------------------------
    jinja               0.0052 seconds
    mako                0.0052 seconds
    tornado             0.0200 seconds
    django              0.2643 seconds
    genshi              0.1306 seconds
    lunar               0.0301 seconds
    cheetah             0.0256 seconds
    ----------------------------------

    Windows Platform:
    ----------------------------------
    ----------------------------------

    jinja               0.0216 seconds
    mako                0.0206 seconds
    tornado             0.0286 seconds
    lunar               0.0420 seconds
    cheetah             0.1043 seconds
    -----------------------------------

這個結果最吸引我的有下面幾點:

Jinja2真(TM)快!

Django真慢!

Mako的實現肯定有特殊的優化點,不同的benchmark差距過大!

現在Lunar的代碼還很臟,而且可以重構的地方還很多,相信重構后性能還會上一個臺階(誰知道呢?)。

Router

Router負責整個web請求的轉發,將一個請求地址和處理函數匹配在一起。主流的Router有兩種接口類型,一種是Django和Tornado類型的"字典式":

pyurl_rules = {
    "/": index,
    "/post/d": post,
}

另外一種是Flask和Bottle這種小型框架偏愛的裝飾器(decorator)類型的router:

py@app.route("/")
def index():
    pass

@app.route("/post/")
def post(id):
    pass

router的實現還是很簡單的,router的本質就是一個字典,把路由規則和函數連接在一起。這里有一些麻煩的是處理帶參數的路由函數,例如上例中,post的id是可以從路由調用地址中直接獲得的,調用/post/12會調用函數post(12),在這里,傳參是較為麻煩的一點。另外的一個難點是redirect和url_for的實現:

py return redirect(url_for(post, 1))

但其實也不難啦,感興趣的可以看一下代碼的實現。

Router的另外一個注意點是,使用裝飾器方式實現的路由需要在app跑起來之前,讓函數都注冊到router中,所以往往需要一些很奇怪的代碼,例如我在Lunar項目的example中寫了一個blog,blog的init文件是像下面這樣定義的:

pyfrom lunar import lunar
from lunar import database

app = lunar.Lunar("blog")
app.config["DATABASE_NAME"] = "blog.db"

db = database.Sqlite(app.config["DATABASE_NAME"])

from . import views

注意最后一行,最后一行代碼需要import views中的所有函數,這樣views中的函數才會注冊到router中。這個痛點在Flask中同樣存在。

WSGI

最后的最后,我們實現了這么多組件,我們還是需要來實現Python請求中最核心和基本的東西,一個WSGI接口:

pydef app(environ, start_response):
     start_response("200 OK", [("Content-Type", "text/plain")])
     yield "Hello world!
"

WSGI接口很簡單,實現一個app,接受兩個參數environ和start_response,想返回什么就返回什么好了。關于WSGI的詳細信息,可以查看PEP333和PEP3333。這里我說幾點對WSGI這個東西自己的理解:

計算機服務,或者說因特網服務的核心是什么?現在的我,會給出協議這個答案。我們會發現,計算機的底層,網絡通信的底層都是很簡單、很樸素的東西,無非是一些0和1,一些所謂字符串罷了。去構成這些服務,把我們連接在一起的是我們解釋這些樸素的字符串的方式,我們把它們稱為協議

WSGI同樣是一個協議,WSGI最大的優勢是,所有實現WSGI接口的應用均可以運行在WSGI server上。通過這種方式,實現了Python WSGI應用的可移植。Django和Flask的程序可以混編在一起,在一個環境上運行。在我實現的框架Lunar中,使用了多種WSGI server進行測試。

在一些文章中,把類似于router,template engine等組件,包裝在網絡框架之中,WSGI應用之上的這些組件成為WSGI中間件,得益于WSGI接口的簡單,編寫WSGI中間件變得十分簡單。在這里,最難的問題是如何處理各個模塊的解耦。

考慮之前提到的模板引擎和ORM framework的實現,模板引擎和數據庫ORM都需要獲取應用的上下文(context),這是實現整個框架的難點。也是項目未來重構的核心問題。

現在代碼之爛是讓我無法忍受的。最近開始讀兩本書,代碼整潔之道和重構,自己在處理大型的軟件體系,處理很多設計模式的問題的時候還是很弱逼。首先會拿模板引擎開刀,我有一個大體重構的方案,會很快改出來,力爭去掉parser中的大條件判斷,并且嘗試做一些性能上的優化。

Lunar是一個學習過程中的實驗品,這么無聊,總是要寫一些代碼的,免得畢業后再失了業。

在最后,還是要感謝亮叔https://github.com/skyline75489,亮叔是我非常崇拜的一個Pythoner,或者說coder,一名天生的軟件工匠。沒有他這個項目不會有這么高的完整度,他改變了我對這個項目的態度。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45313.html

相關文章

  • 用JS 重新造了個輪子,農歷計算腳本,有詳細注釋

    摘要:在重新造輪子之前,準備對性能優化下。最重要的,只寫農歷計算相關的計算,其他無功能,如果需要,通過本腳本。為除了閏月外的正常月份是大月還是小月,為天,為天。表示閏月是大月還是小月,僅當存在閏月的情況下有意義。 工作中有時需要農歷計算,之前從網上找了個JS版本的(摘自wannianli.htm,網上導出都是),直接調用就可以了,非常方便。有優點就有缺點,該版本文件有點大(20KB以上);有...

    zhigoo 評論0 收藏0
  • 全面整理30個重要深度學習庫:按Python和C++等10種語言分類

    摘要:本文介紹了包括等在內的一系列編程語言的深度學習庫。是一個在中用于帶有神經網絡的深度學習的庫,它通過使用帶有的加速。是一個用和開發的深度學習庫。是第一個為和編寫的消費級開元分布式深度學習庫。它帶有豐富的作為機器學習庫一部分的深度學習庫。 本文介紹了包括 Python、Java、Haskell等在內的一系列編程語言的深度學習庫。PythonTheano 是一種用于使用數列來定義和評估數學表達的 ...

    weij 評論0 收藏0
  • 基于 10 大編程語言 30 個深度學習庫

    摘要:本文介紹了包括等在內的一系列編程語言的深度學習庫。是一個部署在編程語言中的深度學習工具包,用于通過高效的算法處理大型文本集。是公司基于開發的深度學習框架。是第一個為和編寫的消費級開元分布式深度學習庫。 本文介紹了包括 Python、Java、Haskell等在內的一系列編程語言的深度學習庫。PythonTheano 是一種用于使用數列來定義和評估數學表達的 Python 庫。它可以讓 Pyt...

    Winer 評論0 收藏0
  • 精通Python網絡爬蟲(0):網絡爬蟲學習路線

    摘要:以上是如果你想精通網絡爬蟲的學習研究路線,按照這些步驟學習下去,可以讓你的爬蟲技術得到非常大的提升。 作者:韋瑋 轉載請注明出處 隨著大數據時代的到來,人們對數據資源的需求越來越多,而爬蟲是一種很好的自動采集數據的手段。 那么,如何才能精通Python網絡爬蟲呢?學習Python網絡爬蟲的路線應該如何進行呢?在此為大家具體進行介紹。 1、選擇一款合適的編程語言 事實上,Python、P...

    spacewander 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<