🔥

【Flask】初期化時の一つの謎を解明したので解説【無駄知識】

2023/09/12に公開

Flaskを使ってアプリケーションを作成している時に、ちょっとした疑問が浮かんだ箇所がありまして、その疑問を調査によって解消することができたので備忘録として残しておきます。

実行環境

OS: Ubuntu 18.04.2 LTS
python: 3.6.8
Flask: 1.1.1

疑問の内容

以下のFlaskアプリケーションを起動させるファイルを実行してみた時に事件は起こりました。

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
    return "Hello World!"
app.run()

上記コードが記載されたファイルを実行してみた結果は以下になります。

$ python study_flask.py
 * Serving Flask app "study_flask" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

ちゃんとFlaskのサーバーが起動してくれています。

しかし以下の一行が引っかかりました。

* Serving Flask app "study_flask" (lazy loading)

直接実行したファイル内で__name__を名称としたFlaskを作成しているのに、
Flask app名が__main__ではなく、ファイル名になっています。

__name__とはpythonの予約語の変数で、基本的に拡張子なしのファイル名が保存されています。
ただし__name__が記述されたファイルが直接実行された時のみ__main__という文字列が代入されます。

今回の場合は__name__が記載されたファイルを直接実行しているので、
__name__という名称のFlask appが出来上がると思っていたのですが、実際にはファイルの名称が格納されていました。

調査結果

検索して調査してみたのですが、解説が載っているサイトを見つけられなかったため、直接Flaskのソースコードを読むことにしました。

class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.
    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
    ~~省略〜〜
  """
    def __init__(
        self,
        import_name,
        static_url_path=None,
        static_folder="static",
        static_host=None,
        host_matching=False,
        subdomain_matching=False,
        template_folder="templates",
        instance_path=None,
        instance_relative_config=False,
        root_path=None,
    ):

__name__は直接flaskの名称になるのではなく、import_nameの引数として渡されている様です。またこのimport_nameはself.import_nameに代入されていました。

さらに読み進めていくと以下のような関数を発見。

def name(self):
    """The name of the application.  This is usually the import name
    with the difference that it's guessed from the run file if the
    import name is main.  This name is used as a display name when
    Flask needs the name of the application.  It can be set and overridden
    to change the value.
    .. versionadded:: 0.8
    """
    if self.import_name == "__main__":
        fn = getattr(sys.modules["__main__"], "__file__", None)
        if fn is None:
            return "__main__"
        return os.path.splitext(os.path.basename(fn))[0]
    return self.import_name

色々処理していますが、重要なのはここ↓です。

if self.import_name == "__main__":
    fn = getattr(sys.modules["__main__"], "__file__", None)
     "~~省略~~"
    return os.path.splitext(os.path.basename(fn))[0]

もしself.import_nameが__main__だったら、最初に実行したファイルの名称を取ってくるという処理が書かれていました。

まとめ

実際にコードを一から追ってみると結構単純でしたが、この答えにたどり着くまでに時間かなり使ってしまいました。

細かなところが気になってしまう性格は損しますね。でも直す気はないです。

Discussion