2021年7月30日金曜日

Python でカスタムエラーを作成する方法

Python でカスタムエラーを作成する方法

概要

Python で独自のエラークラスを作成する方法を紹介します

環境

  • macOS 11.5
  • Python 3.8.3

サンプルコード

class InvalidStringError(Exception):
    def __init__(self, name):
        self.message = "A name of {} has an invalid character.".format(name)

def check_name(name):
    if 'z' in name:
        raise InvalidStringError(name)
    return True

try:
    check_name("hawk")
    check_name("hawkz")
except InvalidStringError as e:
    print(e.message)

少し解説

Exception を継承して作成します 共通のメッセージや共通のパラメータを管理するのであれば一段 Base を挟んで継承すると良いと思います

コンストラクタは定義しなくても OK ですが message などは最低限必要になるので設定できるようにしておくと良いと思います

使用する場合は普通に raise すれば OK です エラーも execpt で検出することができます

参考サイト

2021年7月29日木曜日

Python の with 句を試してみる

Python の with 句を試してみる

概要

Python の with 句を使うことができるサンプルクラスを作成してみました 使えるようになると便利な機能かなと思います

環境

  • macOS 11.5
  • Python 3.8.3

サンプルコード

class WithSampleUser:
    def __init__(self, name, age=None):
        self.name = name
        self.age = age
        self.tel = None
        self.profile = None

    def gen_profile(self):
        if self.age is None:
            raise
        self.profile = {"name": self.name, "age": self.age, "tel": self.tel}

    def __enter__(self):
        # コンストラクタ以外の初期化処理
        self.tel = '090-1234-5678'
        return self

    def __exit__(self, ex_type, ex_value, trace):
        # エラーが発生した場合
        if ex_value:
            self.name = None
            self.age = None
            self.tel = None
            print("error")
        else:
            print(self.profile)

with WithSampleUser("hawksnowlog", 10) as user:
    user.gen_profile()

with WithSampleUser("hawksnowlog") as user:
    user.gen_profile()

解説

まず必須なのは __entry__ と __exit__ の実装になります with 句に入った場合と出た場合に自動的に呼ばれるメソッドで実装が必須になります

__entry__ 側は初期化的なことを行う感じになります 例えばストリームのオープンやファイル操作などのシリアライズを行います

__exit__ は逆に終了時に呼ばれるのでクローズ処理を行います 開いたものを自動的に閉じるために用意されているメソッドみたいな感じなので終了処理を書きます また __exit__ はエラーが発生した際にも呼ばれます その際にはエラー情報が格納された引数「ex_type」「ex_value」「trace」にエラー情報が格納されています

with 句ないで try catch することで発生したエラーを握りつぶせますがあまり with 句内で try catch することはないのかなと思います

想定ユースケース

以下のようなケースで with 句用にクラスをカスタマイズすると良いかなと思います

  • ファイル操作を行うクラス
  • データベース操作を行うクラス
  • HTTP など外部通信を行うクラス
  • ソケットなどのコネクションを確立するクラス

要するに open -> close の流れが必要になるオブジェクトを扱うクラスの場合には with 句で扱えるように拡張するのが良いかなと思います

2021年7月28日水曜日

ipython 超入門

ipython 超入門

概要

Python 標準の repl を強化した版になります 今回はインストールして試してみました

環境

  • macOS 11.5
  • Python 3.8.3
  • ipython 7.25.0

インストール

  • pipenv install ipython

起動

  • ipython
Python 3.8.3 (default, Jun  7 2021, 23:23:45) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.25.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]:

補完

TAB でできます モジュール名や変数名の他、OS 上にあるファイル名も補完してくれます

オブジェクトの情報をデバッグする

定義したオブジェクトの最後に「?」を記述することでオブジェクトのデバッグ情報を確認できます

In [4]: a = "hoge"

In [5]: a?
Type:        str
String form: hoge
Length:      4
Docstring:  
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or
errors is specified, then the object must expose a data buffer
that will be decoded using the given encoding and error handler.
Otherwise, returns the result of object.__str__() (if defined)
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.

シェルの実行

