ヒトリ歩き

愚痴とかいろいろ書きます

Pythonデコレータの実装方法

ロバストPythonでも出てきたが、自分でデコレータを作成してみた。引数ありなしで少し実装が変わるので注意したい。

デコレータ

デコレータは関数の前後に実行したい振る舞いを定義することができる。

コンテキストマネージャとの違いは?

コンテキストマネージャとデコレータは共に、処理の前後で何かしらの処理をしたいときに使うイメージがある。 私の理解だと下記の違いがあると考える。

  • 関数の前後で何かしらの処理をしたい場合に使用
  • ある特定の処理のブロックの前後で何かしらの処理をしたい場合に使用

コンテキストマネージャの場合、with文と使うことでファイルオープンしたオブジェクトをブロック内の処理に渡すことができるので、個人的にコンテキストマネージャの方が使いやすいという印象がある。

自前のデコレータを作るには

引数なしのデコレータと引数ありのデコレータでは実装が少し違うので、実際に作ってみる。

引数なしのデコレータ

下記の例の場合、callingデコレータを用意した。 callingデコレータとなる関数のパラメータにCallableを指定。これがデコレータを付与した関数が設定される。 _wrapper関数をcalling関数内で宣言し、_wrapper関数を返却する。
_wrapper関数は、パラメータとして、*args**kwargsを設定する。 *args**kwargsにデコレータを設定した関数のパラメータが設定される。
下記の例の場合、tanakaと 20*argsに設定されている。

"""
デコレータにする関数を宣言。パラメータはCallable
"""
def calling(func: Callable) -> Callable:
    """
    _wrapper関数を宣言。パラメータは*argsと**kwargs
    デコレータを付与した関数のパラメータが設定される。
    """
    def _wrapper(*args, **kwargs):
        print("calling decorator start ------->")
        print("args = " + str(args))
        # デコレータを付与した関数を実行
        func(*args, **kwargs)
        print("---------> calling decorator end")

    return _wrapper

@calling
def execute(name: str, age: int):
    print("execute start")

if __name__ == '__main__':
    execute("tanaka", 20)

実行すると下記のように、デコレータにした関数の前後のメッセージがデコレータを付与した関数の前後で出力されているのがわかる。

calling decorator start ------->
args = ('tanaka', 20)
execute start
---------> calling decorator end

引数ありのデコレータ

引数ありのデコレータの場合、デコレータにする関数の中で_wrapper関数を呼ぶ内部関数を定義する必要がある。 引数なしのデコレータとの違いは、 デコレータとなる関数から返す関数が_wrapper関数なのか、_wrapper関数をもつ内部関数を返すかの違い。

下記の場合、calling_taxデコレータを用意。パラメータがデコレータで指定するパラメータで戻り値はCallable。 calling_tax関数で内部関数を定義する。 この関数のパラメータはCallableで、デコレータを付与した関数が設定される。
内部関数の_calling_tax関数で_wrapper関数を用意する。ここは引数なしのデコレータと一緒。

"""
デコレータにする関数を宣言
パラメータはそのままデコレータのパラメータになる
"""
def calling_tax(name: str) -> Callable:
    """
    内部関数を宣言。パラメータはCallable
    このCallableは、デコレータを設定した関数が設定される
    """
    def _calling_tax(func: Callable):
        """
        _wrapper関数内で処理を実行
        *args、**kwargsがデコレータを付与した関数のパラメータの値が設定される
        """
        @functools.wraps(func)
        def _wrapper(*args, **kwargs):
            print("calling_tax decorator start ------>")
            print("name = " + name)
            print("args = " + str(args))
            func(*args, **kwargs)
            print("-------> calling_tax decorator end")
        return _wrapper
    return _calling_tax

@calling_tax("watanabe")
def execute_tax(address: str, tel: int):
    print("start execute_tax")
    print("address = " + str(address))
    print("tel     = " + str(tel))

if __name__ == '__main__':
    execute_tax("Kanagawa", 11111)

実行結果は下記のように、デコレータのパラメータも渡されており、デコレータを付与した関数の前後で設定したメッセージが出力されていることがわかる。

