Flask まとめ

ページ遷移について触れます。 MVT のビュー V について見ていきます。 具体的には以下のような用語や関数について、 見ていきます。。

  • blueprint
  • view
  • route
  • endpoint
  • render_template
  • redirect
  • context

最後に「コンテクスト」に焦点を当てていきます。 Flask を理解する時のポイントはコンテクストの様な気がします。

なおテンプレート T は、触れません。 テンプレートについては、以下にまとめがありました。

モデル M は、おそらく SQLAlchemy が該当すると思います。 こちらも今回は触れません。

1. ビュー, view

 リクエストを受けて、レスポンスを返す。  これがウェブアプリケーションの基本的な考え方です。 リクエストされると関数が呼び出されて、文字列 str を返しています。

自分の中で、意外とこの考えが曖昧になっていました。 以下のつぶやきは Django のつぶやきですが Flask も基本は同じです。 勉強になります。ありがとうございます。

正式名称

このとき呼び出される関数を Flask では、view と呼びます。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask

app = flask.Flask(__name__)

@app.route("/")
def index():  # <--- これがビューです。
    return "Hello World!"

app.run()

Blueprints and Views - Flask Tutorial
view 関数は、あなたのアプリケーションに来たリクエストに応答するために、 あなたが書いたコードです。 Flask は URL に来たリクエストを捌くための view がどれに対応するか調べるために、 パターンを使い対応します(訳者注: パターンについて下記参照)。
A view function is the code you write to respond to requests to your application. Flask uses patterns to match the incoming request URL to the view that should handle it.

おそらく「パターン」とは route を登録するときに使う '/<username>', '/post/<int:post_id>' と言った表記を指しているのだと思われます。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

@app.route('/') # <--- これのことかと...
def index():
    return ''

@app.route('/<username>') # <--- これのことかと...
def show_user(username):
    return username

@app.route('/post/<int:post_id>') # <--- これのことかと...
def show_post(post_id):
    return post_id

app.run()


# URL Route Registrations - API
# http://flask.pocoo.org/docs/1.0/api/#url-route-registrations

2. ビュー - リクエスト

 リクエストを受けて、レスポンスを返す。  自分の中で、意外とこの考えが曖昧になっていて理解が苦しいところがありました。

では Flask は、どこからリクエストを受けているのでしょうか?

それは flask オブジェクトのflask.request 属性の中にはいっています。 リクエストされるたびに flask.request が更新されます。 例えば POST された内容は flask.request.form に保存されます。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask

app = flask.Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def index():
    if   flask.request.method == "GET":
        response = template
    elif flask.request.method == "POST":
        response = str(flask.request.form)
    return response                # ^^^^ ココに POST された
                                   #      内容がはいっています。


# FORM - HTMLタグリファレンス から拝借しました。
# http://www.htmq.com/html/form.shtml
template = """\
<form action="/" method="post">
  <p>
    名前:
    <input type="text" name="name" size="40">
  </p>
  <p>
    ご感想:
    <textarea name="kanso" rows="4" cols="40"></textarea>
  </p>
  <p>
    <input type="submit" value="送信">
    <input type="reset"  value="リセット">
  </p>
</form>
"""

app.run()

(1) なんでグローバル変数なの?

なんでってなんでって話なのですが、 以下のような具合で request を引数で受けたらどうなんだろうって思いました。

#
# 偽物の flask
#
import flask

app = flask.Flask(__name__)

@app.route("/")
def index(request):  # <--- request を引数で受けたら
    if   request.method == "GET":
        response = template
    elif request.method == "POST":
        response = str(flask.request.form)
    return response

app.run()

(2) デメリットはなに?

一般にプログラミングする際には、 グローバル変数は使わない方が良いとされています。

では Flask では、 request をグローバル変数 flask から受けてしまうことによって どのような影響が生じたのでしょうか?

  1. 大きなアプリケーションを組みづらくなります。
  2. 「コンテクスト」という理解しにくい概念が発生します。
  3. 「スレッドセーフ」を担保するために特別な実装が必要になります。

1. 大きなアプリケーションが組みづらい。

