flask-源碼解析:上下文

4.flask 源碼解析:上下文

上下文(application context 和 request context)

上下文一直是計(jì)算機(jī)中難理解的概念,在知乎的一個(gè)問(wèn)題下面有個(gè)很通俗易懂的回答:

每一段程序都有很多外部變量纫塌。只有像Add這種簡(jiǎn)單的函數(shù)才是沒(méi)有外部變量的。一旦你的一段程序有了外部變量传货,這段程序就不完整,不能獨(dú)立運(yùn)行。你為了使他們運(yùn)行藤抡,就要給所有的外部變量一個(gè)一個(gè)寫(xiě)一些值進(jìn)去辫封。這些值的集合就叫上下文硝枉。
– vzch

比如,在 flask 中倦微,視圖函數(shù)需要知道它執(zhí)行情況的請(qǐng)求信息(請(qǐng)求的 url妻味,參數(shù),方法等)以及應(yīng)用信息(應(yīng)用中初始化的數(shù)據(jù)庫(kù)等)欣福,才能夠正確運(yùn)行责球。

最直觀地做法是把這些信息封裝成一個(gè)對(duì)象,作為參數(shù)傳遞給視圖函數(shù)。但是這樣的話雏逾,所有的視圖函數(shù)都需要添加對(duì)應(yīng)的參數(shù)嘉裤,即使該函數(shù)內(nèi)部并沒(méi)有使用到它。

flask 的做法是把這些信息作為類似全局變量的東西栖博,視圖函數(shù)需要的時(shí)候屑宠,可以使用 from flask import request 獲取。但是這些對(duì)象和全局變量不同的是——它們必須是動(dòng)態(tài)的仇让,因?yàn)樵诙嗑€程或者多協(xié)程的情況下典奉,每個(gè)線程或者協(xié)程獲取的都是自己獨(dú)特的對(duì)象,不會(huì)互相干擾丧叽。

那么如何實(shí)現(xiàn)這種效果呢卫玖?如果對(duì) python 多線程比較熟悉的話,應(yīng)該知道多線程中有個(gè)非常類似的概念 threading.local蠢正,可以實(shí)現(xiàn)多線程訪問(wèn)某個(gè)變量的時(shí)候只看到自己的數(shù)據(jù)骇笔。內(nèi)部的原理說(shuō)起來(lái)也很簡(jiǎn)單,這個(gè)對(duì)象有一個(gè)字典嚣崭,保存了線程 id 對(duì)應(yīng)的數(shù)據(jù)笨触,讀取該對(duì)象的時(shí)候,它動(dòng)態(tài)地查詢當(dāng)前線程 id 對(duì)應(yīng)的數(shù)據(jù)雹舀。flaskpython 上下文的實(shí)現(xiàn)也類似芦劣,后面會(huì)詳細(xì)解釋。

flask 中有兩種上下文:application contextrequest context说榆。上下文有關(guān)的內(nèi)容定義在 globals.py 文件虚吟,文件的內(nèi)容也非常短:

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app


# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

flask 提供兩種上下文:application contextrequest contextapp lication context 又演化出來(lái)兩個(gè)變量 current_appg签财,而 request context 則演化出來(lái) requestsession串慰。

這里的實(shí)現(xiàn)用到了兩個(gè)東西:LocalStackLocalProxy。它們兩個(gè)的結(jié)果就是我們可以動(dòng)態(tài)地獲取兩個(gè)上下文的內(nèi)容唱蒸,在并發(fā)程序中每個(gè)視圖函數(shù)都會(huì)看到屬于自己的上下文邦鲫,而不會(huì)出現(xiàn)混亂。

LocalStackLocalProxy 都是 werkzeug 提供的神汹,定義在 local.py 文件中庆捺。在分析這兩個(gè)類之前,我們先介紹這個(gè)文件另外一個(gè)基礎(chǔ)的類 Local屁魏。Local 就是實(shí)現(xiàn)了類似 threading.local 的效果——多線程或者多協(xié)程情況下全局變量的隔離效果滔以。下面是它的代碼:

