Yura YuLife

ITエンジニアの覚え書き。

Python の attrs で validation をかける

以前 Qrunch に投稿してた記事を持ってきました。


Python の attrs が便利すぎて、データ保持用のクラスを作る時なんかは専らコレばっかり使っています。

そんな attrs で入力されるデータに validation をかける方法です。

attrs で入力データをチェック

例えば Pose というクラスに対して、位置を表す position は長さ 3 の配列、向きを表す orientation は長さ 4 の配列(クォータニオン) であることをチェックする場合は以下のように書くことができます。

from attr import attrs, attrib


@attrs
class Pose:
    position = attrib()
    orientation = attrib()

    @position.validator
    def check_position(self, attribute, value):
        if len(value) != 3:
            raise ValueError("position must be a list with size=3")

    @orientation.validator
    def check_orientation(self, attribute, value):
        if len(value) != 4:
            raise ValueError("orientation must be a list with size=4")

正しい値を代入した場合

>>> Pose(position=[0, 1, 2], orientation=[0, 0, 0, 1])
Pose(position=[0, 1, 2], orientation=[0, 0, 0, 1])

不正な値を代入した場合

>>> Pose(position=[0, 1], orientation=[0, 0, 0, 0, 1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<attrs generated init d25cdc317f9cca07dcc37d3210f62862e6055553>", line 5, in __init__
  File "<stdin>", line 8, in check_position
ValueError: position must be a list with size=3

普通の class だと __init__ が肥大化したりしがちですが、これだとスッキリ書けますね!

関連サイト

周波数からC3やF4などのノート名に変換する

以前 Qrunch に投稿してた記事を持ってきました。


Python 3 で周波数からノート名に変換

A4 は 440Hz、 C3 は 130.8Hz のようにノート毎に周波数が決まっていますが、周波数からノートに変換するコードを Python 3 で書いてみました。

import math

SCALE_LIST = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
C0_FREQUENCY = 16.35

def frequency_to_note(freq):
    n = round(math.log2(freq / C0_FREQUENCY) * 12)
    return SCALE_LIST[n % 12] + str(n // 12)

実行するとこんな感じ。ノートの音程ぴったりじゃない場合は四捨五入で丸めています。

>>> frequency_to_note(55)
'A1'
>>> frequency_to_note(440)
'A4'
>>> frequency_to_note(130)
'C3'
>>> frequency_to_note(185)
'F#3'

参考URL

Pythonでファイルに書き出さずにzip圧縮

この記事では、Python でファイルを生成せずに zip 圧縮したデータを生成する方法を紹介します。

zip 圧縮したデータを Python 内からアップロードしたい場合なんかに、ファイルに書き出して後から削除する手間を避けたかったり、FaaS などそもそも ReadOnly のファイルシステムで使ったりすることを想定しています。

動作環境

動かし方

Python の zipfile ライブラリと、 io.BytesIO を用いて実現します。

# zip 圧縮したデータをファイルに書き出す代わりに BytesIO のストリームを作成
zip_stream = io.BytesIO()

# ファイルに書き出す代わりに zip_stream に zip 圧縮したデータを出力
with zipfile.ZipFile(zip_stream, 'w', compression=zipfile.ZIP_DEFLATED) as new_zip:
    # foo.txt というファイル名で abcdef という文字列をzipファイルに追加
    new_zip.writestr("foo.txt", "abcdef")
    # ローカルの foo.png を zip ファイルに追加
    new_zip.write("foo.png")

# 生成された zip 圧縮データを出力
# 実際には print の代わりに HTTP の POST とか、S3 へのアップロードとかに使うイメージ
print(zip_stream.getvalue())

参考URL

matplotlib でプロット上の点をドラッグする例

Python の matplotlib 上で、マーカーをドラッグ可能にするプログラムの参考例です。

f:id:yurayur:20170912000951g:plain

動作環境

実装例

github.com

使い方

  • プロット上で左クリックで点を追加
  • プロット上の点をドラッグで移動
  • プロット上の点を右クリックで削除

解説

そんなに難しいことはしておらず、matplotlib の canvas に対してマウスイベントを拾って、適切な処理を行うだけです。

  • マウスのクリックイベント時のコールバック _on_click で新しい点の追加、もしくは既存の点の選択・削除処理
  • マウス移動イベント時のコールバック _on_motion で選択中の点がある場合は移動処理
  • マウスリリースイベント時のコールバック _on_release で選択中の点がある場合は確定処理

ちょっとずるいのは、点のX, Y座標を整数に絞ることで、点の管理を簡略化しているところかも。。。

もっと良い実装があれば PR くださーい!

参考URL

python の multiprocecssing.Pool.map で複数の引数を持つ関数を扱う

python の multiprocessing を用いてマルチスレッド処理を行う際に、複数の引数を持つ関数を扱うときの Tips です。

動作環境

方法

やり方は簡単で、目的となる関数をラップするだけです。

# -*- coding: utf-8 -*-

from multiprocessing import Pool


def calc(a, b, c):
    result = a + b * c
    print("%d + %d * %d = %d" % (a, b, c, result))
    return result


def wrap_calc(num):
    return calc(*num)


def main():
    pool = Pool(processes=4)
    args = [(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]
    print("result = %s" % pool.map(wrap_calc, args))


if __name__ == '__main__':
    main()

結果

$ python pool.py 
1 + 2 * 3 = 7
2 + 3 * 4 = 14
4 + 5 * 6 = 34
3 + 4 * 5 = 23
result = [7, 14, 23, 34]

上記のように、wrap_calc メソッドにリスト型で引数を渡し calc メソッドに展開してあげれば解決します。

参考URL