正直、何かしらコードを書いて示すことはできないのですが... 一般にグローバル変数を使って組み立ててしまうと、 大きなアプリを書きにくいことが多いです。

Flask は、グローバル変数 flask から request を取り出せるようにするために Thread Local という機能を導入しました。

いまは Thread Local という言葉は、無視してください。 Flask の公式ドキュメントに以下のような記述があります。

Thread Locals - Design Decisions in Flask

(Flask が採用した) Thread Local という仕組みは、 スレッドを使ったサーバでは問題が起こるし、 大きなアプリケーションを維持するのは難しくなります。
They cause troubles for servers that are not based on the concept of threads and make large applications harder to maintain.

しかしながら Flask は大きなアプリや非同期サーバのために設計されてはいません。 Flask は素早く簡単に一般的なウェブアプリを組むことを目標にしています。
However Flask is just not designed for large applications or asynchronous servers. Flask wants to make it quick and easy to write a traditional web application.

2. コンテクスト

コンテクストについては、このページの後ろの方で見ていきます。

3. スレッドセーフ

これについては次のページで見ていきます。Thread Local とも関係があります。

(3) メリットはなに?

なぜこのような欠点を受け入れてまで、このような実装をしたのでしょうか? 面倒だったから見たいな文章をどこかで見かけましたような気がします。 現在、捜索中です。

なんだよ面倒ってと思ったのですが、 どう面倒かというとグローバル変数 flaskrequest を入れなかった場合、  フック  の間で request オブジェクトを引数に渡さないといけないのが面倒という文章だったような気がします。

フック

プログラムの中に独自の処理を割りこませるために用意されている仕組み。

もしくは

プログラムにおいて、本来の処理を横取りして独自の処理を割りこませること

です。

例えばどういう機能なのかというと、 例えば before_request を使うとビューが呼び出されたときに、 割り込んで処理を実行することができます。

以下のコードをコピペで実行して、/view1, /view2 にアクセスすると  コンソール  に 'ワロスwww' と表示されます。

import flask
app = flask.Flask(__name__)

@app.before_request
def before_request():
    print('ワロスwwww')

@app.route('/view1')
def view1():
    return 'view1'

@app.route('/view2')
def view2():
    return 'view2'

app.run()

Flask の場合は大量のフックを設ける関係で request については例外的にグローバルにしてしまったということなのでしょうか。 シグナルというのは、JavaScript などの言語で言えばイベントに該当します。

wsgi, Bottle, Flask の速度差 - Qiita
Flask は確かに、簡単なサンプルアプリを書くときの見た目はマイクロフレームワークになっています。 しかし、構造的には沢山のフック、シグナルがあったりしていて、重量級の設計になっています。 Flask 本体と Werkzeug を合わせると数万行のサイズです。 単なる Hello World アプリでも、  数十の関数呼び出しが裏で動いています。 

request を関数の間でリレーのバトン渡しのように、 渡し続けるのは面倒です。

# 引数でバトン渡しするコード
#     引数を書かないといけないので
#     面倒です。
def f(x):
    g(x)

def g(x):
    h(x)

def h(x):
    return x

f(0)
# グローバル変数を使ったコード
#     引数を書く必要がなくなり
#     少し楽になる。
def f():
    g()

def g():
    h()

def h():
    return x

x = 0

(4) Django と比較

以下は Django のコードです。 引数を明示しているので、リクエストを受けてレスポンスを返すというのがわかりやすいです。 型アノテーションという書き方で、リクエストとレスポンスをより明示した書き方をしました。

#
# Django のコードの一部です。
#
def view(request: HttpRequest) -> HttpResponse:
    template = loader.get_template(
        'app_name/template_name.html')
    context = {
        'blog_title' : 'やる夫の日記',
        'content'    : 'ケーキを食べた。'}
    response = HttpResponse(template.render(
        context,
        request))
    return response

(5) Python と比較

Python そのものにもフックはたくさんあります。 Flask は、そう言ったフックを用意することは真似ました。 しかし、クラス定義時に self を書くようなこと、 request を書くことはしなかったということでしょうか。

#
# flask の選択
#   request を明示しない
#
import flask