コマンドの先頭に「!」を付与します 「!」なしでも動作しますが変数としても使えるのでコマンドということを明示するために付与したほうが良いかなと思います

In [6]: a = !pwd

In [7]: a
Out[7]: ['/Users/username/Documents/work/try_ipython']

In [8]: pwd
Out[8]: '/Users/username/Documents/work/try_ipython'

Magic functions

「%」から始まる関数が良いされておりこれを使うと速度の計測 (%timeit) や python スクリプト自体の実行 (%run) ができます

timeit はデフォルトで 100000 ループするので実行時には注意しましょう

最後に

Jupyter のカーネルにもなっているので JypyterNotebook に慣れている人は iPython も簡単に使えるのかなと思います

参考サイト

2021年7月27日火曜日

emacs で日本語切り替えする方法

emacs で日本語切り替えする方法

概要

OS 側の IME ではなく emacs 内部の IME を使って日本語入力を切り替える方法

環境

  • macOS 11.5
  • emacs 27.1

切替方法

  • M-x toggle-input-method

入力方式を指定する場合は

  • M-x set-input-method

その後入力方式を指定します キーバインドは以下の通りです

  • C-x RET C-\

2021年7月26日月曜日

RedisGraph を使ってみた

RedisGraph を使ってみた

概要

RedisGraph は Redis 上で GQL を使うことができる redis の拡張モジュールです 今回はインストール方法から簡単 GQL の発行まで行ってみました

環境

  • Redis with RedisGraph 6.2.3
  • RedisInsight 1.10.1

RedisGraph モジュールが有効な redis を起動する

docker イメージがあるのでそれを使うと簡単に立ち上げることができます すでに redis が立ち上がっている場合は一旦停止してから redisgraph を起動してください

  • docker run -p 6379:6379 -d redislabs/redisgraph

データの登録

ではデータを登録してみましょう Graph データを登録するには redis-cli で GRAPH.QUERY を使います

127.0.0.1:6379> GRAPH.QUERY MotoGP "CREATE (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})"
1) 1) "Labels added: 2"
   2) "Nodes created: 6"
   3) "Properties set: 6"
   4) "Relationships created: 3"
   5) "Cached execution: 0"
   6) "Query internal execution time: 4.416402 milliseconds"

MotoGP というキーに graphdata を登録します 少し複雑に見えますが一つずつ見ていくと単純な構文になっています

データを登録する場合は CREATE を使います SQL で言うところの INSERT になります デーは括弧で囲いリレーションはハイフンを使います

上記のデータは

  • Valentino Rossi はチーム Yamaha に所属
  • Dani Pedrosa はチーム Honda に所属
  • Andrea Dovizioso はチーム Ducati に所属

という 3 つのデータを登録しています グラフデータはある属性がある属性に紐付いているというデータを登録することで可視化や検索を行うことができます

データベースの一覧を取得する

今回は MotoGP というデータベースに登録しています RedisGraph ではデータベースは単純な key として登録されています なのでデータベースの一覧を取得する場合は keys で OK です

127.0.0.1:6379> keys *
1) "MotoGP"

データの検索

例えばライダーの名前だけを取得してみます SQL で言うところの SELECT 文は GQL では MATCH という構文になります

127.0.0.1:6379> GRAPH.QUERY MotoGP "MATCH (r:Rider) RETURN r.name"
1) 1) "r.name"
2) 1) 1) "Valentino Rossi"
   2) 1) "Dani Pedrosa"
   3) 1) "Andrea Dovizioso"
3) 1) "Cached execution: 0"
   2) "Query internal execution time: 0.189014 milliseconds"

(r:Rider) でノードを検索できます 先頭の小文字の r はエイリアスでその後に続くクエリで r で検索したノードを参照できます 今回はすべてのノードを取得してその中の properties の name だけを表示しています

おまけ: RedisInsight で動作確認

RedisInsight を使えばグラフデータを可視化することができます

すでに RedisInsight が構築されているのであれば左メニューにある「RedisGraph」をクリックします そしてデータベースを選択して GQL を入力すれば OK です