# since each thread has its own greenlet we can just use those as identifiers
# for the context.  If greenlets are not available we fall back to the
# current thread ident depending on where it is.
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        # 數(shù)據(jù)保存在 __storage__ 中,后續(xù)訪問(wèn)都是對(duì)該屬性的操作
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    # 清空當(dāng)前線程/協(xié)程保存的所有數(shù)據(jù)
    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    # 下面三個(gè)方法實(shí)現(xiàn)了屬性的訪問(wèn)氓拼、設(shè)置和刪除你画。
    # 注意到抵碟,內(nèi)部都調(diào)用 `self.__ident_func__` 獲取當(dāng)前線程或者協(xié)程的 id,然后再訪問(wèn)對(duì)應(yīng)的內(nèi)部字典撬即。
    # 如果訪問(wèn)或者刪除的屬性不存在立磁,會(huì)拋出 AttributeError。
    # 這樣剥槐,外部用戶看到的就是它在訪問(wèn)實(shí)例的屬性唱歧,完全不知道字典或者多線程/協(xié)程切換的實(shí)現(xiàn)
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

可以看到,Local 對(duì)象內(nèi)部的數(shù)據(jù)都是保存在 __storage__ 屬性的粒竖,這個(gè)屬性變量是個(gè)嵌套的字典:map[ident]map[key]value颅崩。最外面字典 key 是線程或者協(xié)程的 identity,value 是另外一個(gè)字典蕊苗,這個(gè)內(nèi)部字典就是用戶自定義的 key-value 鍵值對(duì)沿后。用戶訪問(wèn)實(shí)例的屬性,就變成了訪問(wèn)內(nèi)部的字典朽砰,外面字典的 key 是自動(dòng)關(guān)聯(lián)的尖滚。__ident_func 是 協(xié)程的 get_current 或者線程的 get_ident,從而獲取當(dāng)前代碼所在線程或者協(xié)程的 id瞧柔。

除了這些基本操作之外漆弄,Local 還實(shí)現(xiàn)了 __release_local__ ,用來(lái)清空(析構(gòu))當(dāng)前線程或者協(xié)程的數(shù)據(jù)(狀態(tài))造锅。__call__ 操作來(lái)創(chuàng)建一個(gè) LocalProxy 對(duì)象撼唾,LocalProxy 會(huì)在下面講到。

理解了 Local哥蔚,我們繼續(xù)回來(lái)看另外兩個(gè)類倒谷。

LocalStack 是基于 Local 實(shí)現(xiàn)的棧結(jié)構(gòu)。如果說(shuō) Local 提供了多線程或者多協(xié)程隔離的屬性訪問(wèn)糙箍,那么 LocalStack 就提供了隔離的棧訪問(wèn)渤愁。下面是它的實(shí)現(xiàn)代碼,可以看到它提供了 push深夯、poptop 方法猴伶。

__release_local__ 可以用來(lái)清空當(dāng)前線程或者協(xié)程的棧數(shù)據(jù),__call__ 方法返回當(dāng)前線程或者協(xié)程棧頂元素的代理對(duì)象塌西。

