メモ_JavaScriptでの有効桁数

Pythonで処理した大きな桁の数(例えば2の69乗=590295810358705700000)をFlaskアプリなどでWebページに表示する際、Java scriptで別処理を行ってから表示すると、数字が丸まったり指数表示になってしまうケースがあります。

原因はJavaScriptでの有効桁数の上限です。JavaScriptでの数値はIEEE 754の倍精度浮動小数点数として表現されます。そのため、10進での有効桁数は約16桁です。ただし、JavaScriptでは安全な整数と呼ばれる範囲内でしか正確に計算できません。安全な整数とは、-2の53乗から2の53乗-1までの整数です。

これを回避するには、bignumber.jsやdecimal.jsといった数値計算ライブラリを使います。

Jupyter lab / notebook 上で、GUIで画像のフォーマット変換&リサイズを行うPythonコード

Jupyter lab / notebook 上で、GUIで画像のフォーマット変換&リサイズを行うPythonコードです。たまに画像を扱う際、Jupyter lab / notebook の操作画面からできるだけ離れたくない場合などに使う想定です。

 

コード:


import ipywidgets as widgets
from IPython.display import display
from PIL import Image
import io
import os
from ipyfilechooser import FileChooser

def resize_image(file, output_format, scale_percent):
    img = Image.open(file)
    width, height = img.size
    new_width = int(width * scale_percent / 100)
    new_height = int(height * scale_percent / 100)
    resized_img = img.resize((new_width, new_height))
    output_buffer = io.BytesIO()
    resized_img.save(output_buffer, format=output_format)
    return output_buffer.getvalue(), new_width, new_height

def image_resize_app(file_content, output_format, scale_percent):
    try:
        image_data, new_width, new_height = resize_image(io.BytesIO(file_content), output_format, scale_percent)
        img_widget = widgets.Image(value=image_data, format=output_format, width=new_width, height=new_height)
        display(img_widget)
        
        # Save the resized image
        file_path = file_selector.selected
        file_name, file_ext = os.path.splitext(os.path.basename(file_path))
        output_file_name = f"{file_name}_new.{output_format}"
        with open(output_file_name, 'wb') as output_file:
            output_file.write(image_data)
        
        print(f"Resized image saved as: {output_file_name}")
    except Exception as e:
        print("Error occurred:", e)

file_selector = FileChooser()

output_format_selector = widgets.Dropdown(
    options=['png', 'jpg', 'bmp', 'gif'],
    value='png',
    description='Output Format:'
)

scale_percent_slider = widgets.IntSlider(
    value=100,
    min=1,
    max=100,
    step=1,
    description='Scale Percent:'
)

resize_button = widgets.Button(description="Resize & Save Image")

def on_resize_button_clicked(b):
    with output:
        output.clear_output()
        if file_selector.selected:
            file_path = file_selector.selected
            with open(file_path, 'rb') as file:
                file_content = file.read()
            output_format = output_format_selector.value
            scale_percent = scale_percent_slider.value
            image_resize_app(file_content, output_format, scale_percent)
        else:
            print("Please select an image file.")

resize_button.on_click(on_resize_button_clicked)

output = widgets.Output()

display(file_selector, output_format_selector, scale_percent_slider, resize_button, output)

結果:

以下のように成功しました。サンプルとして犬の大きなjpg画像を、15%にリサイズしたgif画像に変換してます。

最初の表示

 

画像の選択

 

変換&リサイズ後

 

メモ_Jupyter lab での設定

Jupyter lab 起動時のディレクトリの指定

Jupyter lab が起動した際、デフォルトだと立ち上げた場所がルートになりますが、指定したディレクトリに設定することできます。これでどこから立ち上げても、いつもの作業場所などでJupyter lab が開けます。

 

手順1:

以下のコマンドでjupyter labのconfigファイルの作成します。

jupyter lab --generate-config

jupyter_lab_config.py というファイルが作成されます。

手順2:

適当なエディターで、configファイルを開いて、以下の記述を探します。

# c.ServerApp.root_dir = ''

#を消して有効にして、指定したいディレクトリーのパスを入れてconfigを保存します。

c.ServerApp.root_dir = '/home/workspace/code/~'
手順3:

jupyer labを起動、あるいは再起動します。

jupyter lab

指定したディレクトリーで開かれるはずです。

Jupyter lab でhtmlファイルをデフォルト編集モードで開く設定

webアプリなどを開発している際、テンプレートとしてhtmlファイルを編集する際、デフォルトだとhtml ビューワで開いてしまうので、ファイルをクリックしたら、デフォルトで編集モードで開きたい場合の設定方法です。