app = flask.Flask(__name__)

@app.route("/")
def index():
    return flask.request.args.get('key')

app.run()
#
# 偽物の Flask
#   request を明示する。
#
import flask

app = flask.Flask(__name__)

@app.route("/")
def index(request):
    return request.args.get('key')

app.run()
#
# 偽物の Python
#   self を明示しない
#
class C:
    def f():
        self.attr = 'value'
#
# Python の選択
#   self を明示する
#
class C:
    def f(self):
        self.attr = 'value'

3. ビュー - レスポンス

 リクエストを受けて、レスポンスを返す。  自分の中で、意外とこの考えが曖昧になっていて理解が苦しいところがありました。

では Flask は何型、何クラスのオブジェクトを返しているのでしょうか?

Flask の view 関数を定義するときに私たちは return 文を書いています。 そこで書かれている return 文の書き方は、色々なやり方があります。

@app.route("/")
def view():
    # 1. str をそのまま返す。  
    return 'Hello, world!'

    # 2. flask.render_template を使う。
    return flask.render_template(
        'template', message='Hello, world!')

    # 3. flask.redirect を使う。
    return flask.redirect(
        flask.url_for('helloworld'))

ここで疑問なのは view 関数は、何型、何クラスのオブジェクトを返せばいいのでしょうか?

Blueprints and Views - Flask Tutorial
view は データを返します、 Flask はそのデータを応答のためにレスポンスに変換します (訳者注: HTTP ヘッダ等を付与することを指していると思われます)。 Flask は他にも view の名前と引数に基づいて URL を生成することもできます。
The view returns data that Flask turns into an outgoing response. Flask can also go the other direction and generate a URL to a view based on its name and arguments.

Flask の公式ドキュメントのクイックスタートに 一覧が載っていたので引用いたします。

レスポンスについて
About Response

ビュー関数からの返り値は、自動的にレスポンスオブジェクトに変換されます。
The return value from a view function is automatically converted into a response object for you.

もし返り値が文字列なら HTTP レスポンスのボディにその文字列が埋め込まれ、 ステータスコードに 200 OK と text/html に mimetype が指定された レスポンオブジェクトが生成されます。
If the return value is a string it’s converted into a response object with the string as response body, a 200 OK status code and a text/html mimetype.

もし返り値が辞書なら、jsonify() を呼び出してレスポンスオブジェクトを生成します。
If the return value is a dict, jsonify() is called to produce a response.

Flask アプリケーションがビュー関数の返り値をレスポンスオブジェクトに 変換する手順は以下の通りです。
The logic that Flask applies to converting return values into response objects is as follows:

  1. もしレスポンスオブジェクトがそのまま返されたなら ビュー関数は直接それを返します。
    If a response object of the correct type is returned it’s directly returned from the view.

  2. もし文字列なら、その文字列と事前に Flask に設定された値を 使ってオブジェクトを生成します。
    If it’s a string, a response object is created with that data and the default parameters.

  3. もし辞書なら、レスポンスオブジェクトは jsonify を使います。
    If it’s a dict, a response object is created using jsonify.

  4. タプルが返された場合、そのタプル内の項目は追加情報を提供できます。 そのようなタプルは、(レスポンス、ステータス)、(レスポンス、ヘッダー)、 または(レスポンス、ステータス、ヘッダー)の形式でなければなりません。 ステータス値はステータスコードを上書きし、 ヘッダーは追加のヘッダー値のリストまたは辞書になります。
    If a tuple is returned the items in the tuple can provide extra information. Such tuples have to be in the form (response, status), (response, headers), or (response, status, headers). The status value will override the status code and headers can be a list or dictionary of additional header values.

  5. それでもうまくいかない場合は、Flaskは戻り値が有効なWSGIアプリケーションであると見なし、 それを応答オブジェクトに変換します。
    If none of that works, Flask will assume the return value is a valid WSGI application and convert that into a response object.

まとめ

Flask の view 関数はなんでも受け入れてくれる広い心の持ち主で、 とてもいいやつそうに見えます。