class LocalStack(object):
    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead. """

    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    # push、pop 和 top 三個(gè)方法實(shí)現(xiàn)了棧的操作筝尾,
    # 可以看到棧的數(shù)據(jù)是保存在 self._local.stack 屬性中的
    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

我們?cè)谥翱吹搅?request context 的定義捡需,它就是一個(gè) LocalStack 的實(shí)例:

_request_ctx_stack = LocalStack()

它會(huì)當(dāng)前線程或者協(xié)程的請(qǐng)求都保存在棧里,等使用的時(shí)候再?gòu)睦锩孀x取筹淫。至于為什么要用到棧結(jié)構(gòu)站辉,而不是直接使用 Local呢撞,我們會(huì)在后面揭曉答案,你可以先思考一下饰剥。

LocalProxy 是一個(gè) Local 對(duì)象的代理殊霞,負(fù)責(zé)把所有對(duì)自己的操作轉(zhuǎn)發(fā)給內(nèi)部的 Local 對(duì)象。LocalProxy 的構(gòu)造函數(shù)介紹一個(gè) callable 的參數(shù)汰蓉,這個(gè) callable 調(diào)用之后需要返回一個(gè) Local 實(shí)例绷蹲,后續(xù)所有的屬性操作都會(huì)轉(zhuǎn)發(fā)給 callable 返回的對(duì)象。

class LocalProxy(object):
    """Acts as a proxy for a werkzeug local.
    Forwards all operations to a proxied object. """
    __slots__ = ('__local', '__dict__', '__name__')

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)

    def _get_current_object(self):
        """Return the current object."""
        if not hasattr(self.__local, '__release_local__'):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError('no object bound to %s' % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

這里實(shí)現(xiàn)的關(guān)鍵是把通過(guò)參數(shù)傳遞進(jìn)來(lái)的 Local 實(shí)例保存在 __local 屬性中顾孽,并定義了 _get_current_object() 方法獲取當(dāng)前線程或者協(xié)程對(duì)應(yīng)的對(duì)象祝钢。

NOTE:前面雙下劃線的屬性,會(huì)保存到 _ClassName__variable 中若厚。所以這里通過(guò) “_LocalProxy__local” 設(shè)置的值拦英,后面可以通過(guò) self.__local 來(lái)獲取。關(guān)于這個(gè)知識(shí)點(diǎn)测秸,可以查看 stackoverflow 的這個(gè)問(wèn)題疤估。

然后 LocalProxy 重寫(xiě)了所有的魔術(shù)方法(名字前后有兩個(gè)下劃線的方法),具體操作都是轉(zhuǎn)發(fā)給代理對(duì)象的霎冯。這里只給出了幾個(gè)魔術(shù)方法铃拇,感興趣的可以查看源碼中所有的魔術(shù)方法。

繼續(xù)回到 request context 的實(shí)現(xiàn):

_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

再次看這段代碼希望能看明白肃晚,_request_ctx_stack 是多線程或者協(xié)程隔離的棧結(jié)構(gòu)锚贱,request 每次都會(huì)調(diào)用 _lookup_req_object 棧頭部的數(shù)據(jù)來(lái)獲取保存在里面的 requst context

那么請(qǐng)求上下文信息是什么被放在 stack 中呢关串?還記得之前介紹的 wsgi_app() 方法有下面兩行代碼嗎拧廊?

ctx = self.request_context(environ)
ctx.push()

每次在調(diào)用 app.__call__ 的時(shí)候,都會(huì)把對(duì)應(yīng)的請(qǐng)求信息壓棧晋修,最后執(zhí)行完請(qǐng)求的處理之后把它出棧吧碾。

我們來(lái)看看request_context, 這個(gè) 方法只有一行代碼:

def request_context(self, environ):
    return RequestContext(self, environ)

它調(diào)用了 RequestContext墓卦,并把 self 和請(qǐng)求信息的字典 environ 當(dāng)做參數(shù)傳遞進(jìn)去倦春。追蹤到 RequestContext 定義的地方,它出現(xiàn)在 ctx.py 文件中落剪,代碼如下:

class RequestContext(object):
    """The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    `_request_ctx_stack` and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.
    """

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.match_request()

    def match_request(self):
        """Can be overridden by a subclass to hook into the matching
        of the request.
        """
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e

    def push(self):
        """Binds the request context to the current context."""
        # Before we push the request context we have to ensure that there
        # is an application context.
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        _request_ctx_stack.push(self)

        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()

    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.
        """
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.app.do_teardown_request(exc)

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)

    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.auto_pop(exc_value)

每個(gè) request context 都保存了當(dāng)前請(qǐng)求的信息睁本,比如 request 對(duì)象和 app 對(duì)象。在初始化的最后忠怖,還調(diào)用了 match_request 實(shí)現(xiàn)了路由的匹配邏輯呢堰。

push 操作就是把該請(qǐng)求的 ApplicationContext(如果 _app_ctx_stack 棧頂不是當(dāng)前請(qǐng)求所在 app ,需要?jiǎng)?chuàng)建新的 app context) 和 RequestContext 有關(guān)的信息保存到對(duì)應(yīng)的棧上凡泣,壓棧后還會(huì)保存 session 的信息枉疼; pop 則相反皮假,把 request context 和 application context 出棧,做一些清理性的工作骂维。

到這里惹资,上下文的實(shí)現(xiàn)就比較清晰了:每次有請(qǐng)求過(guò)來(lái)的時(shí)候,flask 會(huì)先創(chuàng)建當(dāng)前線程或者進(jìn)程需要處理的兩個(gè)重要上下文對(duì)象航闺,把它們保存到隔離的棧里面褪测,這樣視圖函數(shù)進(jìn)行處理的時(shí)候就能直接從棧上獲取這些信息。

NOTE:因?yàn)?app 實(shí)例只有一個(gè)来颤,因此多個(gè) request 共享了 application context汰扭。

