在學習flask開發,書中一段異步發送郵件的代碼是這樣寫的:
from threading import Thread
from flask import current_app, render_template
from flask.ext.mail import Message
from . import mail
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(to, subject, template, **kwargs):
app = current_app._get_current_object()
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr
在send_mail函數中,程序使用了current_app._get_current_object()賦值給app作爲當前程序的實例。此處爲什麼不直接使用current_app呢?
flask官方文檔中是這樣解釋這個方法_get_current_object()的:
Return the current object. This is useful if you want the real object behind the proxy at a time for performance reasons or because you want to pass the object into a different context.
這個問題的關鍵在倒數的第三句:
thr = Thread(target=send_async_email, args=[app, msg])
因爲這裏開了一個單獨的線程,也許你會奇怪了,憑什麼開了一個線程就不能使用 current_app
了?對!就是開了一個線程就不能使用 current_app
。原因在於 current_app
的實現。
current_app
在 Flask
是一個代理,如果你看 Flask
源碼的話會發現其實它外部包裹的是這樣的:
源碼地址 line: 48-58
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
...
current_app = LocalProxy(_find_app)
這個 LocalProxy
就不展開講了,但是我可以告訴你這個LocalProxy
的作用就是可以根據線程/協程返回對應當前協程/線程的對象,也就是說
-
線程 A 往 LocalProxy 中塞入 A
-
線程 B 往 LocalProxy 中塞入 B
無論在是什麼地方,
- 線程 A 永遠取到得是 A,線程 B 取到得永遠是 B
這就是在 Flask
中可以在代碼中直接使用 request
、current_app
這樣的變量的底層原因。
所以,答案來了,因爲這裏開了一個新線程,如果你不穿真實對象過去,那麼你在線程裏面使用 current_app
將獲取不到對象,因爲他沒有 flask 上下文
。