いい感じに自動的(暗黙的に)にやってくれるのは、 動的言語のメリットであると思います。 しかし、上記の説明は煩雑で煩わしいものであったのではないかなと感じます。

The Zen of Python - PEP 20

  • 明示的であることは、暗黙的であるより良い。
    Explicit is better than implicit.

  • もし実装が説明困難なら悪い実装だ
    If the implementation is hard to explain, it's a bad idea.

では、なぜこのような 重箱の隅を突くような話を事細かにさせていただいたのかと言うと

それは実は後年、Armin Ronacher 自身、 Flask ではないのですが、 Python の細かい部分での一貫性のない挙動、暗黙的にいろんなことをやってくれる動作を嫌がっていた 気配のある記事を書いています。

Flask くらいならいいのでしょうがやはりより大きなものを作ろうとするときは、 きっと厳しく型を定めていかないといけないのかなと思ったりもします。

現在は Armin Ronacher は、より型システムの厳格な言語である Rust に軸足を移してしまいました。 id:methane 様のツイートで知りました。

4. ルーティング

ルートの書式の公式ドキュメントがどこにあるかわからなくて 探し待ってました笑

@app.route('/')  # <--- ここの部分の書き方の資料です笑
def index():
    return 'Hello World'

flask.Flask.route メソッドの公式の説明書はこちら。

(1) 書式

route の書式の公式の説明書は、こちら。

string accepts any text without a slash (the default)
int accepts integers
float like int but for floating point values
path like the default but also accepts slashes
any matches one of the items provided
uuid accepts UUID strings

Django は 1 系から 2 系にバージョンアップするにあたり 正規表現から Flask ライクな形式に移行しました。 Armin Ronarcher のセンスすごいと思ってしまうわけです。

Django 2.0の変更点について - Qiita

従来の正規表現を用いた書き方(django.conf.urls.url())に加えて、 新たにシンプルな書き方(django.urls.path())ができるようになりました。

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
path('articles/<int:year>/', views.year_archive),

(2) デコレータ

デコレータ @ については、以下の記事で学びました。

ちなみに、ルートのディスパッチは線形探索らしいです。 辞書的な形で O(1) に近い形でやるのかなと思ったのですが、意外とここも単純でした。

5. エンドポイント, endpoint

Flask のエンドポイント, endpoint とはビュー, view に付与された文字列です。簡単に言えば、関数の名前です。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

# 1) 例えば
#    このビューのエンドポイントは
#    'view' になります。
@app.route('/')
def view():  
    # 2) また url_for を使えば
    #    エンドポイントから
    #    ルート相対パスを取得できます。
    return flask.url_for('view') 

app.run()

endpoint は識別子なんだ、この識別子を使って、 あなたのコードのどの部分がリクエストを捌くかを判別するんだ。
the "endpoint" is an identifier that is used in determining what logical unit of your code should handle the request.
What is an 'endpoint' in Flask? - Stakoverflow

(1) url_for 関数

url_for 関数の引数に関数名 endpoint を渡すと ルート相対パス に変換します。 flask.redirect とセットで用いられたり、 あるいは template 内でも使われたりします。

以下、公式マニュアルから。

flask.url_for
指定された endpoint への URL を生成します。
Generates a URL to the given endpoint with the method provided.

url_for() 関数は名前と引数に基づいて URL を生成します。 ビュー関数と関連したこの名前のことを、 endpoint とも呼びます。
The url_for() function generates the URL to a view based on a name and arguments. The name associated with a view is also called the endpoint, and by default it’s the same as the name of the view function.
Endpoints and URLs - Flask

(2) 問題

問題です。Flask では1つのビューに2つの URL を指定できます。 以下のコードを実行して

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

@app.route('/hello')
@app.route('/nihao')
def view():
    return flask.url_for('view')

app.run()

以下の URL にアクセスした時、表示される文字列は、 hello ですか?それとも nihao ですか?

たいした意味はないのですが、おそらく線形探索してるんだろうな、 くらいの雰囲気が伝わればと思いました。

(3) endpoint を書き換える。

エンドポイントは基本的には関数名です。 しかし、キーワード引数 endpoint を使って変更できます。 いつ使うかは知りません。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

