Интерпретация исходного кода Python Web Flask (4) — глобальные переменные

Python

обо мне
Вдумчивый программист и практик на протяжении всей жизни, в настоящее время он возглавляет группу предпринимателей.Стек технологий включает Android, Python, Java и Go, которые также являются основным стеком технологий нашей команды.
Гитхаб:github.com/hylinux1024
Публичный аккаунт WeChat: пожизненный разработчик (angrycode)

FlaskГлобальная переменная в имеетcurrent_app,request,gиsession. Однако следует отметить, что хотя в заголовке указаны глобальные переменные, на самом деле эти переменные связаны с контекстом текущего запроса.

current_appэкземпляр приложения текущей активированной программы;requestэто объект запроса, который инкапсулирует запрос, отправленный клиентомHTTPсодержание запроса;gЭто объект, используемый для временного хранения при обработке запросов, и эта переменная сбрасывается каждый раз, когда делается запрос;sessionэто сеанс пользователя, используемый для хранения значений, которые необходимо сохранять между запросами, это словарь.

0x00 current_app

Контексты приложений можно использовать для отслеживания экземпляров приложений во время запроса. Вы можете использовать его напрямую, импортировав его как глобальную переменную(Обратите внимание, что эта переменная не является глобальной).
FlaskЭкземпляры имеют множество свойств, таких какconfigМожетFlaskнастроить.

обычно создаютFlaskвремя экземпляра

from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
...

Обычно не импортируется напрямуюappВместо этого эта переменная используется при импортеcurrent_appэтот прокси-сервер экземпляра контекста приложения

from flask import current_app
жизненный цикл current_app

FlaskПриложение обрабатывает запросы клиентов (request), он будет помещен в поток, в данный момент обрабатывающий запрос (push) экземпляр контекста и экземпляр запроса (request), который появляется, когда запрос заканчивается (pop) экземпляр запроса и экземпляр контекста, поэтомуcurrent_appиrequestОн имеет тот же жизненный цикл и привязан к потоку, который в данный момент обрабатывает запрос.

Если экземпляр контекста не передается напрямую, используйтеcurrent_app, сообщит об ошибке

RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that
needed to interface with the current application object in some way.
To solve this, set up an application context with app.app_context().

Если вы хотите использовать напрямуюcurrent_appнажимать вручную(push) экземпляр контекста приложения, как видно из приведенного выше сообщения об ошибке, вы можете использоватьwithзаявление, помогите намpushэкземпляр контекста

def create_app():
    app = Flask(__name__)

    with app.app_context():
        init_db()

    return app

должен быть в курсеcurrent_appявляется локальной переменной «потока», поэтомуcurrent_appЕго необходимо использовать в функциях представления или функциях командной строки, иначе будет сообщено об ошибке.

Чтобы понять это, необходимо понять механизм работы серверной программы. Как правило, серверные программы представляют собой многопоточные программы, поддерживающие пул потоков.Для каждого запроса сервер будет получать поток из пула потоков для обработки запроса клиента, а приложениеcurrent_app,requestРавные переменные являются локальными переменными «потока», они связаны в «потоке» (эквивалентно собственному независимому пространству памяти потока), поэтому их можно использовать только в среде потока.

существуетFlaskЭто также реализовано через локальные переменные потока?Мы обсудим этот вопрос позжеПринцип работыРаздел даст ответ.

0x01 g

Чтобы хранить данные в контексте приложения,Flaskпри условииgЭта переменная делает это за нас.gНа самом деле этоglobalАббревиатура , его жизненный цикл совпадает с жизненным циклом контекста приложения.

Например, если база данных запрашивается несколько раз в одном запросе, экземпляр подключения к базе данных может быть сохранен в текущем запросе.gпеременная, соединение закрывается в конце жизненного цикла контекста приложения.

from flask import g

def get_db():
    if 'db' not in g:
        g.db = connect_to_database()

    return g.db

@app.teardown_appcontext
def teardown_db():
    db = g.pop('db', None)

    if db is not None:
        db.close()

0x02 request

requestинкапсуляция на стороне клиентаHTTPЗапрос, который также является потоком локальной переменной.

не поместил эту переменную в обработкеapiВ запрашиваемой функции он инкапсулирован локальными переменными потока, что значительно облегчает использование и делает код более лаконичным.