calling_tax decorator start ------>
name = watanabe
args = ('Kanagawa', 11111)
start execute_tax
address = Kanagawa
tel     = 11111
-------> calling_tax decorator end

まとめ

自分でデコレータを作成する方法を理解できた。 引数あり/なしで実装の仕方が少し違うので注意したい。
コンテキストマネージャとの使い分けも今後、考えながら自分でデコレータを用意して実装をシンプルに実現できるようにしていきたい。

参考

atmarkit.itmedia.co.jp

zenn.dev

note.nkmk.me

Pythonで簡単なGUI作成: Streamlitの活用

この記事が目に留まり、Streamlitを知ったので、使ってみた。 Pythonで簡単にWebアプリの画面が出来ちゃう

it.impress.co.jp

Streamlit(ストリームリット)

Streamlitは、データ分析型のWebアプリケーションフレームワークとして広く利用されている。
簡単なコーディングでGUIを作成できる。

インストール

streamlitをインストールして、streamlit helloを実行すると、ブラウザが立ち上がりサンプルを確認できる。

pip install streamlit
streamlit hello

streamlit.io

チュートリアルをやってみる

docs.streamlit.io

下記のコードをuber_pickups.pyで作成し、streamlit run uber_pickups.pyを実行するとブラウザに画面が表示される。

import streamlit as st
import pandas as pd
import numpy as np

st.title("Uber pickups in NYC")

Numpyでヒストグラムのデータを生成し、st.bar_chart関数を使うだけで、ヒストグラムが表示できる。

hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0, 24))[0]
st.bar_chart(hist_values)

mapにもプロットできる。

st.sliderを使って、スライダーも表示可能。 ラベルと最小、最大、デフォルト値を設定する。

hour_to_filter = st.slider('hour', 0, 23, 17)  # min: 0h, max: 23h, default: 17h

チェックボックスを使って、データの表示/非表示もできる。

ページの遷移の設定も可能。チュートリアルも存在するが、ここでは割愛する。

最後に

たた数行でグラフやマップも表示できるし、他にも入力フォームもPythonだけで出来てしまう。
PySimpleGUIも同じように数行で画面が作成できるけど、PySimpleGUIよりも今どきなWebアプリの画面ができるので、凄いなと感じた。
次回は、入力フォームからデータの登録とグラフの表示をしてみる。

lru_cacheデコレータを使ってみよう

ロバストPythonでも出てきたlru_cacheを試してみる。 キャッシュをデコレータだけで実現でき、標準で備わっているのでいい。

lru_cacheとは

関数をメモ化用の呼び出し可能オブジェクトでラップし、呼び出し最大maxsize回まで保存するデコレータ maxsizeを指定しない場合、上限なしで保存する。

docs.python.org

どんなときに使えるのか

処理に時間がかかる場合に使える。 キャッシュを利用した場合、キャッシュしたデータ元が更新され、キャッシュが更新されていない場合は古いデータが返却されてしまう。 そのため、常に最新データを返す必要がある場合は注意が必要。

使ってみる

呼び出し最大2回まで保存するように設定して実行してみる。

@lru_cache(maxsize=2)
def calculate(a):
    print("call calculate")
    r = 0
    for i in range(10000000):
        r += i

    return r

def main_call():

    for i in range(5):
        calculate(1)
        print(calculate.cache_info())

if __name__ == '__main__':
    main_call()

5回実行してprint文は1回だけなのでキャッシュが効いている。
しかし、説明によれば最大maxsizeまで保存すると書いてあるので、3回目でprint文がでるのかなと思ったが、違うみたい。

call calculate

CacheInfoを確認してみる

ラップされた関数には、cache_info()関数が追加されるようなので、cache_info()関数をコールして、CacheInfoを確認する。 currsizeは、常に1になっている。どうやら、キャッシュで保持しているデータの数みたい。

call calculate
49999995000000
CacheInfo(hits=0, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=1, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=2, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=3, misses=1, maxsize=2, currsize=1)
49999995000000
CacheInfo(hits=4, misses=1, maxsize=2, currsize=1)