@app.route('/', endpoint='endpoint')
def view():
    return flask.url_for('endpoint')

app.run()

(4) 引数付きの flask.url_for

url_for 関数には、エンドポイント以外の引数を渡せます。 以下のコードをコピペして実行して見てください。 print 文が何を出力しているか、見ていただけると幸いです。

「実行してブラウザにアクセスして下さい」は、さすがに面倒くさそうだったので、 test_request_context を使いました笑

また、なぜ with 文を使っているかは、このあと、コンテクストを説明する際に、 見ていきます。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

# 1)
@app.route('/view0/')
def view0():
    return ''

with app.test_request_context('/view0/?arg0=hello&arg1=nihao'):
    print(flask.request.args['arg0'])
    print(flask.request.args['arg1'])
    print(flask.url_for(
        'view0', arg0='hello', arg1='nihao',))


# 2)
@app.route('/view1/<string:arg0>')
def view1(parameter0):
    return ''

with app.test_request_context('/view1/hello?arg1=nihao'):
    print(flask.request.view_args['arg0'])
    print(flask.request.args['arg1'])
    print(flask.url_for(
        'view1', arg0='hello', arg1='nihao',))


# 3)
@app.route('/view2/<string:arg0>/<string:arg1>')
def view2(arg0, arg1):
    return ''

with app.test_request_context('/view2/hello/nihao'):
    print(flask.request.view_args['arg0'])
    print(flask.request.view_args['arg1'])
    print(flask.url_for(
        'view2', arg0='hello', arg1='nihao',))

(6) 疑問: なぜ URL でないのか

flask.redirect には2種類の書き方があります。 ルート相対パスを書く方法と、エンドポイントを各2種類の方法です。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

@app.route('/')
def index():
    # 1. ルート相対パスで
    #    リダイレクト
    return flask.redirect('/hello')

@app.route('/hello')
def hello():
    # 2. エンドポイントで
    #    リダイレクト
    return flask.redirect('nihao')

@app.route('/nihao')
def nihao():
    return 'Nihao, shijie!'

app.run()

なぜルート相対パス /hello でも書けるのに、 わざわざエンドポイント nihao を用意したのでしょうか?

理由はわかりません。 でも、1つのビュー関数に2つ以上の URL を定義できたりします。 url_for 関数を使うと、テンプレートを書くときに、 それなりに綺麗に書けたりするので、その辺りが意図かなと、 勝手に思い込んでいます。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

@app.route('/hello')
@app.route('/nihao')
def view():
    return flask.url_for('view')

app.run()

以下のリンク先は、2つの URL に複数のルートを定義している例です。 あと、全部の URL を捕まえる方法になります。 練習にはちょうど良いかも。

(6) 疑問: なぜ identity でないのか

Flask は何かを特定するときに文字列を多用します。 例えば FLask クラスや Blueprint クラスもそうでした。

個人的には import しちゃえばよかったんじゃないかなと。 import すると identity で管理することになります。

公式チュートリアルからコードをちょっと引っ張ってきます。

以下のコードはログインしていなかったら 'auth.login' というエンドポイントを持つ ビューに飛ばしてくださいね、というコードです。

#
# 本物
#    エンドポイント、文字列で管理する
#
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

import させるなら、こんな感じになります。

#
# 偽物
#    identity で管理する。
#
import auth

def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for(auth.login))

        return view(**kwargs)

    return wrapped_view

文字列として参照してしまうのはアンチパターンな気がします。

1. メソッドやプロパティが文字列として参照されている - まずコードの可読性を最適化しよう

一部のフレームワークはこれをしがちです。 「コールバック」を文字列として渡し、必要に応じてリフレクションを使います。 この場合、古き良きCMD+Fを使いましょう。

最悪なフレームワークは、動的言語で、これらの文字列を動的にします。JavaScriptやActionScript 3.0がそうです。

文字列をエンドポイントとして使うことのメリットは import しなくてもいいことです。 そしてデメリットは、import してないので、どこで定義されているか、わからないということです。

そもそも Flask は大きいものは作らないよ、という設計思想なので、 どこで定義されてるかわかりにくいという指摘も、 もしかしたらお門違いなのかもしれません。

