home about terms twitter

【Python】Flask+Sympyでhtmlとしてブラウザ上に計算処理結果を表示

date: 2021-08-01 | mod: 2021-08-01

browser_animation

前書き

今日では広く知られている通り、Pythonでは数値解析を行うことができます。これまでRなど他の言語でも行えてきたことではありますが、数値解析以外のアプリケーションとしての実装が容易なこともPythonを扱うメリットです。

そこで、このチュートリアルでは、Pythonを用いた数値計算結果をウェブブラウザ上に表示することを目的とします。 数値処理の結果を表示させることは、数値解析の結果の共有オンライン教育等において有意義であると言えます。

このチュートリアルで作成するのは最低限で簡便なウェブアプリケーションであり、Bootstrapなどの装飾は行わないものとします。

方法

開発環境の構築

このチュートリアルで使用している開発環境は以下の通りです。

  • Windows10
  • Python 3.7.4

pipを用いており、matplotlibもPySimpleGUIもインストールされていない場合は、以下のコマンドでインストールを行います。 condaを用いる際は別途conda用のコマンドをご参照ください。

  • pip install flask
  • pip install sympy

インストールが終了すると、これらのライブラリおよび依存するライブラリが使用できます。

今回用いたパッケージとそのバージョンは以下の通りです。

Package            Version
------------------ --------
click              8.0.1
colorama           0.4.4
Flask              2.0.1
importlib-metadata 4.6.3
itsdangerous       2.0.1
Jinja2             3.0.1
MarkupSafe         2.0.1
mpmath             1.2.1
pip                21.2.2
setuptools         57.4.0
sympy              1.8
typing-extensions  3.10.0.0
Werkzeug           2.0.1
wheel              0.36.2
zipp               3.5.0

Flask

FlaskはPython製の軽量なウェブアプリケーションフレームワークです(公式ドキュメント)。最初に必要な機能が最小限であるため、簡易なウェブアプリケーションを作成する際に優れています。

同様のウェブアプリケーションフレームワークにDjango(公式ドキュメント)があります。こちらは必要な機能が一通りそろっており、十分な規模のウェブアプリケーションを作成することができる点が優れています。

今回は簡単なウェブアプリケーションを作成したいので、flaskを選択します。

Sympy

Pythonにおいて数値計算や記号計算を行うためのライブラリです(公式ドキュメント)。身近な例では中学数学や高校数学で扱うような以下の計算を行うことが可能です。

  • 因数分解・展開
  • 指数・対数
  • 微分・積分
  • 順列・組み合わせ
  • 行列

ここに挙げているのは一例であり、さらに多くの計算を行うことができます。

今回は数値解析の例として因数分解・展開を行います。 因数分解および展開の例に関しては、[PythonのSympyを用いた因数分解と展開(二次式)]についてもご参照ください。

次の因数分解と展開を行います。 $$(ax+b)^2 = a^2x^2+2abx+b^2$$

コード

FlaskおよびSympyで処理を行うためのメインとなるPythonファイルは以下の通りです。

main.py

from flask import Flask, render_template, jsonify
import random as rd
import sympy as sp

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

# (x ± a)^2 -> x^2 +-2ax + a^2と展開を行う関数
def tenkai(key): 
    x = sp.Symbol('x')
    y = sp.Symbol('y')

    qa_all_dict = {} # 辞書として問題と答えを取得する

    for i in range(1, 10):
        a = rd.randint(1, 9) # xの係数(整数)
        b = rd.randint(1, 14) # 定数項(整数)
        
        # key == '+': (x + a)^2の意味
        if key == '+':
            expr = (a*x + b)**2
        elif key == '-':
            expr = (a*x - b)**2
        expd = sp.expand(expr)

        qus = str(expr).replace('**', '^').replace('*', '') # 問題
        ans = str(expd).replace('**', '^').replace('*', '') # 正答
    
        qa_dict = {"key": i, "question": qus, "answer": ans}
        qa_all_dict[i] = qa_dict

    return qa_all_dict

# index.htmlに記載する内容
@app.route("/")
def result():
    my_dic = tenkai('+') # tenkai()の引数が+ならば(x + a)^2, -ならば(x - a)^2
    return render_template('index.html', message=my_dic) 

if __name__ == '__main__':
    app.run()