追加で以下のプログラムで動作を確認してみる。

@lru_cache(maxsize=2)
def calculate(a):
    print("-- Call calculate function")
    r = 0
    for i in range(10000000):
        r += i

    return r

def main_call():

    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("cacheが効いているはず -------")
    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("1のキャッシュが削除 -------")
    print("calculate 3")
    calculate(3)
    print(calculate.cache_info())

    print("2と3のcacheが効いているはず -------")
    print("calculate 2")
    calculate(2)
    print(calculate.cache_info())

    print("calculate 3")
    calculate(3)
    print(calculate.cache_info())

    print("1のcacheがない -------")
    print("calculate 1")
    calculate(1)
    print(calculate.cache_info())

実行結果からcalculate 3でキャッシュミスが発生し、calculate 3のキャッシュが登録されて、再度calculate 1の実行で、キャッシュミス(misses)が発生していることがわかる。
デコレータの説明では、最大maxsize回まで保存するとなっているけど、最大maxsize個まで保存するの方が説明としては正しいのかなと。(文章の理解力の問題か・・・)

calculate 1
-- Call calculate function
CacheInfo(hits=0, misses=1, maxsize=2, currsize=1)
calculate 2
-- Call calculate function
CacheInfo(hits=0, misses=2, maxsize=2, currsize=2)
cacheが効いているはず -------
calculate 1
CacheInfo(hits=1, misses=2, maxsize=2, currsize=2)
calculate 2
CacheInfo(hits=2, misses=2, maxsize=2, currsize=2)
1のキャッシュが削除 -------
calculate 3
-- Call calculate function
CacheInfo(hits=2, misses=3, maxsize=2, currsize=2)
2と3のcacheが効いているはず -------
calculate 2
CacheInfo(hits=3, misses=3, maxsize=2, currsize=2)
calculate 3
CacheInfo(hits=4, misses=3, maxsize=2, currsize=2)
1のcacheがない -------
calculate 1
-- Call calculate function
CacheInfo(hits=4, misses=4, maxsize=2, currsize=2)

まとめ

functools.lru_cacheは簡単にキャッシュを実現でき、標準でPythonに組み込まれているので、OSSを改めてインストールする必要がないので、プロジェクトの関係でOSSが利用できないプロジェクトでも導入がしやすいと考える。
自分自身も使う機会があれば、積極的に使ってみたい。

参考

www.fujitsu.com

高速な検索スピードを体感するTimescaleDB

データ集計など時間がかかわるデータの検索に時系列DBは必須。 使い方や関数は早めにキャッチアップが必要だと実感。

時系列DBとは

時間情報を持ったデータを格納し、最適化されたデータベース。 TimescaleDBはその中の1つであり、今回TimescaleDBをお試ししてみる。
詳細は参考の資料を参照。

https://www.sraoss.co.jp/wp-content/uploads/files/event_seminar/material/2021/timescaledb-intro-20210624.pdf

コンテナの起動

本体をインストールしたくないので、コンテナで起動する。 パスワードを設定したい場合、POSTGRES_PASSWORD環境変数に設定する。ここはPostgreSQLと一緒。

docker pull timescale/timescaledb-ha:pg16
docker run -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=example timescale/timescaledb-ha:pg16
docker exec -it timescaledb psql -U postgres

docs.timescale.com

ハイパーテーブルの作成

TimescaleDBの紹介資料を参照して、ハイパーテーブルを作成する。 通常のテーブルを作成して、create_hypertable関数を実行。
サーバのCPU使用率、メモリ使用率の情報をもつserverspecテーブルを作成。

  • テーブルの作成
CREATE TABLE serverspec (
    time timestamp NOT NULL,
    servername text NOT NULL,
    cpu_userd double precision,
    memory_used double precision
);
  • create_hypertable関数の実行

create_hypertable関数には、ハイパーテーブルに変換するテーブル名、分割の条件にする時間の列名を指定する。 戻り値は、ハイパーテーブルのID、スキーマ名、テーブル名。