6. ブループリント, blueprint

(1) 概要

blueprint は view を束ねたもの。app は blueprint を束ねたものです。 app > blueprint > view のような具合になります。

例えば以下のように app には無数の view がつけておくと混乱してしまいます。

app -------------  view, view, view, view, view, view, view, view

blueprint することが分割できます。

app -------------  view, view
 |---- blueprint - view, view
 |---- blueprint - view, view
 |---- blueprint - view, view

(2) コード

例えば Flask の公式チュートリアルで作るブログアプリ flaskr は以下のような構成になっています。

app 
 |---- blog ------ index, create, delete
 |---- auth ------ register, login, logout

これをコードにすると、だいたい以下のようになります。 対話モード >>> にコピペで実行できます。

ただ、意味のあるコードというわけではありません。 実行して url_for が何を返すかを見るために、書きました。

@app.route('/') とするのではなく @blog_bp.route('/') とブループリントに登録している イメージだけつければと思いました。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

#
# blog_bp
#     ブログのブループリト
#
blog_bp = flask.Blueprint('blog', __name__, url_prefix='')
@blog_bp.route('/')
def index():
    return flask.url_for('blog.index')

@blog_bp.route('/create')
def create():
    return flask.url_for('blog.create')

@blog_bp.route('/delete')
def delete():
    return flask.url_for('blog.delete')

#
# auth_bp
#     認証用のブループリント
#
auth_bp = flask.Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/register')
def register():
    return flask.url_for('auth.register')

@auth_bp.route('/login')
def login():
    return flask.url_for('auth.login')

@auth_bp.route('/logout')
def logout():
    return flask.url_for('auth.logout')

app.register_blueprint(blog_bp)
app.register_blueprint(auth_bp)
app.run()

(3) エンドポイント

通常はビュー関数の名前がエンドポイントになります。 view が app 直下ではなく blueprint に割り当てられた場合は、 エンドポイントは以下のようになります。

ブループリントの名前.関数の名前

例えば register というビュー関数のエンドポイントは、 ブループリントの名前 auth + ビュー関数の名前 registerauth.register となります。

auth_bp = flask.Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/register')
def register():
    return flask.url_for('auth.register')

(4) ブループリントの引数

だいたい以下のような具合です。

auth_bp = flask.Blueprint(
    # 1) 名前
    name='auth',
    # 2) 位置
    import_name=__name__,
    # 3) 起点
    url_prefix='/auth'
)

name

name はわかりやすいです。 ここで定義された name は配下のブループリントのエンドポイントの接頭辞になります。

また、クラス定義文を動的に書こうと思った時にも、 文字列でクラス名を書かないといけません。これと感覚的に近いかなと思います。

# 1 と 2 は等価です。

# 1
class C:
    pass

# 2
C = type('C', (object, ), {})

import_url

import_name は何をしているか、さっぱり見当がつきません。 次の章で見ていきます。

url_prefix

url_prefix も、まだわかります。 これを起点にしてルート相対パスが決まるのでしょう。

例えば ビュー関数 register への URL は url_prfix'/auth' + '/register''/auth/register' となります。

auth_bp = flask.Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/register')
def register():
    return flask.url_for('auth.register')

7. Flask の __main__ ってなに?

(1) 疑問

いつも思うんです。この __name__ ってなに?って

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app       = flask.Flask(                 __name__)
blueprint = flask.Blueprint('blueprint', __name__)
                                       # ^^^^^^^^ これです
@blueprint.route('/')
def index():
    return 'Hello, world!'

app.register_blueprint(blueprint)
app.run()

(2) 答え

template や static ディレクトリがどこに置かれているかを判別するために、 使うそうです。

Quickstart - Flask
これは Flask がテンプレートや静的ファイルなどがどこに置かれているかを 知るのに必要です。 より詳しくは Flask のドキュメントをご確認ください。
This is needed so that Flask knows where to look for templates, static files, and so on. For more information have a look at the Flask documentation.

このことについては、以下のリンクから気づくことができました。

あとリンク先で簡単にソースコードを追いかけてくれています。

