成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Lunar, 一個Python網(wǎng)絡(luò)框架的實現(xiàn)

邱勇 / 1887人閱讀

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

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

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

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

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

也可以通過pip直接安裝:

$ pip install lunar

這里我大概的記述一下lunar整個項目的各個組件的設(shè)計和實現(xiàn)。

ORM framework

首先是ORM。

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

在ORM框架中,我們使用類的定義來表示數(shù)據(jù)庫中的表結(jié)構(gòu),使用類方法避免繁瑣的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類,代表了數(shù)據(jù)庫中的一張表,類中的一系列屬性分別對應(yīng)著表中的列數(shù)據(jù)。

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

py    p = Post.get(id=1)

返回的結(jié)果是Post類的實例,這個實例,p.id返回的不是一個PrimaryKeyField,而是一個int類型值,其他的與數(shù)據(jù)庫關(guān)聯(lián)的類屬性也是同樣。

orm框架本質(zhì)上是sql語句或者數(shù)據(jù)庫模式(schema)和python對象之間的轉(zhuǎn)換器或者翻譯器,這有些類似于編譯器結(jié)構(gòu)。

在這里,Post是我們創(chuàng)建的一個orm類,post擁有若干數(shù)據(jù)操作方法,通過調(diào)用類似這樣的更加人性化或者直觀的api,代替?zhèn)鹘y(tǒng)的sql語句和對象映射。orm框架將語句翻譯為sql語句,執(zhí)行,并在最后將語句轉(zhuǎn)換為post類的實例。

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

py p = Post.get(id=1)

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

select * from post where id=1;

可以看到的是,get方法會使用一個select語句,翻譯程序?qū)ost類的表名和條件分別組合到select語句中,嗅覺靈敏的pythoner會發(fā)現(xiàn)這是一個典型的Post類的classmethod,直接通過類來調(diào)用這個方法,我們可以快速的寫出這個函數(shù)的偽代碼:

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.

從本質(zhì)上,所有的翻譯工作都可以這樣來完成。但是在重構(gòu)后的代碼中可能會掩蓋掉很多細(xì)節(jié)。

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

考慮實現(xiàn)ORM框架的create_all方法,創(chuàng)建所有ORM框架規(guī)范下類的實際數(shù)據(jù)庫表,這是熟悉SQLAlchemy的Pythoner都會比較熟悉的一個方法。

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

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

那么怎么控制一個類被創(chuàng)建的時候的行為?答案是使用元編程,Python中有多種實現(xiàn)元編程的方式,descriptor或者metaclass等方式都是實現(xiàn)元編程的方式,在這里,我們使用元類(metaclass)。關(guān)于metaclass,網(wǎng)絡(luò)上最經(jīng)典的文章莫過于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,在創(chuàng)建的時候,會由MetaModel控制創(chuàng)建過程,最后返回整個類,在創(chuàng)建過程中,我們將表名稱和類本身全部塞入了db的一個屬性中。這樣create_all方法便可以直接使用tabledict中的類屬性直接創(chuàng)建所有的表:

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的所有核心技術(shù)全部介紹完畢。ORM并不是一個很tough的工作,但是也并不是很簡單。ORM框架的實現(xiàn)是一個解決一系列問題的過程,其實思考的過程是最為激動人心的。

模板引擎

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

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

最簡單的例子:

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

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

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

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

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

    ...

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

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

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

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

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

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

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

exec(intermediate, context)

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

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

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

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

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

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


{% 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("")"

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

pyfrom lunar.template import Template

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

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

自定義模板規(guī)則

利于性能調(diào)優(yōu),比如C語言優(yōu)化

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

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

第一個結(jié)果是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
-------------------------------------------------------

第二個結(jié)果是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
    -----------------------------------

這個結(jié)果最吸引我的有下面幾點:

Jinja2真(TM)快!

Django真慢!

Mako的實現(xiàn)肯定有特殊的優(yōu)化點,不同的benchmark差距過大!

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

Router

Router負(fù)責(zé)整個web請求的轉(zhuǎn)發(fā),將一個請求地址和處理函數(shù)匹配在一起。主流的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的實現(xiàn)還是很簡單的,router的本質(zhì)就是一個字典,把路由規(guī)則和函數(shù)連接在一起。這里有一些麻煩的是處理帶參數(shù)的路由函數(shù),例如上例中,post的id是可以從路由調(diào)用地址中直接獲得的,調(diào)用/post/12會調(diào)用函數(shù)post(12),在這里,傳參是較為麻煩的一點。另外的一個難點是redirect和url_for的實現(xiàn):

py return redirect(url_for(post, 1))

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

Router的另外一個注意點是,使用裝飾器方式實現(xiàn)的路由需要在app跑起來之前,讓函數(shù)都注冊到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中的所有函數(shù),這樣views中的函數(shù)才會注冊到router中。這個痛點在Flask中同樣存在。

WSGI

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

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

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

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

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

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

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

結(jié)

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

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

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/45313.html

相關(guān)文章

  • 用JS 重新造了個輪子,農(nóng)歷計算腳本,有詳細(xì)注釋

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

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

    摘要:本文介紹了包括等在內(nèi)的一系列編程語言的深度學(xué)習(xí)庫。是一個在中用于帶有神經(jīng)網(wǎng)絡(luò)的深度學(xué)習(xí)的庫,它通過使用帶有的加速。是一個用和開發(fā)的深度學(xué)習(xí)庫。是第一個為和編寫的消費級開元分布式深度學(xué)習(xí)庫。它帶有豐富的作為機器學(xué)習(xí)庫一部分的深度學(xué)習(xí)庫。 本文介紹了包括 Python、Java、Haskell等在內(nèi)的一系列編程語言的深度學(xué)習(xí)庫。PythonTheano 是一種用于使用數(shù)列來定義和評估數(shù)學(xué)表達(dá)的 ...

    weij 評論0 收藏0
  • 基于 10 大編程語言 30 個深度學(xué)習(xí)庫

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

    Winer 評論0 收藏0
  • 精通Python網(wǎng)絡(luò)爬蟲(0):網(wǎng)絡(luò)爬蟲學(xué)習(xí)路線

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

    spacewander 評論0 收藏0

發(fā)表評論

0條評論

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