到這里,關(guān)于 context 的實(shí)現(xiàn)和功能已經(jīng)講解得差不多了福铅。還有兩個(gè)疑惑沒(méi)有解答萝毛。

  1. 為什么要把 request context 和 application context 分開(kāi)?每個(gè)請(qǐng)求不是都同時(shí)擁有這兩個(gè)上下文信息嗎滑黔?
  2. 為什么 request context 和 application context 都有實(shí)現(xiàn)成棧的結(jié)構(gòu)笆包?每個(gè)請(qǐng)求難道會(huì)出現(xiàn)多個(gè) request context 或者 application context 嗎?

第一個(gè)答案是“靈活度”略荡,第二個(gè)答案是“多 application”庵佣。雖然在實(shí)際運(yùn)行中,每個(gè)請(qǐng)求對(duì)應(yīng)一個(gè) request context 和一個(gè) application context汛兜,但是在測(cè)試或者 python shell 中運(yùn)行的時(shí)候巴粪,用戶可以單獨(dú)創(chuàng)建 request context 或者 application context,這種靈活度方便用戶的不同的使用場(chǎng)景粥谬;而且椄馗可以讓 redirect 更容易實(shí)現(xiàn),一個(gè)處理函數(shù)可以從棧中獲取重定向路徑的多個(gè)請(qǐng)求信息漏策。application 設(shè)計(jì)成棧也是類似派哲,測(cè)試的時(shí)候可以添加多個(gè)上下文,另外一個(gè)原因是 flask 可以多個(gè) application 同時(shí)運(yùn)行:

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

這個(gè)例子就是使用 werkzeugDispatcherMiddleware 實(shí)現(xiàn)多個(gè) app 的分發(fā)掺喻,這種情況下 _app_ctx_stack 棧里會(huì)出現(xiàn)兩個(gè) application context芭届。

Update: 為什么要用 LocalProxy

寫(xiě)完這篇文章之后,收到有位讀者的疑問(wèn):為什么要使用 LocalProxy感耙?不使用 LocalProxy 直接訪問(wèn) LocalStack 的對(duì)象會(huì)有什么問(wèn)題嗎褂乍?

這是個(gè)很好的問(wèn)題,上面也確實(shí)沒(méi)有很明確地給出這個(gè)答案即硼。這里解釋一下树叽!

首先明確一點(diǎn),LocalLocalStack 實(shí)現(xiàn)了不同線程/協(xié)程之間的數(shù)據(jù)隔離谦絮。在為什么用 LocalStack 而不是直接使用 Local 的時(shí)候题诵,我們說(shuō)過(guò)這是因?yàn)?flask 希望在測(cè)試或者開(kāi)發(fā)的時(shí)候,允許多 app 层皱、多 request 的情況性锭。而 LocalProxy 也是因?yàn)檫@個(gè)才引入進(jìn)來(lái)的!

我們拿 current_app = LocalProxy(_find_app) 來(lái)舉例子叫胖。每次使用 current_app 的時(shí)候草冈,他都會(huì)調(diào)用 _find_app 函數(shù),然后對(duì)得到的變量進(jìn)行操作瓮增。

如果直接使用 current_app = _find_app() 有什么區(qū)別呢怎棱?區(qū)別就在于,我們導(dǎo)入進(jìn)來(lái)之后绷跑,current_app 就不會(huì)再變化了拳恋。如果有多 app 的情況,就會(huì)出現(xiàn)錯(cuò)誤砸捏,比如:

from flask import current_app

app = create_app()
admin_app = create_admin_app()

def do_something():
    with app.app_context():
        work_on(current_app)
        with admin_app.app_context():
            work_on(current_app)

這里我們出現(xiàn)了嵌套的 app谬运,每個(gè) with 上下文都需要操作其對(duì)應(yīng)的 app,如果不適用 LocalProxy 是做不到的垦藏。

對(duì)于 request 也是類似梆暖!但是這種情況真的很少發(fā)生,有必要費(fèi)這么大的功夫增加這么多復(fù)雜度嗎掂骏?