(3) __name__ ってなに?

あれ?__name__ には __main__ が代入されるんじゃないの?と疑問に思われるかもしれません。 実は、それは「スクリプト」として実行された時だけです。

「モジュール」、「パッケージ」として import された時は、 「スクリプト」からの相対パスが代入されます。

例えば、以下の様なディレクトリ階層について

$ tree
.
├── script.py
└── package0
    ├── __init__.py
    ├── module1.py
    └── package1
        ├── __init__.py
        └── module2.py

2 directories, 5 files
$ 

各々 __name__ を表示させて見ると以下の様になります。

$ python3 script.py 
__file__: script.py
__name__: __main__
__file__: /Users/user/__name__/package0/__init__.py
__name__: package0
__file__: /Users/user/__name__/package0/module1.py
__name__: package0.module1
__file__: /Users/user/__name__/package0/package1/__init__.py
__name__: package0.package1
__file__: /Users/user/__name__/package0/package1/module2.py
__name__: package0.package1.module2
$ tree

__name__
__name__ 属性はモジュールの完全修飾名に設定されなければなりません。 この名前を利用してインポートシステムでモジュールを一意に識別します。

__main__
'__main__' はトップレベルのコードが実行されるスコープの名前です。 モジュールが、標準入力から読み込まれたとき、スクリプトとして実行されたとき、 あるいはインタラクティブプロンプトのとき、__name__ には '__main__' が設定されます。

今回使用したサンプルコードはこちらにあります。

import は意外に難しいのです。 以下の記事で説明させていただきました。 2つがポイントです。 パスの概念。「スクリプト」、「モジュール」、「パッケージ」の3つの違いを理解することです。

(4) もうちょっと詳しく

公式マニュアルと調べてみると import_name という名前の引数らしいです。 これはなにものでしょうか?