手順1:

メニューバーの Setting から、 Advanced Settings Editor を選択。

手順2:

JSON Settings Editor をクリックして、Document Managerを開く。

手順3:

User Preferences に、以下のような記述を追加する


{
  "defaultViewers": {
    "html": "Editor"
  }
}

メモ_Ubuntu22×MySQL8での自動起動設定

OSが起動した際に、MySQLを自動的に起動する、あるいは自動起動しないようにする設定方法のメモ。

 

手順

Ubuntu22は、systemd系の自動起動プロセスになっている(古いLinuxだとSysVinit系)

次のコマンドで、自動起動プロセスの一覧が確認できる。

systemctl list-units

mysql自動起動になっているかを確認する際は下記のようにする。

systemctl list-unit-files | grep mysql

ここで「mysql.service enabled enabled」となっていれば自動起動になっているが、disable であれば自動起動しない設定になっている。

自動起動するには、以下のコマンドを実行する。

sudo systemctl enable mysql.service

逆に自動起動を解除する際は以下。

sudo systemctl disable mysql.service

「systemctl disable サービス名.service」という書式なので、mysql以外、httpdなどでも同様。

あとは再起動してみて、「service mysql status」でステータスを確認すればOK。以下のように起動直後でactiveになってます。

 

メモ_JupyterNotebookでtqdmを利用してプログレスバーを表示する

JupyterNotebookで処理時間が長いコードを実行する際、完了までどのくらいかかるか、どのくらい進行したかをリアルタイムでプログレスバーで表示したい場合、tqdmというライブラリを利用することができます。

pip install tqdm
コード例:

サンプルとして、MySQLのデータベースに素数を格納する処理を実行します。上限は自然数100万で、78498個の素数が格納されます。

from tqdm import tqdmによってtqdmをインポートし、with tqdm(total=len(primes), desc="Inserting primes") as pbar:というコンテキストマネージャを使用してプログレスバーを表示しています。ループ内でpbar.update(1)を呼び出すことで、進行状況をリアルタイムで更新しています。 これで、Jupyter Notebookでコードを実行すると、進行中であることをプログレスバーで確認できるようになります。


import time
import json
import mysql.connector
from tqdm import tqdm #プログレスバー表示用のライブラリをインポート

start_time = time.time()

# データベース情報
database_info = {
    "host": "YOUR_MYSQL_HOST",
    "user": "YOUR_MYSQL_USERNAME",
    "password": "YOUR_MYSQL_PASSWORD",
    "database": "YOUR_database"
}

limit = 1000000
sieve = [True] * (limit + 1)
sieve[0] = sieve[1] = False

for i in range(2, int(limit ** 0.5) + 1):
    if sieve[i]:
        for j in range(i * i, limit + 1, i):
            sieve[j] = False

primes = [i for i in range(limit + 1) if sieve[i]]

# MySQLデータベースに素数を保存
try:
    conn = mysql.connector.connect(**database_info)
    cursor = conn.cursor()

    # 既存のデータを削除
    cursor.execute("TRUNCATE TABLE prime_list")

    # 新しい素数リストを挿入
    with tqdm(total=len(primes), desc="Inserting primes") as pbar:
        for prime in primes:
            cursor.execute("INSERT INTO prime_list (prime_num) VALUES (%s)", (prime,))
            pbar.update(1)

    conn.commit()
    print("Successfully saved the primes to the database.")

except mysql.connector.Error as err:
    print("Error:", err)

finally:
    if cursor:
        cursor.close()
    if conn:
        conn.close()

end_time = time.time()

print("Found", len(primes), "primes.")
print("Time taken: {:.5f} seconds".format(end_time - start_time))

結果:

以下のように成功しました。せっかくのプログレスバーなのでgifアニメで貼り付けました。とても簡単なコードでこういうことが実現できるのは、本当に素晴らしいですね。

 

メモ_FlaskアプリでDBから指定範囲のデータをjsonでダウンロードさせる機能の追加

Flaskで構築したWebアプリに、DBから指定した範囲のデータをjson形式でダウンロードさせる処理を追加する際のメモ。

 

サンプルの仕様:

id(連番)、prime_list(素列を格納)というカラムがあるDB(DB名はnuminfo、table名はrime_num)から、idないしprime_listの範囲を指定してデータを取得する。例えば、id=1〜id=100なら、100番目までの素数の値がjsonで得られる。

 

手順1. データを取得する関数を定義