其實(shí)還有一個(gè)更大的問(wèn)題轰驳,這個(gè)例子也可以看出來(lái)。比如我們知道 current_app 是動(dòng)態(tài)的弟灼,因?yàn)樗澈髮?duì)應(yīng)的棧會(huì) push 和 pop 元素進(jìn)去级解。那剛開(kāi)始的時(shí)候,棧一定是空的袜爪,只有在 with app.app_context() 這句的時(shí)候蠕趁,才把棧數(shù)據(jù) push 進(jìn)去。而如果不采用 LocalProxy 進(jìn)行轉(zhuǎn)發(fā)辛馆,那么在最上面導(dǎo)入 from flask import current_app 的時(shí)候俺陋,current_app 就是空的,因?yàn)檫@個(gè)時(shí)候還沒(méi)有把數(shù)據(jù) push 進(jìn)去昙篙,后面調(diào)用的時(shí)候根本無(wú)法使用腊状。

所以為什么需要 LocalProxy 呢?簡(jiǎn)單總結(jié)一句話:因?yàn)樯舷挛谋4娴臄?shù)據(jù)是保存在棧里的苔可,并且會(huì)動(dòng)態(tài)發(fā)生變化缴挖。如果不是動(dòng)態(tài)地去訪問(wèn),會(huì)造成數(shù)據(jù)訪問(wèn)異常焚辅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末映屋,一起剝皮案震驚了整個(gè)濱河市苟鸯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棚点,老刑警劉巖早处,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瘫析,居然都是意外死亡砌梆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)贬循,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咸包,“玉大人,你說(shuō)我怎么就攤上這事杖虾±锰保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵亏掀,是天一觀的道長(zhǎng)忱反。 經(jīng)常有香客問(wèn)我,道長(zhǎng)滤愕,這世上最難降的妖魔是什么温算? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮间影,結(jié)果婚禮上注竿,老公的妹妹穿的比我還像新娘。我一直安慰自己魂贬,他們只是感情好巩割,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著付燥,像睡著了一般宣谈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上键科,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天闻丑,我揣著相機(jī)與錄音,去河邊找鬼勋颖。 笑死嗦嗡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饭玲。 我是一名探鬼主播侥祭,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了矮冬?” 一聲冷哼從身側(cè)響起谈宛,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欢伏,沒(méi)想到半個(gè)月后入挣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡硝拧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葛假。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片障陶。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖聊训,靈堂內(nèi)的尸體忽然破棺而出抱究,到底是詐尸還是另有隱情,我是刑警寧澤带斑,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布鼓寺,位于F島的核電站,受9級(jí)特大地震影響勋磕,放射性物質(zhì)發(fā)生泄漏妈候。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一挂滓、第九天 我趴在偏房一處隱蔽的房頂上張望苦银。 院中可真熱鬧,春花似錦赶站、人聲如沸幔虏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)想括。三九已至,卻和暖如春烙博,著一層夾襖步出監(jiān)牢的瞬間瑟蜈,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工习勤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踪栋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓图毕,卻偏偏與公主長(zhǎng)得像夷都,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 4.flask 源碼解析:上下文 上下文(application context 和 request contex...
    火雞不肥閱讀 296評(píng)論 0 0
  • 原文:Flask的Context(上下文)學(xué)習(xí)筆記作者:饅頭白啊白 上下文這個(gè)概念多見(jiàn)于文章中囤官,是一句話中的語(yǔ)境冬阳,...
    氨基鈉閱讀 872評(píng)論 0 2
  • 上下文這個(gè)概念多見(jiàn)于文章中,是一句話中的語(yǔ)境党饮,也就是語(yǔ)言環(huán)境肝陪。一句莫名其妙的話出現(xiàn)會(huì)讓人不理解什么意思,如果有語(yǔ)言...
    饅頭白啊白閱讀 31,299評(píng)論 6 66
  • 庭院深深深幾許刑顺,楊柳堆煙氯窍,簾幕無(wú)重?cái)?shù)。玉勒雕鞍游冶處蹲堂,樓高不見(jiàn)章臺(tái)路狼讨。雨橫風(fēng)狂三月暮,門(mén)掩黃昏柒竞,無(wú)計(jì)留春住政供。淚眼問(wèn)...
    SlashBoyMr_wang閱讀 3,354評(píng)論 2 4
  • 伴隨著炎熱高溫的空氣,火辣的想要刺穿皮膚的陽(yáng)光朽基,像一個(gè)咄咄逼人的女孩子一樣布隔,盛夏來(lái)了,而且高燒不退稼虎。 南方盛夏的厲...
    菜菜子Yonan閱讀 207評(píng)論 0 2