class flask.Flask(          import_name, ...
class flask.Blueprint(name, import_name, ...

import name - アプリケーションパッケージの名前
import_name - the name of the application package
flask.Flask - Flask

該当箇所を引っ張ってきました。 公式ドキュメントによる とtemplate や static ディレクトリの場所を 指定するだけではなくデバッグ時にも必要らしいです。

About the First Parameter - Flask
The idea of the first parameter is to give Flask an idea of what belongs to your application. This name is used to find resources on the filesystem, can be used by extensions to improve debugging information and a lot more.

So it’s important what you provide there. If you are using a single module, __name__ is always the correct value. If you however are using a package, it’s usually recommended to hardcode the name of your package there.

For example if your application is defined in yourapplication/app.py you should create it with one of the two versions below:

app = Flask('yourapplication')
app = Flask(__name__.split('.')[0])

Why is that? The application will work even with __name__, thanks to how resources are looked up. However it will make debugging more painful. Certain extensions can make assumptions based on the import name of your application.

For example the Flask-SQLAlchemy extension will look for the code in your application that triggered an SQL query in debug mode. If the import name is not properly set up, that debugging information is lost. (For example it would only pick up SQL queries in yourapplication.app and not yourapplication.views.frontend)

(5) FastAPI

FastAPI などの同等のマイクロフレームワークで確認しました。 しかし、見当たりません。 デバッグ時でも必ずしも必要なものではないのかもしれません...

#
# FastAPI のコード
#
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}
$ # app.run() は見当たらないので
$ # uvicorn という ASGI をインストールして実行する様子
$ pip install uvicorn
$ uvicorn main:app --reload

静的ファイルを追加する時は app.mount でディレクトリを明示する様子です。

#
# FastAPI のコード
#
from fastapi import FastAPI
from starlette.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

それにしても __name__ は必要なさそう... __name__ などの文字列 str を使って何かを識別しようとするのは、 Flask の特徴の様な気がします。

8. コンテクスト

コンテクストとはグローバル変数です。 Flask はグローバル変数を多用します。 これが理解へのポイントです。

Python にはスコープがあります。 例えば built-in, global, local という三段階層です。 その辺りの話はここで見ていきました。

これと同じようにして Flask にもスコープではないのですが、 名前空間がネストしています。 flask.g, flask.current_app, flask.request です。

そして、このコンテクスト, context は Flask の引っかかりどころです。 これらはコンテクストに関する疑問です。

(1) flask.request

コンテクストというと一般に文脈みたいな意味合いがあります。  ここではコンテクストはグローバル変数、みたいなものだと思って考えて下さい。 

なぜこのような言葉が生じたかというと Flask がグローバル変数 flask から request を受けるような設計になっているからです。

で、ここで着目していただきたいことは flask というグローバル変数から Request Context という「状態」を取得しています。

繰り返しになります、関数の引数として渡せばよかったのでは?と思いました。 関数の引数に request をバトン渡しするコードを書くのが嫌だったらしいです。 公式ドキュメントのどこかに書かれていた気がするのですが、失念しました...

import flask

@app.route('/')
def index():
    print(flask.request)
    return 'Hello, world!'
import flask

@app.route('/')
def book(request):  # <--- 引数で渡せばよかったんじゃないの?
    print(request)
    return 'Hello, world!'

上で見てきた引数付き url_for 関数のサンプルコードをもう一度持ってきます。 with 文を使っています。with 文を使ってそういうコンテクスト、文脈を設定しているということです。

#
# 対話モード >>> に
# コピペで実行できます。
#
import flask
app = flask.Flask(__name__)

# 1)
@app.route('/view0/')
def view0():
    return ''

#
# with 文を使って
# flask.request にリクエストを代入している。
#
with app.test_request_context('/view0/?arg0=hello&arg1=nihao'):
    print(flask.request.args['arg0'])
    print(flask.request.args['arg1'])
    print(flask.url_for(
        'view0', arg0='hello', arg1='nihao',))

(2) flask.current_app

init_app というメソッドを見かけますが、 これはアプリケーションコンテクストというグローバル変数、状態を初期化しています。

db = SQLAlchemy()
        db.init_app(app)

これ、本当に自分も最初は何しているのかチンプンカンプンでした。

teratail にも似た様な質問が上がっています。

これの考え方としては app というグローバル変数に、 db が使うコンテクストを設定しています。 自分も最初これがなにを意図しているのか、わかりませんでした。

また Flask は、1度に複数のアプリケーションを持つことができます。 そのため g ではなく app に設定しているということなのでしょうか。

#
# 本物
#
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'
#
# 偽物
#
from flask import route

@route('/')
def index():
    return 'Hello World!'

なぜ、このような設計になったかについては、こちらに書かれています。 結構、面白いです。

(3) flask.g

Flask には g があり、その配下に Application Context があり その配下にRequest Context という2つの Context があります。

通常 Python のグローバル変数というとモジュールの中だけですが、 g は Flask application 内のグローバル変数です。

Application Context は Application のグローバル変数を保持しています。 Request Context は Request のグローバル変数を保持しています。

この Context というのは、日本語で言えば「状態」になります。 例えば、Request Context を参照するには以下の様になります。

9. リンク

読んだわけではないのですが、リンクを集めました。

いまさらながら Flask についてまとめる

  1. はじめに
  2. Routing
  3. Template
  4. Logging
  5. Blueprint
  6. エラーハンドリング

そのほか

10. おわりに

以上、Flask の基本的な機能についておさらいしました。 ここまで、以下の流れで見てきました。

上の方で散々、欠点を3、4つもあげて、 挙句の果てには Armin Ronacher はいなくなったとか書いてると、 なんかディスってるみたいです。

  1. グローバル変数を使ってしまう(flask.request)
  2. 識別に文字列を多用してしまう(endpoint, import_name?)
  3. ビュー関数の返り値の型が自由すぎる。

それでも元々の設計思想が大きいものは作らない、 なので上で挙げた欠点は、そんなに大した欠点でもないと思ったりします。 欠点があるということは、それに対応した利点があります。

Armin Ronacher は、イケメンにして天才なのです。

Flask, jinja2, click, Rust言語のredis-rs等のを使ってきて感じるのは、 Arminも間違いなくDXの天才であること。...
yukinarit84 さんのコメント 2018/12/02

Last Updated: 8/5/2019, 11:31:06 AM