ライダーのノード一覧が表示されたらノードをダブルクリックしてみましょう すると紐付いているチームの情報がグラフ上に展開され確認することができます もし紐付きが複数ある場合はいろいろなノードがリアルタイムで展開されていくのでノードの関連性が簡単に確認することができます

最後に

ある属性とある属性が紐付き関係性がグラフデータのような構造のデータがある場合に RedisGraph に入れて可視化すると簡単に関係性を確認することができるようになります

テーブルデータは SQL、グラフデータは GQL というふうに使い分けできるようになると強力なツールになると思います

参考サイト

2021年7月23日金曜日

Redis の管理画面の RedisInsight を試してみた

Redis の管理画面の RedisInsight を試してみた

環境

Redis の管理画面に RedisInsight というのがあります これを使うと画面からキーの登録や削除、取得などができるようになります redis-cli を直接叩いたりすることもできます

環境

  • macOS 11.4
  • Redis 4.0.9
  • RedisInsight 1.10.1

インストール

今回は docker 上で動作させます 以下のコマンドで起動できます

  • docker run -d -v redisinsight:/db -p 8001:8001 redislabs/redisinsight:latest

これでブラウザで 8001 にアクセスすると管理画面が確認できます 一番始めに EULA に同意する必要があるのでトグルボタンを変更して CONFIRM をクリックしましょう

設定

次に接続する Redis を選択します 今回は VM 上で動作している既存の redis があるのでそれを使います その場合は「I already have a database」を選択します

「Connect to Redis Database」を選択します

Redis を追加する画面になるので IP アドレスとポートと好きなデータベース名を入力しましょう

追加が完了すると以下のように一覧に表示されます

動作確認

試しに key/value を登録してみましょう 「ADD KEY」からキーを登録できます 今回は文字列を登録しています

登録が完了すると以下のように一覧に表示されます

最後に

編集機能まで使えると危険なことがあるので slave を用意してそっちを登録するとビューワーとして使うことができると思います (おそらく readonly ユーザは作成できないはず)

参考サイト

2021年7月22日木曜日

Flask 内部で別の API をコールしたい場合はモジュールやメソッドとして切り出す必要がある

Flask 内部で別の API をコールしたい場合はモジュールやメソッドとして切り出す必要がある

概要

Flask で作成した API を他のルーティングからコールしたいケースはあると思います 本来は HTTP Client などを使ってちゃんとコールしたいところですがさすがにできなさそうなのでそういった場合は共通する処理をモジュールやメソッドとして切り出すしかなさそうです

環境

  • macOS 11.4
  • Python 3.8.3
  • flask 1.1.2

サンプルコード

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/abc/<x>/<y>', methods=['GET'])
def abc_route(x, y):
    return jsonify(abc(x, y))

@app.route('/xyz', methods=['POST'])
def xyz():
    x = request.form["x"]
    y = request.form["y"]
    return jsonify(abc(x, y))

def abc(x, y):
    return "{}.{}".format(x, y)

動作確認

  • FLASK_DEBUG=1 pipevn run flask run

GET と POST で同じ結果が返ってきます

  • curl -XPOST localhost:5000/xyz -F 'x=1' -F 'y=2'
  • curl -XGET localhost:5000/abc/1/2

ポイント

共有するモジュールが method やクエリに依存しないようにする

例えば flask では GET と POST ではパラメータの受け取り方が変わります 共有するモジュール内ではパラメータの受け取りなどはせずにパラメータをそれぞれのルーティングで受け取ったあとで引数としてパラメータを受け取るようにしましょう

共有するモジュール内でレスポンスを返さないようにする

あくまでもレスポンスを返すのはルーティング側で行うようにしましょう 同じ処理をさせたいがレスポンスは違うようにしたい場合に対応できなくなります

return などは単純な文字列や辞書型などを返却するようにして flask のレスポンスとして整形するのはルーティング側で行います

別アプリとして切り出すのもありか

例えばサンプルで言うところの abc(x, y) を別の Flask アプリとして切り出します そして requests などを使って RPC 的に abc をコールします この方法であれば例えば外部向けに公開したりもできるのでより汎用的に使えるようになります もしくは PyPi 形式で外部公開するのもありだと思います

最後に

変なことはせずに単純に考えるようにしましょう

参考サイト