SELECT create_hypertable('serverspec', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
HINT:  Use datatype TIMESTAMPTZ instead.
    create_hypertable    
-------------------------
 (1,public,serverspec,t)

データの操作

実際にデータの挿入、検索、更新、削除をする。

  • 挿入
INSERT INTO serverspec VALUES ('2024-02-18 21:00:00', 'server1', 19.4, 10.4);
INSERT 0 1
  • 検索
SELECT * FROM serverspec;
        time         | servername | cpu_userd | memory_used 
---------------------+------------+-----------+-------------
 2024-02-18 21:00:00 | server1    |      19.4 |        10.4
(1 row)
  • 更新
UPDATE serverspec SET cpu_userd = cpu_userd + 0.1 WHERE time = '2024-02-18 21:00:00' and servername = 'server1
';
UPDATE 1
select * from serverspec;
        time         | servername | cpu_userd | memory_used 
---------------------+------------+-----------+-------------
 2024-02-18 21:00:00 | server1    |      19.5 |        10.4
(1 row)
  • 削除
DELETE FROM serverspec WHERE time = '2024-02-18 21:00:00' and servername = 'server1';

チャンクの確認準備

チャンクの確認のために、事前にデータを投入する。 1分間隔のデータを1年間分投入する。

INSERT INTO serverspec
   SELECT time,
     servername,
     round(random()::numeric * (100 - 0), 1) AS cpu_used,
     round(random()::numeric * (100 - 0), 1) AS memory_used
   FROM generate_series('2024-01-01 00:00:00'::timestamp, '2024-12-31 23:59:00', '1 minute') AS time,
     unnest(ARRAY['server1', 'server2', 'server3']) AS servername;
ANALYZE;

投入データのSQLを少し解説する。 random関数を使用して、0から100の範囲で数値を生成し、round関数で小数第一位を四捨五入する。

round(random()::numeric * (100 - 0), 1)

generate_series関数の第一パラメータに開始値、第二パラメータに終了値、第三パラメータに刻み値を設定する。 下記の場合は、1分刻みのデータを生成する。

generate_series('2024-01-01 00:00:00'::timestamp, '2024-12-31 23:59:00', '1 minute')

www.postgresql.jp

そもそも、チャンクとは

TimescaleDBでのチャンクとは、データを内部的に時間と空間で分割したテーブルのこと。 ユーザは、ハイパーテーブルを介して、チャンクのテーブルを意識することなく、データにアクセスができる。 空間は時間以外にチャンクを分割する条件。 チャンクのデフォルトは、7日ごとに分割される。チャンクを分割する間隔は、create_hypertableを実行する 際に指定可能。

チャンクの確認

登録したデータによって、チャンクがどのように生成されている状態なのかを確認する。 チャンクを確認する場合、show_chunks関数を使用する。

select show_chunks('serverspec');
               show_chunks               
-----------------------------------------
 _timescaledb_internal._hyper_1_1_chunk
 _timescaledb_internal._hyper_1_2_chunk
 _timescaledb_internal._hyper_1_3_chunk
 _timescaledb_internal._hyper_1_4_chunk
 _timescaledb_internal._hyper_1_5_chunk
 _timescaledb_internal._hyper_1_6_chunk
 _timescaledb_internal._hyper_1_7_chunk
 _timescaledb_internal._hyper_1_8_chunk
<省略>
(53 rows)

オプションをつけることで、取得する範囲を指定できる。 2024-12-01よりも古い日付のものを取得。

SELECT show_chunks('serverspec', older_than => DATE '2024-02-01');
              show_chunks               
----------------------------------------
 _timescaledb_internal._hyper_1_2_chunk
 _timescaledb_internal._hyper_1_3_chunk
 _timescaledb_internal._hyper_1_4_chunk
 _timescaledb_internal._hyper_1_5_chunk
 _timescaledb_internal._hyper_1_6_chunk
(5 rows)

docs.timescale.com

チャンクの削除

チャンクの削除は、drop_chunk関数を使用する。 データの削除は、DELETEコマンドでできるが、その場合、不要領域が発生し、再利用するためにVACUUMが必要になる。 チャンクごと削除すれば、不要領域が発生せず、VACUUMを実行する必要がない。

SELECT drop_chunks('serverspec', older_than => DATE '2024-02-01');
              drop_chunks               
----------------------------------------
 _timescaledb_internal._hyper_1_2_chunk
 _timescaledb_internal._hyper_1_3_chunk
 _timescaledb_internal._hyper_1_4_chunk
 _timescaledb_internal._hyper_1_5_chunk
 _timescaledb_internal._hyper_1_6_chunk
(5 rows)

docs.timescale.com

データ取得

実際に、データの取得をいろいろとやってみる。 TimescaleDBのGettingStartedを参考にする。

docs.timescale.com

firstとlast

firstは、別の列をキーに並べ替え、最初の値を返す。 lastは、fistの反対で、最後の値を返す。

SELECT servername, first(cpu_userd, time), last(cpu_userd, time) FROM serverspec WHERE time > now() - INTERVAL '4 days' GROUP BY servername;
 servername | first | last 
------------+-------+------
 server2    |  14.4 | 19.4
 server3    |  26.4 |  2.2
 server1    |   1.4 | 47.2
(3 rows)

time_bucket

time_bucket関数は時間を任意の間隔で丸める関数。 time_bucket関数を使用した場合、任意の時間で丸めることが可能。

SELECT time_bucket('1 hour', time) as bucket, first(cpu_userd, time), last(cpu_userd,time) FROM serverspec GROUP BY bucket LIMIT 10;
       bucket        | first | last 
---------------------+-------+------
 2024-02-01 00:00:00 |  45.8 | 82.3
 2024-02-01 01:00:00 |    45 | 75.3
 2024-02-01 02:00:00 |   2.1 | 67.4
 2024-02-01 03:00:00 |     9 | 66.8
 2024-02-01 04:00:00 |  12.4 |  5.5
 2024-02-01 05:00:00 |  53.8 | 52.9
 2024-02-01 06:00:00 |  12.2 | 69.9
 2024-02-01 07:00:00 |  99.6 | 12.1
 2024-02-01 08:00:00 |  39.2 | 25.8
 2024-02-01 09:00:00 |   3.7 | 29.1

30分間隔にする場合、30 minutesを指定する。

SELECT time_bucket('30 minutes', time) as bucket, first(cpu_userd, time), last(cpu_userd,time) FROM serverspec GROUP BY bucket LIMIT 10;
       bucket        | first | last 
---------------------+-------+------
 2024-02-01 00:00:00 |  45.8 | 36.5
 2024-02-01 00:30:00 |  71.3 | 82.3
 2024-02-01 01:00:00 |    45 | 76.4
 2024-02-01 01:30:00 |    78 | 75.3
 2024-02-01 02:00:00 |   2.1 | 56.5
 2024-02-01 02:30:00 |  29.8 | 67.4
 2024-02-01 03:00:00 |     9 |  0.2
 2024-02-01 03:30:00 |  47.8 | 66.8
 2024-02-01 04:00:00 |  12.4 | 41.1
 2024-02-01 04:30:00 |  37.6 |  5.5

Aggregation

時間ごとのCPUのMAX値などを集計してみる。

SELECT time_bucket('1 day', time) as day, servername, max(cpu_userd) as cpu_max, first(cpu_userd, time)  as open, last(cpu_userd, time) as close, min(cpu_u
serd) as cpu_min FROM serverspec GROUP BY day, servername ORDER BY day DESC, servername;
         day         | servername | cpu_max | open | close | cpu_min 
---------------------+------------+---------+------+-------+---------
 2024-12-31 00:00:00 | server1    |    99.8 | 60.1 |  47.2 |     0.1
 2024-12-31 00:00:00 | server2    |     100 | 14.1 |  19.4 |       0
 2024-12-31 00:00:00 | server3    |    99.9 | 66.8 |   2.2 |     0.1
 2024-12-30 00:00:00 | server1    |     100 | 33.5 |  78.3 |       0
 2024-12-30 00:00:00 | server2    |    99.9 | 44.8 |  34.5 |     0.1
 2024-12-30 00:00:00 | server3    |     100 | 32.5 |  50.7 |     0.1
 2024-12-29 00:00:00 | server1    |    99.8 | 42.7 |  31.4 |     0.1

docs.timescale.com

最後に

TimescaleDBのコンテナの起動からデータの集計を試してみて、PostgreSQL単体よりも検索スピードが高速であることを体感できた。(計測まではしていない) さまざまな機器からの情報を集計し、グラフ化するといった集計処理には最適なデータベースと理解。 Zabbixとも連携ができるように、監視装置からのアラームの情報を集計等でも活躍すると思うので、これからも時系列DBについて勉強しておく。

参考

透過的という言葉とは

qiita.com

パーティショニング

www.postgresql.jp

TimescaleDBのセットアップ

時系列DBのことを知りたくて、TimescaleDBのコンテナを使ってサクッとセットアップ。

時系列DBとは

時間情報を持ったデータを格納し、最適化したデータベース。   TimescaleDBはその中の1つ。詳細は参考の資料を参照。

https://www.sraoss.co.jp/wp-content/uploads/files/event_seminar/material/2021/timescaledb-intro-20210624.pdf

セットアップ

インストール

本体をインストールしたくないので、コンテナで起動する。
パスワードを設定したい場合、POSTGRES_PASSWORD環境変数に設定する。ここはPostgreSQLと一緒。

docker pull timescale/timescaledb-ha:pg16
docker run -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=example timescale/timescaledb-ha:pg16
docker exec -it timescaledb psql -U postgres

docs.timescale.com

ハイパーテーブルの作成

TimescaleDBの紹介資料を参照して、ハイパーテーブルを作成する。 通常のテーブルを作成して、create_hypertable関数を実行。
サーバのCPU使用率、メモリ使用率の情報をもつsserverspecテーブルを作成。

  • テーブルの作成
CREATE TABLE serverspec (
    time timestamp NOT NULL,
    servername text NOT NULL,
    cpu_userd double precision,
    memory_used double precision
);
  • create_hypertable関数の実行

create_hypertable関数には、ハイパーテーブルに変換するテーブル名、分割の条件にする時間の列名を指定する。
戻り値は、ハイパーテーブルのID、スキーマ名、テーブル名。

SELECT create_hypertable('serverspec', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
HINT:  Use datatype TIMESTAMPTZ instead.
    create_hypertable    
-------------------------
 (1,public,serverspec,t)

データの操作

実際にデータの挿入、検索、更新、削除をする。

  • 挿入
INSERT INTO serverspec VALUES ('2024-02-18 21:00:00', 'server1', 19.4, 10.4);
INSERT 0 1
  • 検索
SELECT * FROM serverspec;
        time         | servername | cpu_userd | memory_used 
---------------------+------------+-----------+-------------
 2024-02-18 21:00:00 | server1    |      19.4 |        10.4
(1 row)
  • 更新
UPDATE serverspec SET cpu_userd = cpu_userd + 0.1 WHERE time = '2024-02-18 21:00:00' and servername = 'server1
';
UPDATE 1
select * from serverspec;
        time         | servername | cpu_userd | memory_used 
---------------------+------------+-----------+-------------
 2024-02-18 21:00:00 | server1    |      19.5 |        10.4
(1 row)
  • 削除
DELETE FROM serverspec WHERE time = '2024-02-18 21:00:00' and servername = 'server1';

最後に

TimescaleDBのインストールとデータの投入、更新、削除までやってみたが、PostgreSQLなので簡単だった。
ここから、チャンクとか時系列DBとして大事な概念が出てくるので、引き続き確認していく。

SQLAlchemyのORMを活用したデータの一括操作

大量データを1件ずつ登録、更新、削除をした場合、データベースとのやりとりが必要になる。 そのため、大量データをまとめて投入したい。 その方法として、bulk insertなどがある。 SQLAlchemyのORMを使ってbulk操作を実現するのか確認する。

wa3.i-3-i.info

Bulk Insert

insert関数にORMクラスを指定して実行する。 ここでは、insert後に確定するidがあるので、returning関数を指定して、insert後のデータを取得する。 returning関数で結果を取得するには、scalars関数を使う必要がある。 returning不要であれば、execute関数でもOK。 ただし、returningから返却されるデータの順序は保証されていない。 順序を保証するために、sort_by_parameter_orderパラメータを指定する必要がある。

students = session.scalars(
    insert(Student).returning(Student),
    [
        {"name": "sato", "address": "Tokyo", "email": "sato@ddd.com"},
        {"name": "tanaka", "address": "Kanagawa", "email": "tanaka@ddd.com"},
        {"name": "wata", "address": "Chiba", "email": "wata@ddd.com"}
    ]
)
session.commit()

Bulk Update

主キーをWHERE条件に設定して、UPDATEを実行する。 下記の場合は、idが主キー。

session.execute(
    update(Student),
    [
        {"id": 1, "name": "sato2"},
        {"id": 2, "name": "tanaka2"},
        {"id": 3, "name": "wata2"}
    ]
)

SQLAlchemyで生成するSQL文。

UPDATE student SET name=%(name)s WHERE student.id = %(student_id)s

主キーを使用せずに、別の条件でUPDATEをしたい場合はwhere関数を使用する。 ここでは、Student.nameと辞書のキーであるu_nameをバインドするために、bindparam関数を使ってバインドする。 また、実行時にはsession.executeではなくsession.connection().executeを使用する必要がある。

session.connection().execute(
    update(Student).where(Student.name == bindparam("u_name")),
    [
        {"u_name": "sato", "name": "sato3"},
        {"u_name": "wata", "name": "wata3"}
    ]
)

docs.sqlalchemy.org

Bulk Delete

Bulk ORM DELETEはサポートされていないため、session.connection().executeでcoreモジュールを使って、実行する。

session.connection().execute(
    delete(Student).where(Student.name == bindparam("d_name")), 
    [
        {"d_name": "wata3"},
        {"d_name": "tanaka"}
    ]
)
session.commit()

session.executeでもできる。

session.execute(
    delete(Student).where(Student.name.in_(["wata3", "tanaka"]))
)
session.commit()

最後に

ORMを使ったBulk操作のやり方を理解できた。 より高度な使い方をする場合は、もう少しマニュアルを見る必要があると思う。 開発でも活かしていきたい。

参考

docs.sqlalchemy.org

2024年2月の振り返り

もう2月も終わりということで、2月の振り返り。

やったこと

朝活または夜活を20分やる

なんとか2月も継続してやることができた。順調に草も生えてきている。 ただ、2日ほど草が生えてないけど、気にしない。

投稿をいつもより頑張ってみた

1月が8記事に対して、2月が10記事の投稿。
気持ちは1月よりもかなり多く投稿した気持ちでいたが、意外と2つしか違いがなかった。
2月は祝日がない週は週3で投稿をすることを意識したら、PVが少し伸びてきている。 ストックはあるにしても3月も週3回は投稿できるようにチャレンジする。

1年のやることリストを少しだけ

結局、1年のやることリストが完成してない。 完成する気配がない。どうしましょう。

わかったこと

ちょっと無理してるかも

週3回投稿しているけど、ちょっと無理している気もする。 モチベーションが落ちないように少しペースは調整しようと思う。
仕事も無理してやろうとするので、少し早めに終わるとか調整する。

次にやること

ちょっとそろそろOSSに貢献したい

そろそろPython系のOSSに貢献したいので、どれかいい感じなものを探してみる。 以前、PowerMockにプルリクしてマージしてもらったけど、もう数年前の話。 ちょっと、探してみよう。

内部ツールを共有

個人的に作成したツールを社内に共有してみる。
あまり使う頻度は高くないけど、あれば便利なはずなのでグループに共有してみようと思う。

プライベートで

2月もちょくちょく奥さんと口喧嘩になったので、3月は仲良くしたい。

最後に

2月はそれなりに頑張れたと思う。
2月の後半からプロジェクトが忙しくなり、人も減りで自分への負荷が高くなりそう。
ちょっと、2月のペースはきついけど、無理せずに楽しめたらと思う。