requestЖизненный цикл сопровождаетсяcurrent_appЭто то же самое, созданное с начала запроса и уничтоженное в конце запроса. так жеFlaskпри обработке запросаpushОдинrequestи прокси-экземпляр контекста приложения, прежде чем его можно будет использовать. если нетpushОб использовании ошибки будет

RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.

Обычно эта ошибка часто встречается в тестовом коде, если вам нужно использовать в модульном тестированииrequest,можно использоватьtest_clientили вwithиспользуется в заявленииtest_requet_context()имитировать

def generate_report(year):
    format = request.args.get('format')
    ...

with app.test_request_context(
        '/make_report/2017', data={'format': 'short'}):
    generate_report()

0x03 session

Как упоминалось ранее, если данные передаются во время запроса, вы можете использоватьgпеременная, но если вы хотитеrequest), чтобы поделиться данными, вам нужно использоватьsession, который является частным типом словаря. Можно манипулировать как словарьsession.
sessionэто сеанс пользователя, который может сохранять данные между запросами. Например, используяloginПосле того, как интерфейс выполняет вход пользователя, информация о входе пользователя сохраняется вsessionВ, то доступ к другим интерфейсам можно передатьsessionЧтобы получить информацию о входе пользователя.


@app.route('/login')
def login():
    # 省略登录操作
    ...
    session['user_id']=userinfo
    
@app.route('/show')
def showuser():
    # 省略其它操作
    ...
    userid = request.args.get('user_id')
    userinfo = session.get(userid)

Как работает 0x04

мы знаемFlaskПри обработке запроса,wsgi_app()Этот метод будет выполнен. пока вFlaskвнутри исходного кодаrequestиcurrent_appчерез_request_ctx_stackЭта структура стека для сохранения, соответственно

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

требует вниманияПоследняя версия исходного кода будет немного отличатьсяrequestиcurrent_appЕсть две структуры стека для хранения:_request_ctx_stackи_app_ctx_stack. Но идеи старого и нового кода похожи.

В последнем исходном коде определение глобальных переменных

# 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"))

в_find_appи_lookup_app_objectМетод определяется так

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app
    
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)

можно увидетьcurrent_appиgдаLocalProxyпройти через_app_ctx_stack.topв упаковке.requestиsessionда_request_ctx_stackупаковка.LocalProxyдаwerkzeugбиблиотекаlocalпрокси объекта.LocalStackКак следует из названия, это структура данных, реализующая стек.
Как упоминалось ранее, глобальные переменные привязаны к потокам, и каждый поток имеет независимое пространство памяти.AПеременные, установленные потоком, вBНити нельзя получить, только послеAЭта переменная может быть получена только в потоке. это вPythonВ стандартной библиотеке естьthread localsКонцепция чего-либо.
Однако вPythonВ дополнение к потокам существуют также процессы и сопрограммы, которые могут обрабатывать параллельные программы. Итак, чтобы решить эту проблемуFlaskЗависимостиwerkzeugреализует свою собственную локальную переменнуюwerkzeug.local. Он работает так же, как и локальные переменные потока (thread locals) похож.

нужно использоватьwerkzug.local

from werkzeug.local import Local, LocalManager

local = Local()
local_manager = LocalManager([local])

def application(environ, start_response):
    local.request = request = Request(environ)
    ...

application = local_manager.make_middleware(application)

существуетapplication(environ,start_response)Метод инкапсулирует информацию запросаrequestПеременные привязаны к локальным переменным. Затем в том же контексте, например, во время запроса, вы можете передатьlocal.requestчтобы получить соответствующийrequestИнформация.

Вы также можете увидетьLocalManagerЭтот класс, который является диспетчером локальных переменных, обеспечивает своевременную очистку информации о локальных переменных после завершения запроса.

в исходном кодеLocalManagerаннотируется так

Local objects cannot manage themselves. For that you need a local manager. You can pass a local manager multiple locals or add them later by appending them to manager.locals. Every time the manager cleans up, it will clean up all the data left in the locals for this context.

LocalНе могу справиться сама, нужна помощьLocalManagerЭта экономка выполняет работу по очистке после завершения запроса.

0x05 Сводка

current_app,g,requestиsessionдаFlaskЕсть 4 общие глобальные переменные.current_appактуаленFlaskэкземпляр запущенной службы,gпеременные, используемые для хранения данных в контексте приложения,requestИнкапсулирует информацию запроса клиента,sessionПредставляет информацию о сеансе пользователя.

0x06 Учебные материалы