アウトプットとして出力するためのテンプレートとなるhtmlファイルは以下の通りです。

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>因数分解</title>

    <!-- Katex config -->
    <!-- 数式を表示させるために必要 ここから -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" integrity="sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq" crossorigin="anonymous">
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" integrity="sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz" crossorigin="anonymous"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous"></script>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
          renderMathInElement(document.body, {delimiters: [
            {left: "\\[", right: "\\]", display: true},
            {left: "$", right: "$", display: false}
          ]});
        });
    </script>
    <!-- 数式を表示させるために必要 ここまで -->
    
  </head>

  <body class="print_pages">
      <!-- 問題の表示 ここから-->
      <div class="container">
        <div class="header">
          <h3 class="alert alert-info" role="alert">式の展開</h3>
        </div>
        <div class="border" style="text-align: center;">
          $公式: (x+a)^2 = x^2+2ax+a^2$<br>
          $x$に係数がある場合には次のようになります。<br>
          $(ax+b)^2 = a^2x^2+2abx+b^2$<br>
          分配法則を利用して計算することもできます。<br>
        </div>
        <br>
          <!-- main.pyから変数messageを取得したとき -->
          {% if message %}
            <table width="100%"><tr>
            <!-- 辞書であるmessageにあるkeyの数だけループ ここから-->
            {% for key in message %}
              <td>
              <!-- message[key].key = 問題番号、message[key].question = 問題
              問題はドルマークではさむことで数式として読み込ませる。 -->
              ({{message[key].key}}) ${{message[key].question}}$
              </td>
              <!-- 横に3つ問題を配置したら、brで余白をとってから次の行へ行く -->
              {% if key%3==0 %}
                </tr></table>
                <br><br><br><br><br>
                <table width="100%"><tr>
              {% endif %}
            {% endfor %}
            <!-- 辞書であるmessageにあるkeyの数だけループ ここまで-->
            <td></td>

            </tr></table>

            <br><br><br>
            <hr>

          {% endif %}
      </div>
      <!-- 問題の表示 ここまで-->

      <!-- 正答の表示 ここから-->
      <div class="container">
        <div class="header">
          <h3 class="alert alert-info" role="alert">式の展開</h3>
        </div>
        <div class="border" style="text-align: center;">
          $公式: (x+a)^2 = x^2+2ax+a^2$<br>
          $x$に係数がある場合には次のようになります。<br>
          $(ax+b)^2 = a^2x^2+2abx+b^2$<br>
          分配法則を利用して計算することもできます。<br>
        </div>
        <br>
          {% if message %}
            <table width="100%"><tr>
            {% for key in message %}
              <td>
              <!-- ${{message[key].answer}}$で答えを表示させている以外は問題の表示と同様 -->
              ({{message[key].key}}) ${{message[key].question}}$ <br>${{message[key].answer}}$
              </td>
              {% if key%3==0 %}
                </tr></table>
                <br><br><br><br>
                <table width="100%"><tr>
              {% endif %}
            {% endfor %}
            <td></td>

            </tr></table>

            <br><br><br>
            <hr>

          {% endif %}
      </div>
      <!-- 正答の表示 ここまで-->
    
  </body>

</html>

ディレクトリ構成

上記のファイルを含めたディレクトリ構成は以下の通りになります。

│  main.py
│
└─templates
        index.html

コマンド

FlaskおよびSympyをインストールした環境で以下のコマンドを実行します。

python main.py

結果

作成したコードを実行すると、以下の出力が得られます。

 * Serving Flask app 'main' (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)

この状態で任意のウェブブラウザ(Chrome等)からhttp://127.0.0.1:5000/にアクセスすると、以下の画面が現れます。

browser_result

ランダムな係数と定数項を用いた問題が9題分、出題されます。

その上部には因数分解と展開の公式を表示しています。

そのページを下へスクロールすると、先ほどの問題の答えが表示されています。

browser_answer

問題および正答はページが更新されるたびに別のものへ変化します。すなわちアクセスするたびに異なった問題を出題することが可能です。

browser_animation

「ランダムな問題と正答の更新」の実装

ランダムな問題と正答を作成するには以下のコードを用います。

for i in range(1, 10):
	a = rd.randint(1, 9) # xの係数(整数)
	b = rd.randint(1, 14) # 定数項(整数)

	# key == '+': (x + a)^2の意味
	if key == '+':
		expr = (a*x + b)**2
	elif key == '-':
		expr = (a*x - b)**2
	expd = sp.expand(expr)

	qus = str(expr).replace('**', '^').replace('*', '') # 問題
	ans = str(expd).replace('**', '^').replace('*', '') # 正答

	qa_dict = {"key": i, "question": qus, "answer": ans}
	qa_all_dict[i] = qa_dict

Pythonの標準ライブラリであるrandomでは、randintを用いることでランダムな整数を出力することができます(公式ドキュメント「random – 疑似乱数を生成する」)。

今回のコードではrandomrdとしてimportしていますので、本来はrandom.randint(1, 9)としているところをrd.randint(1, 9)としています。

randint(1, 9)では1~9の整数をランダムに返します。

randintによって得られた整数を$(ax+b)^2$の$a$と$b$に代入しています。

qus = str(expr).replace('**', '^').replace('*', '')では、Sympyによる計算からLaTex表記に合わせるために記号を変換しています。


以上の方法で、FlaskとSympyを用いて計算処理結果をブラウザ上に表示させることができます。

今回はごく簡単な因数分解と展開の式を扱いましたが、Sympyはさらに多様な解析を行うことが可能であり、また他のライブラリと組み合わせることでより幅広いアプリケーションを開発することが可能です。

また、今回はローカルサーバーとして実行しましたが、FlaskはGCP(Google Cloud Platform)等のクラウドサーバー上においても実行することができます。

本チュートリアルは本格的なウェブアプリケーションの足掛かりとなることが期待されます。

関連記事