db_utils.pyはデーテベース接続とデータ取得処理をまとめたもの。別途、app.pyから呼び出す


# db_utils.py
import mysql.connector

def get_data_by_id_range(start_id, end_id):
    try:
        # MySQL接続情報の設定 (必要に応じて変更)
        db_config = {
            'host': 'localhost',
            'user': 'ユーザー名を入れる',
            'password': 'パスワードを入れる',
            'database': 'numinfo'
        }

        # MySQLサーバに接続
        conn = mysql.connector.connect(**db_config)
        cursor = conn.cursor()

        # idの範囲を指定してデータを取得
        query = f"SELECT prime_num FROM prime_list WHERE id >= {start_id} AND id <= {end_id}"
        cursor.execute(query)
        data = [row[0] for row in cursor.fetchall()]

        cursor.close()
        conn.close()

        return data

    except Exception as e:
        print("エラーが発生しました:", e)
        return []

# 同様に prime_num の範囲を指定してデータを取得する関数を追加する

手順2. json取得画面のテンプレートにformを設置。post先の「/get_data」はapp.pyで定義(手順3で説明)

便宜上、テンプレートはhome.htmlとする。以下のformタグを入れる


    <h1>データ取得フォーム</h1>
    <form method="post" action="/get_data">
        <label for="form_type">取得タイプ:</label>
        <select name="form_type" id="form_type">
            <option value="id_range">ID範囲</option>
            <option value="prime_num_range">素数範囲</option>
        </select>
        <br>
        <label for="start_range">開始範囲:</label>
        <input type="text" name="start_range" id="start_range">
        <br>
        <label for="end_range">終了範囲:</label>
        <input type="text" name="end_range" id="end_range">
        <br>
        <input type="submit" value="データ取得">
    </form>

手順3.ユーザーが入力した範囲に応じてデータを取得し、JSONファイルを出力する処理する先として「/get_data」を定義する

以下をapp.pyとかに記載する。これで、formを置いてあるWebページから、条件にそったjsonがダウンロードできるようになります。


#db_utils.pyをここで呼び出す
import db_utils

#以下、中略


@app.route('/get_data', methods=['POST'])
def get_data():
    form_type = request.form['form_type']
    start_range = int(request.form['start_range'])
    end_range = int(request.form['end_range'])

    if form_type == 'id_range':
        data = db_utils.get_data_by_id_range(start_range, end_range)
        output_file = 'prime_id_output.json'
    else:
        data = db_utils.get_data_by_prime_num_range(start_range, end_range)
        output_file = 'prime_num_range_output.json'

    # JSONファイルにデータを書き込む
    with open(output_file, 'w') as json_file:
        json.dump(data, json_file)

    # レスポンスにContent-Dispositionヘッダーを設定して、ブラウザがダウンロードするようにする
    response = Response(json.dumps(data), content_type='application/json')
    response.headers['Content-Disposition'] = f'attachment; filename={output_file}'

    return response
結果:

下記のように成功しました。formで指定した内容にそったデータがjsonでダウンロードされて、無事にエディターで開けました。

メモ_Flaskアプリでapp.py以外でルート定義を行う方法

Flaskアプリを開発していて、app.pyがだんだん肥大化して見通しが悪くなることがあります。ルート(エンドポイント)定義をapp.py以外に切り出してコードをスッキリさせることができます。

一般的な方法は、FlaskのBlueprintを使用することです。Blueprintは、アプリケーションの機能をモジュール化して、アプリケーション全体にわたって再利用可能なコンポーネントを作成するための仕組みです。

 

1. Blueprintを作成する

別のファイルにルートを定義するために、新しいPythonファイル(例:routes.py)を作成し、Blueprintを使用してルートを定義します。


# routes.py

from flask import Blueprint

# Blueprintオブジェクトを作成します。
# "my_routes"はBlueprintの名前で、第2引数はBlueprintが属するモジュールの名前です。
my_routes_bp = Blueprint('my_routes', __name__)

# ルートを定義します。
@my_routes_bp.route('/my_route')
def my_route():
    return 'This is my custom route!'

Blueprintを登録する

app.py(または別のエントリーポイントファイル)で、作成したBlueprintをアプリケーションに登録します。


# app.py

from flask import Flask
from routes import my_routes_bp  # 作成したBlueprintをインポートします

app = Flask(__name__)

# Blueprintを登録します。
app.register_blueprint(my_routes_bp)

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

これで、routes.pyで定義した/my_routeエンドポイントが、app.pyで定義したFlaskアプリケーションに組み込まれます。