- 投稿日:2021-08-09T23:51:05+09:00
【SikuliX】スマホゲームの自動化(ウィンドウサイズ・位置固定)
はじめに 前回の記事では、スマホゲームを自動化するためのサンプルコードを記載しました。 今回はSikuliXを使用していて発生する問題の解決策を記載していきます。 発生する問題 SikuliXでは画面上の画像を認識してスクリプトにより操作を自動化します。 しかし、指定した画像サイズが画面上に表示されている画像サイズと違う場合、うまく画像認識されずスクリプトが正しく動作しない場合があります。 例えば、BlueStacksなどのエミュレータではウィンドウサイズを自由に変えることが可能です。 ウィンドウサイズ変更してしまい元のサイズに戻せなくなり、検索画像の取り直ししなければならない状況が発生することがあります。 この事象を解決するため、対象のウィンドウサイズを固定にする方法を検討しました。 サンプルコード setwindow.py #!/usr/bin/env python # -*- coding: utf-8 -*- import ctypes # ウィンドウハンドラ取得 hWnd = ctypes.windll.user32.FindWindowW(0, "BlueStacks") # 移動&リサイズ ctypes.windll.user32.MoveWindow(hWnd, 1200, 0, 700, 1100, 1) 解説 サンプルコードは今回もBlueStacksを使用した例です。 FindWindowWでウインドウ名「BlueStacks」のハンドラを取得し、そのハンドラに対してMoveWindowで(1200,0)の位置で700x1100のサイズに移動とリサイズをしています。 しかし、SikuliX2.0.4に組み込まれているPythonではWin32関連のモジュールは組み込まれていないようで、そのまま自動化のスクリプトに組み込んで実行するだけではエラーが発生します。 SikuliX上ではWin32関連モジュールは動作しない仕様のようなので、SikuliX上では動作させずウインドウサイズの変更はWindowsのコマンドラインから実行します。 SikuliXは、そのスクリプトを実行させることで実現します。 上記スクリプトを「C:\sikuli\setwindow.py」に保存したとします。 SikuliX上の別スクリプトから以下の記述で呼び出すことでsetwindow.pyが実行されます。 rpa.py #ウィンドウサイズ固定 import subprocess subprocess.call('python C:\sikuli\setwindow.py') #以降は自動化スクリプトを記載 上記記述を自動化スクリプトの先頭に追加することで、ウィンドウサイズや位置を変更した場合でも、元の位置に戻すことが可能となります。 まとめ SikuliX外のPythonスクリプトをSikuliXから実行させることでWin32関連のウインドウサイズ変更を動作させるようにしました。 SikuliXを実行するとき、毎回固定したウインドウサイズにすることで画面サイズが変わることによる認識されない問題を回避できます。 自分の動作環境に応じて表示させる位置や画面サイズの数字を変更し使用してみてください。
- 投稿日:2021-08-09T23:32:11+09:00
[Atcoder] ABC213(C問題)の整理
概要 AtCoder Beginner Contest 213 C問題に関して、備忘録を兼ねてコードを整理します。 問題は下記のリンクです。 環境 : Python 3.8 解法 何の数字も書いていない行・列は取り除かれるため、結果的に$\{A_i\}$, $\{B_i\}$それぞれの要素を、数字が小さい方から順に(最も小さい要素を1として)順位付けすればよいと考えることができます。ただし同じ値の要素は、操作の結果も当然同じ行・列に移動するため同じ順位をつけなければならず、かつ同じ順位が続いた(同じ値の要素が複数あった)場合でも順位は飛ばさない点に注意が必要です。 解答例 # 標準入力 H, W, N = map(int,input().split()) A, B = [], [] for i in range(N): a, b = map(int, input().split()) A.append(a) B.append(b) # 重複を除いた番号と、それが小さい方から何番目かというindexを対応させる辞書を作成 # A:カードがある行数、B:カードがある列数 A_set = set(A) B_set = set(B) A_dict, B_dict = {}, {} for i, A_name in enumerate(sorted(A_set),1): #enumerateの2番目の引数は、数え始める数値 A_dict[A_name] = i for j, B_name in enumerate(sorted(B_set),1): #B_dict = {B_name:j} B_dict[B_name] = j # 元の配列A,Bにおいて各要素が小さいほう方から数えて何番目かを辞書を参照してprint for i in range(N): print(A_dict[A[i]], B_dict[B[i]]) 公式の解答例を参考に作成しました。特に後半では、$\{A_i\}$, $\{B_i\}$の要素が、それぞれ小さい方から数えて何番目なのか、という順位とセットになった辞書型を作成しています。具体的には($\{A_i\}$に絞って見てみると)、 $\{A_i\}$から重複を除いた配列を作成。 A_set = set(A) sorted関数でA_setを小さい順に並べ替えた上で、enumerate関数で対応した順位とセットにした辞書を作成し、それを1つずつA_dictに保存して辞書を作成。 for i, A_name in enumerate(sorted(A_set),1): #enumerateの2番目の引数は、数え始める数値 A_dict[A_name] = i 元の配列$\{A_i\}$に関して、作成した辞書を参照しながら、対応する順位を表示。 for i in range(N): print(A_dict[A[i]], B_dict[B[i]]) となっています。 コメント 問題文を素直に受け取ると「何の数字も書いていない行・列を探索する」ことから始めそうですが、そうすると上手く解けない(または処理時間がオーバーする)と思われます。 numpyのargsort()関数を用いると、順位付けの際に同じ値の要素を別の順位でつけてしまうため上手くいかないようです。 以上
- 投稿日:2021-08-09T23:26:14+09:00
【Django】Django Rest API(認証あり)にリクエストをする
概要 DjangoのRest APIにPOSTでリクエストを行う際にちょっとハマったので、書いておこうと思います。 問題 curlで試しにGETリクエストを行ったところ、問題なく動作するが、POSTでリクエストを行ったところcsrf tokenが無効というエラーが出た レスポンスには明確な理由が書いてなかったので、uwsgiのログを見た WARNING Forbidden (Referer checking failed - no Referer.) WARNING:django.security.csrf:Forbidden (Referer checking failed - no Referer.) Refererがないと弾かれてしまうっぽい 解決方法 リクエストヘッダーに Refererを設定することで解消できた。 以下のようにすることでリクエストを行うことが出来た $ curl -X POST -b "csrftoken=hoge; sessionid=hoge;" -H "x-csrftoken: hoge" -H "Referer: https://hoge.com" -d '{"data": "hoge"}' "https://endpoint.com/api/hoge" GETリクエストの場合は、Refererがなくても動作したので、なくて大丈夫っぽいです
- 投稿日:2021-08-09T23:07:38+09:00
MITの公開クラス(Introduction to Deep Learning)のlab1-1をやってみた
はじめに こんにちは。 私は最近MITがディープラーニング(以降 DL)の授業をYouTube上で公開しているのを知り、それを使って目下勉強中です。 下記リンクには授業内で使っているスライドと各授業のYouTube動画パスがあります。 6S.191 Introduction to Deep Learning もちろん英語ですが、読める方はぜひ挑戦してみてください。結構面白いです! Lab1-1 ディープラーニングを実際に実装しているソースコードが公開されているので、それの解説をしていきます(要は備忘録です) ソースコード ⇦ 今回はpart1です! 今回の内容はTensorFlowの基本的な機能の説明です 1. tesnorflowの機能の説明 2. パーセプトロンの実装 3. 勾配降下法の実装 以上のラインナップです。 ちなみにTensorFlowはなぜTensorFlowと呼ばれるかご存知でしょうか? Tensor(N次元のベクトルや行列)のFlow(nodeや計算)をするのでTensorFlowと言う名前らしいです!明日から友達に「TesnsorFlowの名前の由来知ってる?」でマウントを取れますね! コードの解説 TensorFlowで次元数の確認と特定の次元数を持つ変数を作成 # 0次元 sport = tf.constant('Tennis', tf.string) number = tf.constant(1.41421356237, tf.float64) print("`sport` is a {}-d Tensor".format(tf.rank(sport).numpy())) print("`number` is a {}-d Tensor".format(tf.rank(number).numpy())) output `sport` is a 0-d Tensor `number` is a 0-d Tensor 0次元のtensorを定義しました。 これはスカラーを表しています。スカラーはただの値なので次元を持っていません。 tf.rank(変数).numpy()メソッドを使うことによって、その変数の次元数を取得することができます。 numpy()で次元数のみの取得を実現しています。rank()だけだと不要な値まで返ってきます。 また、tf.constantでは公式ドキュメントより「Creates a constant tensor from a tensor-like object」とあるので、 tensor型の変数を作成しているというのがわかります。第二引数でどんな型のデータを持つかを決めます。 # 1次元 = ベクトル sports = tf.constant(["Tennis", "Basketball"], tf.string) numbers = tf.constant([3.141592, 1.414213, 2.71821], tf.float64) print("`sports` is a {}-d Tensor with shape: {}".format(tf.rank(sports).numpy(), tf.shape(sports))) print("`numbers` is a {}-d Tensor with shape: {}".format(tf.rank(numbers).numpy(), tf.shape(numbers))) output `sports` is a 1-d Tensor with shape: [2] `numbers` is a 1-d Tensor with shape: [3] 1次元のtensorを定義しました。 これはベクトルの形です。ベクトルは1次元として扱われます。tf.shape(変数)で要素数を返します。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output `matrix` is a 2-d Tensor with shape: [3 2] rank=次元数 shape=(行数、列数)を返す。 2次元のtensorを定義しました。 2次元のデータはマトリックス(行列)になります。形としてはリストの中にリストがある形となります。 assert関数を使うことで条件を満たしていない場合AssertionErrorを表示することができ何を満たせてないかがすぐわかるようになります。 assert 条件 例として上のコードを書き換えてみます。 ### Defining higher-order Tensors ### '''TODO: Define a 2-d Tensor''' # matrix = tf.constant([['Baseball', 'Softball'], ['Soccer', 'Football'], ['Tennis', 'Badminton']], tf.string) matrix = tf.constant([1, 2 ,3 ], tf.int64) assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(matrix).numpy() == 2 # assertについて https://codezine.jp/article/detail/12179 print("`matrix` is a {}-d Tensor with shape: {}".format(tf.rank(matrix).numpy(), tf.shape(matrix))) output AssertionError Traceback (most recent call last) <ipython-input-42-2e33b2575781> in <module>() 6 7 assert isinstance(matrix, tf.Tensor), "matrix must be a tf Tensor object" ----> 8 assert tf.rank(matrix).numpy() == 2 9 # assertについて https://codezine.jp/article/detail/12179 10 AssertionError: このように次元数がクリアできてない(2次元以上ではない)のでエラーが出ています。 これは自分の書いた計算式とかがあっているや欲しいoutputが手に入っているのかエラーで教えてくれるのですごく便利なものだと思います! '''TODO: Define a 4-d Tensor.''' # Use tf.zeros to initialize a 4-d Tensor of zeros with size 10 x 256 x 256 x 3. # You can think of this as 10 images where each image is RGB 256 x 256. # (画像の数、画像のheight, 画像のwidth, 画像のチャネル数) images = tf.constant(tf.zeros([10, 256, 256, 3])) assert isinstance(images, tf.Tensor), "matrix must be a tf Tensor object" assert tf.rank(images).numpy() == 4, "matrix must be of rank 4" assert tf.shape(images).numpy().tolist() == [10, 256, 256, 3], "matrix is incorrect shape" 4次元のtensorを定義しました。 コメントアウトでも書いてますが4次元のリストは画像データを表す際に使われます。上のコードでは10枚のRGB表記の写真(256x256)となります。 実際にどのような構造になっているかみてみます。 images output <tf.Tensor: shape=(10, 256, 256, 3), dtype=float32, numpy= array([[[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.], ..., [0., 0., 0.], [0., 0., 0.], [0., 0., 0.]],... ちゃんと4次元のリストになっていますね。3個の要素が入ったリストが並んでいると思いますが、これはRGB(Red, Green, Blue)の値になります。それが256x256あると言うことになります。 このようにtf.zeros()を使うことで4次元のtensorを簡単に実現することができます。他にもtf.ones, tf.fill, tf.eyeなども同じようなことができます。 またリストやarrayと同じようにスライスを用いることで指定の要素にアクセスできます。 # use slices to access substensors row_vector = matrix[1] column_vector = matrix[:,1] scalar = matrix[0,1] print("`row_vector`: {}".format(row_vector.numpy())) print("`column_vector`: {}".format(column_vector.numpy())) print("`scalar`: {}".format(scalar.numpy())) output `row_vector`: [b'Soccer' b'Football'] `column_vector`: [b'Softball' b'Football' b'Badminton'] `scalar`: b'Softball' Tesnor型同士の計算 Tensor型同士の簡単な計算方法をみていきます。 # Create the nodes in the graph, and initialize values a = tf.constant(15) b = tf.constant(61) # Add them! c1 = tf.add(a,b) c2 = a + b # TensorFlow overrides the "+" operation so that it is able to act on Tensors print(c1) print(c2) output tf.Tensor(76, shape=(), dtype=int32) tf.Tensor(76, shape=(), dtype=int32) ### Defining Tensor computations ### # Construct a simple computation function def func(a,b): '''TODO: Define the operation for c, d, e, f (use tf.add, tf.subtract, tf.multiply, tf.divide).''' c = tf.add(a,b) d = tf.subtract(b,1) e = tf.multiply(c,d) f = tf.divide(e, a) return f # Consider example values for a,b a, b = 1.5, 2.5 # Execute the computation f_out = func(a,b) print(f_out) output tf.Tensor(4.0, shape=(), dtype=float32) このようにtensorflowのメソッドを使って計算を行うこともできますし、tesnorflowは+, -, *, /の計算オペレーションもカバーしているのでこれを使っても計算できます! パーセプトロンの実装 そして今からが面白いところですね!tensorflowライブラリを使ってディープニューラルネットワークの考えの基になっているパーセプトロンを実装してみましょう! パーセプトロンはすごくシンプルなものでy=ax+bの式に非常に似ています。ベクトルxをインプットとして受け取り、各xの要素に重みw1をかけて(複数output時は行列)、全てを足してバイアスw0を足すことで結果zを出します。そして最後に非線形関数(例:sigmoid)を適用して、出力zを出します。非線形関数を使う理由としてはそのままでいくと直線の出力しか出せなくて、分類を行う時直線でしか分類が行えなくなるので使います!イメージはこんな感じですね!より細かく分類することを可能にしています! パーセプトロンの式も書いておきます。 y=g(w_{0}+\sum_i^mx_{i}w_{i}) アウトプットが一つの単純パーセプトロンの式です。i=1からはじまります(書き方がわからなかったです)。 gは非線形関数を表しています。 今回は複数のアウトプットを求めますので下の式になります。 z_{i}=w_{0,i}+\sum_j^mx_{j}w_{j,i} やることはほとんど変わりません。変わったところはoutputを複数出すために重みとバイアスが増えて行列になっているところですね。 それではpythonで実装していきましょう! ここではクラス作成して使っています。tensorflowだけを使って実装することもできますが、クラス、関数を作ることで可読性や汎用性のあるコードにすることができますので、おすすめです! では、コード全体をみましょう! # Simple perceptron ### Defining a network Layer ### # n_output_nodes: number of output nodes # input_shape: shape of the input # x: input to the layer class OurDenseLayer(tf.keras.layers.Layer): def __init__(self, n_output_nodes): super(OurDenseLayer, self).__init__() # 親クラスのコンストラクタを呼び出し self.n_output_nodes = n_output_nodes def build(self, input_shape): d = int(input_shape[-1]) # Define and initialize parameters: a weight matrix W and bias b # parameter is random at first self.W = self.add_weight('weight', shape=[d, self.n_output_nodes]) # note the dimensionality self.b = self.add_weight('bias', shape=[1, self.n_output_nodes]) # note the dimensionality def call(self, x): '''TODO: define the operation for z (hint: use tf.matmul)''' z = tf.matmul(x, self.W) + self.b # TODO '''TODO: define the operation for out (hint: use tf.sigmoid)''' y = tf.sigmoid(z) # TODO return y # Since layer parameters are initialized randomly, we will set a random seed for reproducibility tf.random.set_seed(1) layer = OurDenseLayer(3) # 3 outputs layer.build((1,2)) # 1行、2列 → inputが2つある x_input = tf.constant([[1,2.]], shape=(1,2)) y = layer.call(x_input) # test the output print(y.numpy()) # should output 3 answers mdl.lab1.test_custom_dense_layer_output(y) output [[0.2697859 0.45750412 0.66536945]] [PASS] test_custom_dense_layer_output True となります。アウトプットを見てわかる通り今回は3つのアウトプットを出すようにしたので3つでてきてます。 では順番に解説していきます。クラスの解説は今回の目的とは外れるのでしません。 まずコンストラクタ__init__でアウトプットがいくつかを指定できるようにします。 build()メソッドでインプットの形を受け取ります。インプットはベクトルなので(1, n)となります。 それを使い必要な重みとバイアスを初期化しています。 最後にcall()メソッドで計算を行なっています。tf.matmulでベクトルxと重みとバイアスの行列を計算しています。 そして最後にシグモイド関数(tf.sigmoid)を適用してアウトプットyを出しています。 ちなみにcallメソッドはobj.call()かobj()で呼び出すことができます。 パーセプトロンのアルゴリズムは非常にシンプルでわかりやすかったと思います!今はインプット層とアウトプット層しかありませんでしたが、 これをもっと多層にしていけば最終的にディープニューラルネットワーク(Fully Connected Neural Network)になります! 続いてSequential modelを使っても実装してみます。 # Sequential model using Model API ### Defining a model using subclassing ### from tensorflow.keras import Model from tensorflow.keras.layers import Dense class SubclassModel(tf.keras.Model): # Define the model's layer def __init__(self, n_output_nodes): super(SubclassModel, self).__init__() '''TODO: Our model consists of a single Dense layer. Define this layer.''' self.dense_layer = Dense(n_output_nodes, activation='sigmoid') # アウトプット層のobj作成 # call function to define the Model's forward pass def call(self, inputs): return self.dense_layer(inputs).numpy() n_output_nodes = 3 model = SubclassModel(n_output_nodes) x_input = tf.constant([[1,2.]], shape=(1,2)) print(model.call(x_input)) output [[0.5607363 0.6566898 0.1249697]] Sequentialは次回以降も頻繁に使われますが、ネットワーク層を順番に定義していくことができ非常に読みやすく使いやすいです。 今回はアウトプット層のみですが、より多層にすることも可能です。 以上でパーセプトロンの実装例は終わりです!このアルゴリズムの理解は今後のニューラルネットワークの理解に非常に役に立ちますのでぜひ理解してください! 勾配降下法の実装 勾配降下法ニューラルネットワークの学習において非常に重要になってくる考えです。学習フェーズではLoss関数(例:relu)を使用して、予測されyと正解yのlossを出します。lossは小さければ誤差がない(正解に近い)のでそれを使って勾配降下法のアルゴリズムで重みを最適化していきます。要は重みに対するLossの微分をとって最適な重みを探そうと言うものです。 new W=W-n\frac{\partial L}{\partial W} 微分を求めることで傾きを知れるのでlossが小さくなる向きがわかります(つまりマイナス方向に移動したい)。nはlearning rateと呼ばれるもので実際にどのくらいの距離を移動するかを決定しています。そして移動した分をWから引いて新しいWを取得します。これを繰り返すことで図のようにlossが小さい方へと移動し精度の高い予測ができるようになります。簡単ですが以上が勾配降下法の説明です。これの派生で色々な手法や勾配降下方の問題点もありますので興味ある方は調べてみてください。 では、まずtensorflowを使って簡単な勾配(微分)を計算してみます。ここではわかりにくいですが確率的勾配降下法(SGD)が使われています。 GradientTapeを使ってしてみます。 # First, we will look at how we can compute gradients using GradientTape and access them for computation. ### Gradient computation with GradientTape ### # y = x^2 # Example: x = 3.0 x = tf.Variable(3.0) # Initiate the gradient tape with tf.GradientTape() as tape: # Define the function y = x * x # Access the gradient -- derivative of y with respect to x dy_dx = tape.gradient(y,x) assert dy_dx.numpy() == 6.0 print(dy_dx) output tf.Tensor(6.0, shape=(), dtype=float32) with tf.GradientTape()の中で関数を定義します。 tape.gradient(y,x)でxに対するyの勾配を求めます。 簡単に実装することができました!これは自分でも微分できるものなので正しく計算できているか、ぜひ確認してください。 次にlossが本当に小さくなっていくのかをみていきます。ここではxをランダムに決めて正解x=4に勾配降下法を使って近づけていきます。 パーセプトロンを使っての計算などはせずに単純に定義したxが正解xに近づけるかをみていきます。 # how actually we use gradient to minimize loss ### Function minimization with automatic differentiation and SGD ### # initialize a random value for our initial x x = tf.Variable([tf.random.normal([1])]) print('Initializing x={}'.format(x.numpy())) learning_rate = 1e-2 # learning rate for SGD history = [] # Define the target value x_f = 4 # We will run SGD for a number of iterations. At each iteration, we compute the loss, # compute the derivative of the loss with respect to x, and perform the SGD update. for i in range(500): with tf.GradientTape() as tape: '''TODO: define the loss as described above''' # loss = tf.keras.metrics.mean_squared_error(x_f, x) loss = (x_f - x)**2 # loss minimization using gradient tape grad = tape.gradient(loss, x) # compute the derivative of the loss with respect to x new_x = x - learning_rate*grad # sgd update x.assign(new_x) # update the value of x history.append(x.numpy()[0]) # plot the evolution of x as we optimize towards x_f plt.plot(history) plt.plot([0,500], [x_f,x_f]) plt.legend(('Predicted', 'True')) plt.xlabel('Iteration') plt.ylabel('x value') output Initializing x=[[-1.1771784]] 実際に正解に近づいてるのがわかりますね。やっていることは先程説明した通りで、勾配降下法で最適なxを見つけて、xをアップデートしてより正解xに近づくようにしこれを500回繰り返してます。グラフで見てわかるように正解にどんどん近づいていってます。 以上で勾配降下法の説明終わりです! まとめ 以上でtensorflowの説明を終わります。今回は学習や予測を通しての実装はしませんでしたが、今回の書いたコードをうまく組み合わせば、自分で予測を行うようプログラムできると思います。興味あればぜひやってみてください! パーセプトロンの理解は非常に重要ですので押さえておいてください。また、勾配降下法は学習において最重要なので、理屈を知っておくことは非常に重要です。 では、次回は音楽自動生成をRNNでしていきます! 間違いや理解しにくいところがあればコメントしていただけると幸いです。
- 投稿日:2021-08-09T22:55:41+09:00
PyQt6の基本の使い方からのまとめとPyQt5との違い
最近PyQt6を使い始めたので、色々試してここで基本を始めとして使い方をまとめておきたいと思います。 ここではPyQtの基本的な使い方を紹介します。書いたのはPyQt6のコードですが、全部の機能はPyQt5/PySide2でもほとんど同じように通用します。PyQt6とPyQt5のコードの書き方の違うところについても指摘します。 私は元々以前からPyQt4/PySideとPyQt5/PySide2を使ったことがあります。(以前PyQt4/PySideでmmdpaimayaというMayaのGUIを作ったこともあります) 現在PyQt6になってもやはり書き方の違いは少ないようなので、大した問題はありません。 ここで表示する画像は全部macで試した結果となります。windowsやlinuxを使っても結果は大体同じですが、大きく違う場合もあります。 はじめに インストール pyqt6はpythonのほとんどのモジュールと同じようにpipで簡単にインストールできます。 pip install pyqt6 ウィンドウを作成する まずは簡単なウィンドウの作成から始めます。 import sys from PyQt6.QtWidgets import QApplication,QWidget class Madoka(QWidget): def __init__(self): super().__init__() self.setWindowTitle('空っぽな窓') # ウィンドウのタイトル self.setGeometry(100,100,200,150) # ウィンドウの位置と大きさ qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() 自分の欲しいウィンドウを作成するために、まずはPyQt6.QtWidgets.QWidgetクラスを継承するのです。 作成するクラスの名前はどんな名前でも構いませんが、「ウィンドウ」は「窓」という意味だから、こういう素朴な理由でここではウィンドウのクラス名を全部「Madoka」にします。他の変数の名前も適当に付けられたものが多いので気にしないでください。 ここで.setWindowTitleメソッドでウィンドウのタイトルを、.setGeometryメソッドでウィンドウの位置と大きさを指定します。 .setGeometryは位置も大きさも一気に指定することになりますが、位置だけ指定したい場合は.moveを、大きさだけの場合は.resizeを使います。 PyQt5からの変更 PyQt5とPyQt6の違いはざっくり言うと、.exec_()を.exec()に書き換えることです。 .exec_() > .exec() 以下のコードも全部そうです。PyQt5で書かれたコードをPyQt6で使いたい場合、これだけ書き換えておけば大体対応できます。 ボタン(QPushButton)などのウィジェットを入れる 次はウィジェットを入れる方法です。まずはボタンのウィジェットであるQPushButtonから例をあげます。 ウィンドウと同じように、.setGeometryで位置と大きさを指定できます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() botan = QPushButton('とあるボタン',self) botan.setGeometry(25,25,150,100) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() setStyleSheetで色々調整 ウィンドウのウィジェットも、ボタンなどの他のウィジェットも、大体.setStyleSheetメソッドでスタイルを調整できます。書き方はウェブサイトに使うcssとほぼ同じです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() css = ''' background-color: #e7869f; color: #635d81; font-family: Kaiti SC; font-weight: bold; font-size: 36px; font-style: italic; text-decoration: underline; ''' self.setStyleSheet(css) botan = QPushButton('第二ボタン',self) css = ''' background-color: #fae4cb; border: 3px solid red; text-align: right; padding: 15px; ''' botan.setStyleSheet(css) botan.setGeometry(20,20,250,110) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() ちなみに、文字を調整するには、PyQt6.QtGui.QFontと.setFontメソッドを使うという方法もありますが、.setStyleSheetの方が便利なのでここでは割愛します。 QLabelと線で文字を囲む 文字を表示したいだけの場合はQLabelを使うのです。.setFrameShapeと.setFrameShadowと.setLineWidthで囲む線を描くことができます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QFrame from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() shirase = QLabel('囲まれた領域',self) shirase.setGeometry(10,10,120,40) shirase.setAlignment(Qt.AlignmentFlag.AlignCenter) shirase.setLineWidth(3) # 線の広さ shirase.setFrameShape(QFrame.Shape.Box) # 線の形 shirase.setFrameShadow(QFrame.Shadow.Raised) # 線の影 qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() PyQt5からの変更 PyQt6ではフラグなどの置かれた場所はPyQt5とはちょっと違います。 Qt.AlignCenter > Qt.AlignmentFlag.AlignCenter QFrame.Box > QFrame.Shape.Box QFrame.Raised > QFrame.Shadow.Raised ボタンが押されたら何か引き起こす 次はボタンに機能を指定します。機能のないボタンはただのハリボテです。 クリックされたらやらせたいメソッドを.clicked.connectで指定するのです。 例えばクリックしたらウィンドウが閉じられるボタン。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() botan = QPushButton('閉じろ',self) botan.clicked.connect(self.tojiro) def tojiro(self): print('今日はここまで。さよなら') self.close() qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こんなポタンができます。押してみたらこのウィンドウがすぐ閉じられます。 ボタンが押された時や放された時に何かさせる .clicked.connectがクリックされた時に発動するのです。つまり、押してすぐ放すことです。もしただ押しかければすぐ発動させたい場合は.pressed.connectを使うのです。同じく、ボタンからマウスが解放された時に発動させたい場合は.released.connectを使うのです。 例えばボタンが押された時間を計るGUI。 import sys,time from PyQt6.QtWidgets import QApplication,QWidget,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() self.botan = QPushButton('押してみて',self) self.botan.setGeometry(10,10,150,50) self.botan.pressed.connect(self.osaretara) self.botan.released.connect(self.hanasaretara) def osaretara(self): self.kaishi = time.time() # 押されたら時間を計って記録する self.botan.setText('押されている') # ボタンに書いた文字も変更 def hanasaretara(self): print('%.3f秒押されていた'%(time.time()-self.kaishi)) # 放されたらまた時間を計って押され始めた時の時間でひく self.botan.setText('押してみて') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QPixmapで画像を入れる GUIに画像を入れることもできます。 例えばサンプルとしてこの画像を使います madoka.jpg ソース https://www.pixiv.net/artworks/46330232 この画像をウィンドウの中に入れてみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLabel from PyQt6.QtGui import QPixmap class Madoka(QWidget): def __init__(self): super().__init__() label = QLabel(self) # 画像を置くQLabel label.move(10,10) pix = QPixmap('madoka.jpg') # 画像を読み込むQPixmap pix = pix.scaledToWidth(180) # 大きさの変更 label.setPixmap(pix) self.setFixedSize(pix.width()+20,pix.height()+20) # ウィンドウの大きさも画像の大きさに合わせて変更 qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こうやって画像がちょうどウィンドウの中に収められます .scaledToWidthメソッドは指定した広さまでリサイズしますが、同じように高さだけを指定したい場合は.scaledToHeightを使います。 その他にQGraphicsViewなどのウィジェットで画像をいじることもできますが、ここでは割愛します。 レイアウト .setGeometryや.moveで直接数字で位置を指定するという方法の他に、レイアウトでウィジェットを置くという方法があります。こうやって直接数字でいちいち指定する必要がなくなるので、楽になる場合も多いです。 QHBoxLayoutとQVBoxLayout 横で並べる場合はQHBoxLayoutを、縦で並べる場合はQVBoxLayoutを使うのです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QHBoxLayout,QVBoxLayout class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 30px') hbl = QHBoxLayout() self.setLayout(hbl) vbl1 = QVBoxLayout() hbl.addLayout(vbl1) vbl1.addWidget(QPushButton('鯖')) vbl1.addWidget(QPushButton('鰯')) vbl2 = QVBoxLayout() hbl.addLayout(vbl2) vbl2.addWidget(QPushButton('鮭')) vbl2.addWidget(QPushButton('鯛')) vbl2.addWidget(QPushButton('鮪')) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こうやってボタンはこのように並びます。 QGridLayout グリッドでウィジェットを置く場合はQGridLayoutを使ったら便利です。 例えば「一二三四五六七八九」のボタンを3×3のグリッドに並べる例です。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QGridLayout class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 22px;') grid = QGridLayout() self.setLayout(grid) suuji = '一二三四五六七八九' for j in range(3): # 行 for i in range(3): # 列 grid.addWidget(QPushButton(suuji[i+j*3]),j,i) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QScrollArea スクロールで見える部分を変えられる領域を作成したい場合はQScrollAreaを使うという方法もあります。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QFrame,QScrollArea class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 30px;') seiiki = QScrollArea(self) seiiki.setGeometry(10,10,130,130) kotoba = QLabel('大義名分\n大言壮語\n大気汚染\n大会記録') seiiki.setWidget(kotoba) seiiki.setLineWidth(6) seiiki.setFrameShape(QFrame.Shape.Panel) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() ここでQLabelの中の文字が指定されたQScrollAreaの大きさより大きいので、スクロールをして全体を見るということになります。 QTabWidget QTabWidgetを使って、クリックするとページが切り替えられるタブを作成することができます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QTabWidget,QHBoxLayout,QVBoxLayout,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;') basho = QTabWidget(self) basho.setFixedSize(200,150) umi = QWidget() basho.addTab(umi,'海') hbl = QHBoxLayout() umi.setLayout(hbl) hbl.addWidget(QPushButton('儒艮')) hbl.addWidget(QPushButton('人手')) mori = QWidget() basho.addTab(mori,'森') vbl = QVBoxLayout() mori.setLayout(vbl) vbl.addWidget(QPushButton('麒麟')) vbl.addWidget(QPushButton('馬鹿')) sabaku = QPushButton('無') basho.addTab(sabaku,'砂漠') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こんな3つの多分が出ます。 選んだ多分を切り替えてみたら。 QSplitter QSplitterで可変長の領域を作ることができます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QSplitter,QPushButton from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;') sayuu = QSplitter(Qt.Orientation.Horizontal,self) # 横で分裂 sayuu.setGeometry(10,10,400,120) sayuu.addWidget(QPushButton('左')) jouge = QSplitter(Qt.Orientation.Vertical,self) # 縦で分裂 sayuu.addWidget(jouge) jouge.addWidget(QPushButton('上')) jouge.addWidget(QPushButton('中')) jouge.addWidget(QPushButton('下')) sayuu.addWidget(QPushButton('右')) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() PyQt5からの変更 PyQt6では横と縦を決めるフラグの置かれた場所はPyQt5から変更されます。 Qt.Horizontal > Qt.Orientation.Horizontal Qt.Vertical > Qt.Orientation.Vertical 入力するためのウィジェット QLineEdit 一行だけの文字を入力したい場合はQLineEditを使うことができます。 .returnPressed.connectでenterが押された時に発動したいメソッドを指定することができます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLineEdit class Madoka(QWidget): def __init__(self): super().__init__() self.hako = QLineEdit('',self) self.hako.setGeometry(10,10,150,30) self.hako.returnPressed.connect(self.enterOsaretara) # enterを押したら書かれた文字を表示します def enterOsaretara(self): print(self.hako.text()) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こんなウィンドウができて、何か文字を入れてenterキーを押して試してみたらいいです。 QLineEditが編集されたら何か引き起こす 少しでも編集されただけで即座に発動させたいメソッドは.textEditedや.textChangedを使います。 .textEditedはユーザーが直接箱で書いたり消したりする場合だけ発動しますが、.textChangedを使ったらメソッドなど間接な手段で文字が変更する時も発動するのです。 例えばQLineEditと同じ全く同じ文字をQLabelに表すこと。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLineEdit,QLabel class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;') self.hako = QLineEdit('編集してみて',self) self.hako.setGeometry(10,10,150,30) self.hako.textChanged.connect(self.henshuu) self.shirase = QLabel('編集してみて',self) self.shirase.setGeometry(10,50,150,30) def henshuu(self): self.shirase.clear() self.shirase.setText(str(self.hako.text())) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QTextEdit QLineEditが一行しか書けないが、複数行書く場合はQTextEditを使うのです。 .setTextなどのメソッドはQTextEditと同じですが、入力された文字を取得したい場合はQLineEditでは.textメソッドを使うのに対し、QTextEditは.toPlainTextメソッドを使うのです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QTextEdit,QLabel class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 18px;') jouge = QVBoxLayout() self.setLayout(jouge) def utsusu(): yomikake.setText(kakikake.toPlainText()) kakikake = QTextEdit('ここに書いたものは下の方に写される') jouge.addWidget(kakikake) kakikake.setFixedSize(400,70) kakikake.textChanged.connect(utsusu) yomikake = QLabel('') jouge.addWidget(yomikake) yomikake.setFixedSize(400,70) utsusu() qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QCheckBox チェックボックスはチェックする(選ぶ)ための四角形です。その四角形がチェックされているかどうかは .isChecked()メソッドで調べれます。チェックに変更が起きた時に発動させたいメソッドは.toggled.connectで指定するのです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QCheckBox,QLabel class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 18px;') self.shikaku = QCheckBox('選ぶ?',self) self.shikaku.move(10,10) self.shikaku.toggled.connect(self.henkou) self.shirase = QLabel('いいえ',self) self.shirase.move(10,40) # チェックに変化が起きたら、下にあるQLabelのテクストもそれに従って変える def henkou(self): if(self.shikaku.isChecked()): self.shirase.setText('はい') # チェックされている場合 else: self.shirase.setText('いいえ') # チェックされていない場合 qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QRadioButtonとQButtonGroup ラジオボタンは複数の選択肢の中から一つだけ選択するための円盤です。QtでラジオボタンはQRadioButtonクラスで作成できます。機能させるためにはQButtonGroupを作成して、同じグループのQRadioButtonを入れる必要があります。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QRadioButton,QLabel,QButtonGroup class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 21px;') jouge = QVBoxLayout() self.setLayout(jouge) self.sentakushi = QButtonGroup() # 選択肢のグループ shitsumon = QLabel('一番人口が多い国は?') # 質問を表示する shitsumon.setStyleSheet('color: blue;') jouge.addWidget(shitsumon) for kuni in ['日本','中国','韓国','越南']: botan = QRadioButton(kuni) # 一人ずつラジオボタンを作成する jouge.addWidget(botan) # レイアウトに追加 self.sentakushi.addButton(botan) # 選択グループに追加 # ラジオボタンがクリックされたら下の方のテクストの変化もさせる self.sentakushi.buttonClicked.connect(self.eranda) # 正解かどうかを表示する文字 self.kekka = QLabel('') jouge.addWidget(self.kekka) def eranda(self,botan): if(botan.text()=='中国'): self.kekka.setText('正解') self.kekka.setStyleSheet('color: green;') else: self.kekka.setText('違う') self.kekka.setStyleSheet('color: red;') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() .buttonClicked.connectでラジオボタンがクリックされたら発動するメソッドを指定できます。そのメソッドのパラメータには押された選択肢のオブジェクトが入ります。そのオブジェクトの.text()メソッドでその選択肢のテクストとを取得できます。 QComboBox コンボボックスはQRadioButtonと似ていますがQRadioButtonより作り方は簡単です。まずはQComboBoxを作成して、.addItemメソッドで選択肢にしたい文字列を入れるのです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QHBoxLayout,QComboBox,QLabel class Madoka(QWidget): def __init__(self): super().__init__() sayuu = QHBoxLayout() # 横レイアウト self.setLayout(sayuu) self.hako = QComboBox() # コンボボックスを作成する sayuu.addWidget(self.hako) # レイアウトにコンボボックスを追加する # 択肢を一人ずつコンボボックスに追加する for s in ['西瓜','南瓜','胡瓜','冬瓜']: self.hako.addItem(s) # 選ばれた時に発動するメソッドを指定する self.hako.currentTextChanged.connect(self.eranda) self.hako.setCurrentIndex(-1) self.erandano = QLabel('') # 選ばれた文字列を表示する場所 self.erandano.setStyleSheet('font-family: Kaiti SC; font-size: 32px; color: #f04625') sayuu.addWidget(self.erandano) # 選択肢の選びが更新されたらその新しい選択肢の文字列を取得する def eranda(self,text): self.erandano.setText(text) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() コンボボックスの中の選択肢が更新された時に発動させるメソッドの指定は.currentTextChanged.connectと.currentIndexChanged.connectがあります。違いは取得できるパラメータです。 .currentTextChanged.connectの場合は選んだアイテムの文字列を取りますが、.currentIndexChanged.connectはそのアイテムの置かれた順番を取るのです。 その他に、選んだ選択が変わらなくてもただコンボボックスの中のアイテムがクリックされた時に発動するメソッドを指定する場合は.activated.connectを使うこともできます。 QSpinBox スピンボックスはQSpinBoxで作成できます。 QSpinBoxは整数しか入れられないが、小数を入れたい場合はQDoubleSpinBoxを使うのです。 値が更新された時に発動するメソッドは.valueChanged.connectで指定します。 ここでボタンの長さを決めるためのスピンボックスを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QSpinBox,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() self.resize(350,150) # ウィンドウのサイズの指定 jouge = QVBoxLayout() # 縦レイアウトを使う self.setLayout(jouge) self.nagasa = QSpinBox() # スピンボックスの作成 jouge.addWidget(self.nagasa) # スピンボックスをレイアウトに入れる self.nagasa.setRange(25,300) # 範囲の指定 self.nagasa.setValue(250) # 当初の値の指定 self.nagasa.setSingleStep(25) # 一度ボタンをクリックする時に変わる値の指定 self.nagasa.valueChanged.connect(self.henka) # 値が更新されたら発動するメソッドの指定 self.botan = QPushButton() # スピンボックスの下にボタンを作る jouge.addWidget(self.botan) self.botan.setFixedSize(250,50) self.botan.setStyleSheet('background-color: #7125f0') # ボタンの長さをスピンボックスの値に従って変更させる def henka(self,x): self.botan.setFixedSize(x,50) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QDoubleSpinBox QDoubleSpinBoxの使い方は基本的にQSpinBoxと同じですが小数が入れられます。.setDecimalsで小数部の細かさを指定できます。 試しに、長さをメートルで入れたらインチとセンチの長さを表示するGUIを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QDoubleSpinBox,QLabel class Madoka(QWidget): def __init__(self): super().__init__() jouge = QVBoxLayout() self.setLayout(jouge) self.meetoru = QDoubleSpinBox() jouge.addWidget(self.meetoru) self.meetoru.setDecimals(3) # 小数部の細かさ self.meetoru.setRange(0,1) self.meetoru.setValue(0.5) self.meetoru.setSingleStep(0.001) self.meetoru.setSuffix('メートル') # 語尾に表示する文字列 self.meetoru.valueChanged.connect(self.henka) self.inchi = QLabel() # インチの長さ jouge.addWidget(self.inchi) self.senchi = QLabel() # センチの長さ jouge.addWidget(self.senchi) self.henka(0.5) def henka(self,x): self.inchi.setText('%.3f インチ'%(x*39.37)) self.senchi.setText('%.1f センチ'%(x*100)) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QSlider QSliderはQSpinBoxと似ていますが、数字ではなく円盤の位置で値を代表するウィジェットです。 デフォルトではスライダーの方向が縦ですが、横にしたい場合.setOrientation()で変更できます。 スピンボックスと同じく、値が変えられた時に発動させたいメソッドを.valueChanged.connectを指定します。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QSlider,QLabel from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() jouge = QVBoxLayout() self.setLayout(jouge) self.atai = QLabel() jouge.addWidget(self.atai) self.guruguru = QSlider() jouge.addWidget(self.guruguru) self.guruguru.setOrientation(Qt.Orientation.Horizontal) # 横にする self.guruguru.setRange(1,100) # 調整できる値の範囲 self.guruguru.setValue(50) # 最初の値 self.guruguru.valueChanged.connect(self.henka) self.henka(50) def henka(self,x): self.atai.setText('#'*(int(x/10))+' %d'%x) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() PyQt5からの変更 QSplitterと同じく、縦と横を示すフラグを使うので、フラグの置かれた場所の変更も気をつけないとです。 Qt.Horizontal > Qt.Orientation.Horizontal Qt.Vertical > Qt.Orientation.Vertical QScrollBar QScrollBarはQSliderと似ていますが、円盤の代わりに矩形とボタンが使われます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QScrollBar,QPushButton from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() jouge = QVBoxLayout() self.setFixedWidth(200) self.setLayout(jouge) self.botan = QPushButton() jouge.addWidget(self.botan) self.botan.setStyleSheet('background-color: #8848f2') jouge.setAlignment(self.botan,Qt.AlignmentFlag.AlignCenter) self.subesube = QScrollBar() jouge.addWidget(self.subesube) self.subesube.setStyleSheet('background-color: #ceb9f0') self.subesube.setOrientation(Qt.Orientation.Horizontal) # 横にする self.subesube.setRange(5,160) # 範囲 self.subesube.setValue(40) # 初期値 self.subesube.setSingleStep(4) # 一度ボタンをクリックしたら変更する値 self.subesube.setPageStep(20) self.subesube.valueChanged.connect(self.henka) self.henka(40) def henka(self,x): self.botan.setFixedSize(x,30) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() PyQt5からの変更 Qt.Horizontal > Qt.Orientation.Horizontal Qt.Vertical > Qt.Orientation.Vertical QDial QDialはQSliderと似ていますが、円盤を回ることで値を指定するのです。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QDial,QLabel from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() jouge = QVBoxLayout() self.setLayout(jouge) self.mawaru = QDial() jouge.addWidget(self.mawaru) self.mawaru.setRange(-600,600) # 範囲の指定 self.mawaru.valueChanged.connect(self.henka) self.shirase = QLabel() jouge.addWidget(self.shirase) jouge.setAlignment(self.shirase,Qt.AlignmentFlag.AlignCenter) self.mawaru.setValue(120) def henka(self,x): self.shirase.setText('%d'%x) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QDialog QMessageBox 重要なお知らせとかしたい時にQMessageBoxを使います。 例えばボタンをクリックしたらお知らせを表示する例。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QMessageBox class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 16px') botan = QPushButton('何ですか!?',self) botan.setGeometry(10,10,125,55) botan.clicked.connect(self.oshirase) # ボタンをクリックしたらメッセージボックスが現れる def oshirase(self,x): QMessageBox.about(self,'','お前様はもうお死にになっております') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QMessageBox.aboutに入れるパラメータはQMessageBox.about(親ウィジェット,タイトル,メッセージの内容)となりますが、macなどではタイトルが無効なのでここでは空にします 今回使ったQMessageBox.aboutの他にも、似ているようなQMessageBox.warning、QMessageBox.critical、QMessageBox.informationがあります。 yes/noの答えを取りたい時にQMessageBox.questionを使うことができます。 QMessageBox.questionを使う例は後記のcloseEventの例と共に書きます。 QInputDialog ユーザーに何かを一つだけ入力して欲しいウィンドウを作る場合はQInputDialogを使うことができます。 データの種類によって使い分けするのです。 一行だけの文字列を取りたい場合はQInputDialog.getTextを使うのです。 数行の文字列の場合はQInputDialog.getMultiLineText。 整数の場合はQInputDialog.getMultiLineText。 小数の場合はQInputDialog.getDouble。 コンボボックスの選択肢から文字列を選ぶ場合はQInputDialog.getItem。 例えば文字列を入れる例をあげみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QInputDialog class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 16px') title,okkei = QInputDialog.getText(self,'入力ウィンドウ','ウィンドウのタイトルを入れてください') if(okkei and title!=''): self.setWindowTitle(title) else: self.setWindowTitle('名無し') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QInputDialog.getTextからの返り値は2つあります。1つ目は入力された文字列で、2つ目は、キャンセルが押された場合はFalseとなり、そうではない場合はTrueとなります。 QFileDialog ファイルを読み込んだり保存したりしたい場合はQFileDialogを使うことができます。 QFileDialog.getOpenFileNameは一つだけファイルの名前を取りたい時に使います。 複数のファイルの場合はQFileDialog.getOpenFileNames。 ファルダだけ選ぶ場合はQFileDialog.getExistingDirectory。 ファイルを保存する場合はQFileDialog.getSaveFileName。 例えば、ファイルを選んで、そのファイルの中の文字列を取得して文字数と行数を数えるGUIを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QFileDialog,QLabel class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 22px') namae,ok = QFileDialog.getOpenFileName(self) if(ok): with open(namae) as f: naiyou = f.read() jouhou = QLabel('文字数:%d\n%d行'%(len(naiyou),len(naiyou.split('\n'))),self) jouhou.setGeometry(10,10,160,70) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QFileDialog.getOpenFileNameからの返り値は2つあります。1つ目はファイルの名前で、2つ目はokが押されたかどうかを示すTrue/Falseです。 QColorDialog QColorDialogは色を選びたい時に使います。 ボタンの色を選ぶGUIを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QColorDialog,QPushButton class Madoka(QWidget): def __init__(self): super().__init__() iro = QColorDialog.getColor() # 色を選ばウィンドウ iro = '(%d,%d,%d)'%(iro.red(),iro.green(),iro.blue()) # RGBの値にする botan = QPushButton(iro,self) botan.setStyleSheet('background-color: rgb'+iro) # 選んだ色でボタンの色に指定する botan.setGeometry(10,10,100,100) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() 色を選んでみたらその色のボタンが現れます。 QFontDialog QFontDialogは文字のフォントを選びたい時に使います。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QFontDialog,QLabel class Madoka(QWidget): def __init__(self): super().__init__() moji = QLabel('窓',self) moji.setGeometry(5,5,250,200) font,ok = QFontDialog.getFont() # フォントを選ばウィンドウ if(ok): moji.setFont(font) # 取得したフォントで文字を書く qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QMainWindow ウィンドウのウィジェットはQWidgetの代わりにQMainWindowを使うこともできます。その場合色んな機能が追加されます。 ステータスバー QMainWindowではウィンドウのメイン部分の下の方にあるステータスバーを簡単に作れます。 まずはメインの部分にするウィジェットを.setCentralWidgetで指定するのです。 そしてステータスバーは.statusBarメソッドで作成できます。 その作成されたステータスバーの.showMessageメソッドわ使って、伝えたいメッセージを表示するのです。 import sys from PyQt6.QtWidgets import QApplication,QMainWindow,QWidget,QPushButton class Madoka(QMainWindow): def __init__(self): super().__init__() self.resize(290,180) self.setStyleSheet('font-family: Kaiti SC; font-size: 18px') chuuou = QWidget() self.setCentralWidget(chuuou) # 中央に置くウィジェット botan = QPushButton('中央ボタン',chuuou) botan.setGeometry(10,10,270,110) staba = self.statusBar() # 下にあるステータスバー staba.showMessage('ステータスバーのメッセージ') # 書きたいメッセージ staba.setFixedHeight(50) # ステータスバーの高さを指定することもできる qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() メニューバー QMainWindowを使うと、.menuBar()メソッドでメイン部分の上に置かれるメニューバーを作ることができます。 メニューバーの選択肢にQActionが使われます。 例えば料理メニューを選んで会計するGUIを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QLabel,QMainWindow from PyQt6.QtGui import QAction class Madoka(QMainWindow): def __init__(self): super().__init__() css = ''' * { font-family: Kaiti SC; font-size: 20px; } QMenuBar { background-color: #def; border: 2px solid #222; } QMenu { background-color: #fbd; border: 1px solid #444; } ''' self.setStyleSheet(css) self.ryoushuusho = QLabel('0 円') self.setCentralWidget(self.ryoushuusho) self.staba = self.statusBar() self.menubar = self.menuBar() self.menubar.setNativeMenuBar(False) self.soukei = 0 self.menu1 = self.menubar.addMenu('丼物') self.ryouri1 = QAction('カツ丼',self) self.menu1.addAction(self.ryouri1) self.ryouri1.setStatusTip('350 円') self.ryouri1.triggered.connect(lambda :self.tsuika(350)) self.ryouri2 = QAction('牛丼',self) self.menu1.addAction(self.ryouri2) self.ryouri2.setStatusTip('400 円') self.ryouri2.triggered.connect(lambda :self.tsuika(400)) self.menu2 = self.menubar.addMenu('ラーメン') self.ryouri3 = QAction('叉焼メン',self) self.menu2.addAction(self.ryouri3) self.ryouri3.setStatusTip('550 円') self.ryouri3.triggered.connect(lambda :self.tsuika(550)) self.ryouri4 = QAction('豚骨ラーメン',self) self.menu2.addAction(self.ryouri4) self.ryouri4.setStatusTip('800 円') self.ryouri4.triggered.connect(lambda :self.tsuika(800)) def tsuika(self,nedan): self.soukei += nedan self.ryoushuusho.setText(str(self.soukei)+' 円') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() ここで.setNativeMenuBar(False)はmacの場合に必要です。macではこれを書かなければメニューバーが表示されないのです。 .setStatusTipは、マウスがその項目に置かれた時にステータスバーに文字列を表示させるために使われます。 項目が選ばれた時に発動するメソッドは.triggered.connectで指定するのです。 PyQt5からの変更 PyQt5ではQActionがQtWidgetsに入れられますが、PyQt6ではQActionはQtGuiに属します。使う時に注意する必要があります。 from PyQt6.QtWidgets import QAction > from PyQt6.QtGui import QAction ツールバー ツールバーもメニューバーと同じようにメイン部分の上に置かれるエリアです。ただしドラッグで場所を変えることができます。 メニューバーと同じく、QActionが入れられます。ここでQActionを作成する時に入れるのは(アイコン, テクスト, 親ウィジェット)です。アイコンが指定された場合、アイコンだけが表示されて、テクストはマウスが置かれた時に現れるのです。 例えばパソコン用品を買う時に会計するためのGUIを作ってみます。 アイコンはこの画像を使います。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QMainWindow,QToolBar,QHBoxLayout from PyQt6.QtGui import QIcon,QAction class Madoka(QMainWindow): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 19px') self.staba = self.statusBar() cenwid = QWidget() self.setCentralWidget(cenwid) hbl = QHBoxLayout() cenwid.setLayout(hbl) hbl.addWidget(QLabel('総計 ')) self.ryoushuusho = QLabel('0') hbl.addWidget(self.ryoushuusho) hbl.addWidget(QLabel('円')) hbl.addStretch() self.tana = self.addToolBar('') self.dougu1 = QAction(QIcon('icon_mouse.png'),'マウス',self) self.tana.addAction(self.dougu1) self.dougu1.setStatusTip('+1000 円') self.dougu1.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+1000))) self.dougu2 = QAction(QIcon('icon_keyboard.png'),'キーボード',self) self.dougu2.setStatusTip('+1500 円') self.tana.addAction(self.dougu2) self.dougu2.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+1500))) self.dougu3 = QAction(QIcon('icon_earphone.png'),'イヤフォン',self) self.dougu3.setStatusTip('+2000 円') self.tana.addAction(self.dougu3) self.dougu3.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+2000))) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() QDockWidget QMainWindowに使えるもう一つの機能はQDockWidgetを入れることです。 QDockWidgetはドラッグで移動できるウィジェットです。QMainWindowの.addDockWidgetメソッドで作成されたQDockWidgetを入れることができます。追加する時にQt.DockWidgetAreaのフラグで、始めた時の位置を指定する必要があります。 例えばQDockWidgetを2つ入れる例です。 import sys from PyQt6.QtWidgets import QApplication,QLabel,QPushButton,QMainWindow,QDockWidget,QTextEdit,QScrollArea from PyQt6.QtCore import Qt class Madoka(QMainWindow): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 19px') self.resize(400,300) chuuou = QPushButton('中央') self.setCentralWidget(chuuou) staba = self.statusBar() staba.showMessage('ステータスバー') dowi1 = QDockWidget('dock 1',self) # 一つドックウィジェットを作成する teit = QTextEdit('ドック1') teit.setStyleSheet('background-color: #d4a1c5') dowi1.setWidget(teit) #右の方にドックウィジェットを入れる self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea,dowi1) dowi2 = QDockWidget('dock 2',self) # 2つ目のドックウィジェット scar = QScrollArea() dowi2.setWidget(scar) dock2la = QLabel('ドック2') dock2la.setFixedSize(700,220) scar.setWidget(dock2la) scar.setStyleSheet('background-color: #a1d4bf') #上の方にドックウィジェットを入れる self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea,dowi2) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() こんなウィンドウができて、ドックウィジェットをドラッグしてみたら移動できます。 classのメソッドの書き換え ウィジェットのclassにメソッドを書き換えることである特定の動作が起きる時にやらせたいことを指定することができます。 マウスが入ったり出たりしたイベント マウスがウィジェットに入るというイベントが発生した時にenterEventメソッドで作動が決められます。このメソッドを書き換えることで、イベントが発生した時にやらせたいことを指定できます。 同じように、マウスがウィジェットから出たイベントはleaveEventです。 import sys from PyQt6.QtWidgets import QApplication,QWidget class Madoka(QWidget): def __init__(self): super().__init__() self.resize(180,120) def enterEvent(self,e): self.setStyleSheet('background-color: red') def leaveEvent(self,e): self.setStyleSheet('background-color: blue') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() マウスを通してみたらウィンドウはこんな風に赤くなります。離れたら藍色になります。 ウィジェットがクリックされたイベント マウスが押されたイベントはmousePressEventで、解放されたイベントはmouseReleaseEventです。 ダブルクリックの場合はmouseDoubleClickEvent。 マウスの情報はパラメータとして取得できます。 例えばマウスの置かれた位置は.position()で取れます。 マウスを押したり放したりダブルクリックしたりする時にマウスの位置を出力するウィンドウを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget class Madoka(QWidget): def __init__(self): super().__init__() def mousePressEvent(self,e): p = e.position() # マウスが押された場所 print('(%d,%d)でマウスが押された'%(p.x(),p.y())) def mouseReleaseEvent(self,e): p = e.position() # マウスが解放された場所 print('(%d,%d)でマウスが放された'%(p.x(),p.y())) def mouseDoubleClickEvent(self,e): p = e.position() # マウスがダブルクリックされた場所 print('(%d,%d)でダブルクリック'%(p.x(),p.y())) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() ウィンドウのサイズが変更されたイベント ウィンドウの大きさが変えられた時にresizeEventが発生します。そのresizeEventのパラメータとして、元の大きさを.oldSizeメソッドで、新しい大きさを.sizeメソッドで取得することができます。 import sys from PyQt6.QtWidgets import QApplication,QWidget class Madoka(QWidget): def __init__(self): super().__init__() def resizeEvent(self,e): s0 = e.oldSize() # 元の大きさ s1 = e.size() # 今の大きさ print('(%d,%d) > (%d,%d)'%(s0.width(),s0.height(),s1.width(),s1.height())) qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() ウィンドウが閉じられそうになったイベント ウィンドウが閉じられそうになった時に発生するのはcloseEventです。 これを書き換えることで、閉じる時の確認などすることができます。 例えばQMessageBox.questionを使って本当に閉じたいかどうかを確かめらせる例です。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QMessageBox class Madoka(QWidget): def __init__(self): super().__init__() self.setStyleSheet('font-family: Kaiti SC; font-size: 20px') self.show() def closeEvent(self,e): yesno = QMessageBox.question(self,'','本当に閉じてしまってもよろしいのでしょうか?') if yesno == QMessageBox.StandardButton.Yes: e.accept() # yesと答えたら閉じる else: e.ignore() # noと答えたら何も起きない qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() PyQt5からの変更 QMessageBox.Yes > QMessageBox.StandardButton.Yes キーが押されたイベント キーボードが押された時にkeyPressEventが、キーが解放された時にkeyReleaseEventが発生します。メソッドのパラメータで押されたキーの情報を取得できます。 .key()メソッドを使ったらそのキーのフラグが得られます。 これを使って特定のキーが押された時にやらせたいことを指定することができます。 例えば ← → ↑ ↓ キーでボタンを移動するGUIを作ってみます。 import sys from PyQt6.QtWidgets import QApplication,QWidget,QPushButton from PyQt6.QtCore import Qt class Madoka(QWidget): def __init__(self): super().__init__() self.botan = QPushButton('※',self) self.botan.setGeometry(40,30,60,50) self.show() def keyPressEvent(self,e): x = self.botan.x() y = self.botan.y() if(e.key()==Qt.Key.Key_Left): self.botan.move(x-10,y) self.botan.setText('←') elif(e.key()==Qt.Key.Key_Right): self.botan.move(x+10,y) self.botan.setText('→') elif(e.key()==Qt.Key.Key_Up): self.botan.move(x,y-10) self.botan.setText('↑') elif(e.key()==Qt.Key.Key_Down): self.botan.move(x,y+10) self.botan.setText('↓') elif(e.key() in [Qt.Key.Key_Escape,Qt.Key.Key_Q]): self.close() print('(%d,%d) > (%d,%d)'%(x,y,self.botan.x(),self.botan.y())) def keyReleaseEvent(self,e): self.botan.setText('※') qAp = QApplication(sys.argv) mado = Madoka() mado.show() qAp.exec() このようにボタンとウィンドウが出ます。 キーボードで「←」を押したらボタンが左の方へ移ります。 PyQt5からの変更 キーのフラグの置かれた場所はPyQt5とは違います。 Qt.Key_Left > Qt.Key.Key_Left Qt.Key_Right > Qt.Key.Key_Right Qt.Key_Up > Qt.Key.Key_Up Qt.Key_Down > Qt.Key.Key_Down Qt.Key_Escape > Qt.Key.Key_Escape 参考 今までqiitaでPyQt5の色んな記事を読んでとても参考になりましたので、自分の読んだ記事のリンクをここにまとめてみます。 https://qiita.com/MamoruItoi/items/340efff6c079a00b04fc https://qiita.com/kenasman/items/471b9930c0345562cbbf https://qiita.com/kenasman/items/b9ca3beb25ecf87bfb06 https://qiita.com/kenasman/items/70a3ef914b0e7e55a123 https://qiita.com/kenasman/items/73d01df973a25ae704e4 https://qiita.com/montblanc18/items/0188ff680acf028d4b63 https://qiita.com/marksard/items/7afeb3ab3ffa61ae8d27 https://qiita.com/pashango2/items/7770a27e86d739186ed3 https://qiita.com/Nobu12/items/acd3caa625be8eebc09c https://qiita.com/Nobu12/items/36bc4cc6cf4163b66847 https://qiita.com/Nobu12/items/81da82b8b984bf54f0a0 https://qiita.com/Nobu12/items/6248c509401b0e666a55 https://qiita.com/Nobu12/items/67abf075177fc36484ff https://qiita.com/Nobu12/items/d5f6cc57274a64170734 https://qiita.com/mizu-kazu/items/c1897c07cb0b61ae8f55 https://qiita.com/karakuri-t910/items/7d2aac29d979f2413162 https://qiita.com/karakuri-t910/items/5aae0ba3bed05eeb5109 https://qiita.com/karakuri-t910/items/1bdbcbf8b4b92555c123 https://qiita.com/karakuri-t910/items/e54e771c576d2a0aa11a https://qiita.com/karakuri-t910/items/03debf242efd944eecc2 https://qiita.com/karakuri-t910/items/bfd18dffbe2674a3b478 これらほとんどはPyQt5ですが、やはりPyQt6にもほとんど同じように使えます。 終わりに 以上PyQt6の大体の使い方とサンプルコードを紹介してみました。全部PyQt5から使える機能なので、書き方の違いがわかれば全部PyQt5にも通用します。
- 投稿日:2021-08-09T22:29:44+09:00
【ImageJからPythonへ】napariの使い方 (2)
はじめに 本記事はシリーズになっています。他の記事はこちらから。 前の記事 【ImageJからPythonへ】napariの使い方 (1) 前回はnapariの基本的な操作を解説しました。そこから一段階レベルアップして、今回の記事では viewerから様々なオブジェクトにアクセスする カスタムキーバインドで効率的に解析する という2点についてまとめます。1.は少し退屈ですが、知っているほど今後キーバインドやウィジェットを自作するときに、より柔軟で使いやすい機能を追加できるようになります。少なくとも2.のキーバインドができるようになれば、一気に画像解析が効率化されます。 0. napariの起動 前回みたく、napariを起動し、viewerオブジェクトを用意します。 import napari viewer = napari.Viewer() 1. viewerから様々なオブジェクトにアクセスする 当然ですが、napariは数多くの「構成要素」からなっています。napariの強みはカスタマイズのしやすさなので、これら構成要素をスクリプトから簡単に操作することができます。 ウィンドウに映っているものだけでいえば、次の図に示すようにviewerのattributeからアクセスできます。 重要なオブジェクト viewer.layers listのような形でレイヤーを格納しているオブジェクトです。前回も出てきましたが、listのようにviewer.layers[0]で要素にアクセスできるほか、viewer.layers["name"]のようにdictのように要素にアクセスすることもできます。 selection: 選択されているレイヤーをsetのような形式で返す。 remove(name): nameという名前のレイヤーを削除。存在しない名前を指定した場合はValueErrorが吐かれる。 remove_selected(): 選択されているレイヤーを削除。 select_all(), select_next(), select_previous(): 関数名の通り、それぞれすべてのレイヤー、一つ次のレイヤー、一つ前のレイヤーを選択する。 viewer.scale_bar スケールバーを表示するオブジェクトです。画像の拡大縮小に合わせてちょうどよい長さに合わせてくれるので非常に便利です(ImageJではこれが結構面倒だったりしますね...)。 visible bool: 見えるようにするかどうかをTrue/Falseでセットできる。 colored bool: 色を付けるかどうかをTrue/Falseでセットできる。 font_size float: フォントの大きさを指定する。 unit str: 長さの単位を指定する。"u"は"µ"に直してくれる。"mm" ↔ "µm" ↔ "nm"のような変換もしてくれる。 viewer.text_overlay テキストを重ねて表示するオブジェクトです。オーバーレイなので、画像の拡大縮小、3D回転、スライダーの移動に関係なく常に同じ位置に表示されます。napari-animationプラグインと組み合わせて画像のタイムスタンプを付加したり、一時的に何かの情報を表示する際に使うのが一般的かと思います。 visible bool: 見えるようにするかどうかをTrue/Falseでセットできる。 color: 色を"white"のような名前かRGBA(strまたは配列)で指定する。 font_size float: フォントの大きさを指定する。 position str: テキストの表示位置。"top_left"など。 text str: テキストの内容。改行\nなども対応。 viewer.dims ビューアーの次元に関する情報を格納したオブジェクトです。 ndim int: ビューアーの最大次元数。例えば3D+tの画像と3D画像があるとき、ndim==4となる。 ndisplay int: 2D表示なら2, 3D表示なら3になる。 range tuple: 各次元の目盛りを表す。たとえばrange[0]==(0.0, 4.5, 0.5)であれば、0番目の次元は0.0から4.5の間を0.5間隔で取る。これがz軸であるとすれば、インデックス$0, 1, 2, ...$にたいして実際の空間でのz座標は$z=0.0, 0.5, 1.0, ...$となる。 current_step tuple: 現在表示している座標。例えば3D画像のz=3の断面を表示しているときは(3, 0, 0)となる。 axis_labels tuple: 各軸の名前。ここで指定した文字列がそのまま座標軸やスライダーの横に表示される。xy座標なら("y", "x")となる。 order tuple: 軸の順番。Ctrl+EやCtrl+Tで見る向きを変えたときに関係してくる。少し分かりづらいが、例えばorder=(2, 0, 1)であれば、本来$(z,y,x)$という順番が$(x,z,y)$に変わる。一般に、axis_labelsがtuple(axis_labels[i] for i in order)に変わると考えるとよい。 current_stepとorderを用いると、スタック画像から現在見えている2D画像を取ってくることができます。orderの最後の2つの整数がそれぞれ$y,x$軸が何番目の次元に対応するかを意味するので、current_stepのorder[-2]番目とorder[-1]番目の要素を:に変えればスライスが作れます。 # スライスの作成 sl = list(viewer.dims.current_step) for i in viewer.dims.order[-2:]: sl[i] = slice(None) sl = tuple(sl) # arrayに入れる layer = viewer.layers[0] img2d = layer.data[sl] viewer.axes 座標軸の表示に関する情報を格納したオブジェクトです。基本的にメニューバーの"View > Axes"から変更するため、スクリプトからアクセスすることはほとんどないので省略します。 viewer.cursor カーソルオブジェクトです。カーソル位置を利用する以外使い道がない気が...。 position tuple: カーソルの位置。小数の精度で取得できる。レイヤーのscaleを指定している場合、必ずしもピクセルの位置とは対応しないので注意。ビューアーがn次元ならn個の小数が返ってくる。 viewer.camera カメラオブジェクトです。空間に画像が浮かんでいて、その画像をこのカメラに移して、我々がモニター上でそれを見ているイメージです。大きな画像の特定部位をアップで表示させたいときに使うくらいでしょうか。 angles tuple: カメラの角度。オイラー角で$(r_x, r_y, r_z)$の順番に並んでいる。2D表示では値を変えても次の操作ですぐに戻ってしまう。3D表示用のパラメータ。 center tuple: カメラの中心座標。2D画像に平行な面上をカメラが平行移動するイメージ。画像の$(y_0,x_0)$のピクセルに合わせたければcenterに$(y_0,x_0)$を代入する。 zoom float: カメラのズーム倍率。 その他 viewer.window.main_menu: メニューバーのウィジェット。viewer.window.my_menu = viewer.window.main_menu.addMenu("&My Functions")みたくカスタムのメニューを追加できる。 viewer.window.qt_viewer.layerButtons: レイヤー関係のボタンを格納したウィジェット。 2. カスタムキーバインドで効率的に解析する 画像解析では、領域選択など、手動で行うほうが圧倒的に効率が良いことがあります。しかし、ビューアーとスクリプトを行ったり来たりするのは面倒ですし、カーソルの位置を利用したかったらうまいことやらないとそもそもスクリプトに戻れないので、キーバインドは必須とも言えます。 napariではキーバインドの需要がちゃんと理解されており、非常に簡単にキーバインドを作れます。以下それについて解説します。 ※ 2021/08/09現在、ImageJのROIのような手動描画型のオーバーレイを実装するかどうかがIssueでよく話題になっています(例えばこちら)。早くて次のv0.4.11で実装されるかもしれません。実装され次第、キーバインドの利用の幅がより一層大きくなると思います。 ※ 公式ドキュメントでは、例えばこちらに例があります。 使えるキー アルファベットと記号は基本的に全て使えます。特殊なキーに関しては、以下の文字列を与えることになります。 Shift, Control, Alt, Meta, Up, Down, Left, Right, PageUp, PageDown, Insert, Delete, Home, End, Escape, Backspace, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, Space, Enter, Tab キーの組み合わせは、例えば"Control-Shift-X"のように書きます。 Ctrlと特殊キーなど、OSによっては固有のキーバインドが優先されるので、変な組み合わせは避けましょう。 キーバインドする対象 自作関数のバインドの対象として、napariでは3通り選べます。 napari.Viewerにバインドする。 1種類のレイヤー、例えばShapesレイヤーにバインドする。 ビューアーに追加された特定の1枚のレイヤー、例えばviewer.layers[0]にバインドする。 バインドの対象 バインドする関数の引数 アクセス範囲 キーバインドが利く条件 napari.Viewer ビューアー layers, scale_bar, cursorなど含むビューアー全体 常時 レイヤー1種類 レイヤー レイヤー内のみ その種類のレイヤーが選択されている間 特定の1枚のレイヤー レイヤー レイヤー内のみ その1枚のレイヤーが選択されている間 汎用性は当然1.が最も高いですが、例えばImageレイヤーでしか使わないような関数を1.でバインドするとコードが煩雑になってバグも増えます。また、例えばShapesレイヤーにバインドする場合でも、バインドする関数を呼び出す用のレイヤーと単に図形を描きたいだけのレイヤーで分ける場合は、後者でむやみに関数が呼び出されないようにしたいです。3通りのバインドの仕方は、よく考えてうまく使い分けることが重要です。 引数が変わるだけなので、1.で主に解説します。 キーバインドの定義の仕方 napari.Viewerのメソッドbind_keyを使って関数を登録します。登録された関数はプロパティのkey_mapにappendされます。bind_keyは関数を引数にとるので、デコレータで書くのが最もシンプルで分かりやすいです。 次の例は、F1を押したときにtext_overlayに文字を表示し、もう一度押したら消えるというキーバインドです。 viewer = napari.Viewer() @viewer.bind_key("F1") def func(viewer): if viewer.text_overlay.visible: viewer.text_overlay.visible = False viewer.text_overlay.text = "" else: viewer.text_overlay.visible = True viewer.text_overlay.text = "Pushed!" 関数名funcはこのまま使うことはもうないので、面倒なら_にするか、コメントアウト代わりにswitch_text_overlayみたくただひたすらに分かりやすい長い名前にするのがよいかと思います。 キーバインドの上書き 不注意でキーバインドを上書きしてしまうと困る場合があります。napariではそれを避けるために、デフォルトでは上書きを禁止しています。間違った関数をバインドした場合などは @viewer.bind_key("F1", overwrite=True) def func(viewer): ... というように、overwrite=Trueで明示的に上書きを許可します。 押している間だけ発動するキーバインド 特定のキーを押している間にビューアーのプロパティを書き換え、離したら元に戻すという、withブロック的なことができると便利ですよね。実は関数にyieldを入れるだけでこれができます。次の例は、F2を押している間だけtext_overlayに文字が表示されるキーバインドです。 @viewer.bind_key("F2") def func(viewer): viewer.text_overlay.visible = True viewer.text_overlay.text = "Pushing" yield # 押している間、ここで止まる viewer.text_overlay.visible = False viewer.text_overlay.text = "" 実用的なキーバインドの例 実用的な例も紹介しておきます。次の例は、ビューアーに画像を追加し、カーソルの位置の周囲$5\times5$ピクセルの平均値をグローバル変数resultsにappendしていくキーバインドになります。グローバル変数に残るので、後から解析できます。 viewer = napari.Viewer() # ランダムノイズ画像を作成してビューアーに送る arr = np.random.random((256, 256)) viewer.add_image(arr) # 結果を格納するリスト results = [] @viewer.bind_key("m", overwrite=True) def measure_local_mean(viewer): # カーソルの位置の整数値を取得 y, x = viewer.cursor.position yc = int(y) xc = int(x) # 5x5画像を取得 img = viewer.layers[0].data img0 = img[yc-2:yc+3,xc-2:xc+3] # 平均値をappend mean_intensity = np.mean(img0) results.append(mean_intensity) # 無反応だと不安なので、平均値を左下に表示しておく viewer.status = mean_intensity 他にも、ShapesレイヤーとImageレイヤーを組み合わせて、長方形で囲った領域の平均値を測るなど、かなり自由にカスタマイズできます。ビューアーを引数にとるので、キーバインドからレイヤーを追加することもできます。 レイヤーにバインドする 最後にレイヤーにバインドする場合 (2., 3.) の書き方だけ紹介します。Shapesレイヤーを例にとります。 2. Shapesレイヤーすべてにバインドする クラスメソッドとして呼び出します。 @napari.layers.Shapes.bind_key("F1") def print_coordinates(layer): print(layer.data) 3. 特定の1枚のShapesレイヤーにバインドする viewer.layers[0]のようにビューアーからアクセスしてもよいですが、キーバインド専用のレイヤーを追加する際のadd_shapesの返り値を利用するのが楽です。 # 空のShapesレイヤーを追加する。 shapes = viewer.add_shapes(ndim=2) @shapes.bind_key("F1") def print_last_coordinates(layer): print(layer.data[-1]) 終わりに キーバインドを登録すれば、手動の強みとPythonスクリプト上での解析の利点が組み合わせられるので、画像解析が捗りますね!
- 投稿日:2021-08-09T22:24:36+09:00
Chapter 1 - §01 Python 3 の準備
Python 3 のインストール PythonにはPython 2 と Python 3 がありますが、Python 2 はこれからはバージョンアップされることはないので、Python 3 で解説していきます。 Python 3 のオフィシャルサイト Python 3 のオフィシャルサイト ここからPythonを無料でダウンロードできます。 インストールの仕方はこのサイトを参考にしてください。 統合開発環境の用意 おすすめは、Visual Studio CodeまたはPycharmです。 どちらかをインストールしてください。 ・Visual Studio Code(VScode)のインストール ここからインストール。 ・Pycharmのインストール ここからインストール。 Pythonを起動してみる インストールが完了したら、画面の左下のwindowsロゴマークをクリックして一番上にPython 3.x.x (64bit) と書かれたプログラムがあるのでそれをクリックします。 ↓この画面が出ればOK コードを書いて動かしてみる この画面が出たことで、pythonは動かせるようになりました。 定番の"Hello World!!"を表示させましょう。 print("Hello World!!") これを >>> の後に打って、Enterを押してみましょう。 このようになれば成功です。 今回はこれで終わりにします。 次回からは開発環境を使います。 お疲れ様でした。 余談 pythonは十分な機能がありますが、それでも足りないものが出てくることがあります。 それで、よく使われる外部ライブラリを最初から組み込んである状態で配布されているものもあります。 その一例として、Anacondaというものがあります。 このリンクからダウンロードできます。 Windowsでのインストール方法はこちら。
- 投稿日:2021-08-09T22:24:36+09:00
Chapter01 - §01 Python 3 の準備
Python 3 のインストール PythonにはPython 2 と Python 3 がありますが、Python 2 はこれからはバージョンアップされることはないので、Python 3 で解説していきます。 Python 3 のオフィシャルサイト Python 3 のオフィシャルサイト ここからPythonを無料でダウンロードできます。 インストールの仕方はこのサイトを参考にしてください。 統合開発環境の用意 おすすめは、Visual Studio CodeまたはPycharmです。 どちらかをインストールしてください。 ・Visual Studio Code(VScode)のインストール ここからインストール。 ・Pycharmのインストール ここからインストール。 Pythonを起動してみる インストールが完了したら、画面の左下のwindowsロゴマークをクリックして一番上にPython 3.x.x (64bit) と書かれたプログラムがあるのでそれをクリックします。 ↓この画面が出ればOK コードを書いて動かしてみる この画面が出たことで、pythonは動かせるようになりました。 定番の"Hello World!!"を表示させましょう。 print("Hello World!!") これを >>> の後に打って、Enterを押してみましょう。 このようになれば成功です。 今回はこれで終わりにします。 次回からは開発環境を使います。 お疲れ様でした。 余談 pythonは十分な機能がありますが、それでも足りないものが出てくることがあります。 それで、よく使われる外部ライブラリを最初から組み込んである状態で配布されているものもあります。 その一例として、Anacondaというものがあります。 このリンクからダウンロードできます。 Windowsでのインストール方法はこちら。
- 投稿日:2021-08-09T22:22:33+09:00
Z3Py 例題 巡回問題
問題 以下の都市の集合と各2都市間の移動コストが与えられたとき、全ての都市をちょうど一度ずつ巡り出発地に戻る巡回路のうちで総移動コストが最小のものを求めろ。 回答 example_traveling_salesman.py from z3 import * map = [ [0, 2, 3, 1, 0, 0], [2, 0, 0, 2, 2, 4], [3, 0, 0, 2, 2, 3], [1, 2, 2, 0, 2, 0], [0, 2, 2, 2, 0, 1], [0, 4, 3, 0, 1, 0] ] for total_cost in range(24): t_max = 7 T = [Int("T%s" % i) for i in range(t_max)] f = Function('f', IntSort(), IntSort(), IntSort()) s = Solver() s.add([And(1 <= T[i], T[i] <= 6) for i in range(len(T))]) for i in range(6): for j in range(6): s.add(f(i+1, j+1) == map[i][j]) for i in range(len(T)-1): s.add(f(T[i], T[i+1]) > 0) s.add([Distinct([T[i] for i in range(t_max-1)])]) s.add(T[0] == 1) s.add(T[-1] == 1) s.add(Sum([f(T[i], T[i+1]) for i in range(t_max-1)]) < total_cost) print("total_cost=" + str(total_cost) + ":") print(s.check()) if s.check() == sat: break m = s.model() for i in range(len(T)): print('T['+str(i)+']='+str(m[T[i]])) route_cost = [map[m[T[i]].as_long()-1][m[T[i+1]].as_long()-1] for i in range(t_max-1)] print(route_cost, sum(route_cost)) 出力 total_cost=0: unsat total_cost=1: unsat ~中略~ total_cost=11: unsat total_cost=12: sat T[0]=1 T[1]=2 T[2]=5 T[3]=6 T[4]=3 T[5]=4 T[6]=1 [2, 2, 1, 3, 2, 1] 11 解説 基本戦略はハノイの塔と同じ。初期状態から最終状態への一連の遷移状態が成立していることを確認する。 さらに、遷移コストの合計が〇以下とし、〇をすこしずつ大きくしていき、成立する解を求めることで、最小コストの経路を探索する。 MapはTrue、Falseではなく、各都市間の移動コストとし、0は移動できないと定義した。 他の例題 Z3Py個人的ポータル へ
- 投稿日:2021-08-09T21:55:06+09:00
pyenvでインストールしたpythonをrootユーザー権限(sudo)で使う方法
pythonの環境を確認 $which python /home/[user]/.pyenv/shims/python pyenvで管理されているpythonにパスが通っている。 $python -V Python 3.7.7 バージョンは3.7.7 sudoで実行したときの挙動を確認する print_python_version.py import platform print(platform.python_version()) pythonのバージョンがプリントされるスクリプト $python print_python_version.py 3.7.7 一般ユーザーとして実行する。pyenvのpythonで実行されている。 $sudo python print_python_version.py 2.7.16 rootユーザーの権限で実行する。pythonのバージョンが3.7.7でない。 $sudo which python /usr/bin/python システムのpythonで実行されている。 実行するpythonをshebangで明記する print_python_version_shebang.py #!/home/[user]/.pyenv/shims/python import platform print(platform.python_version()) chmod +x print_python_version_shebang.py 実行権限を与える。 $./print_python_version_shebang.py 3.7.7 一般ユーザーとして実行する。pyenvのpythonで実行されている。 $sudo ./print_python_version_shebang.py 3.7.7 rootユーザーの権限で実行する。pyenvのpythonで実行されている。 まとめ shebangで使用したいpythonのパスを明記すれば解決した。
- 投稿日:2021-08-09T21:44:31+09:00
AWS Step Functions 調査メモ
はじめに AWS Step Functions の調査メモ。深堀りはせず概要を把握することが目標です。 Step Functions で Lambda 実行 Step FunctionsでCloudWatch logsにログを書き込むLambdaを実行します。 以下はログを書き込むLambda(Python3.8)です。 Lambda実行時に受け取ったJSONデータをログに記録します。 また、return で受け取ったJSONデータを返しています。 test001 import json def lambda_handler(event, context): print("test desu {0}".format(event) ) return (event) 必要なIAMロールは、CluodWatch Logs の以下3つ CreateLogGroup CreateLogStream PutLogEvents 作成したStepFunctionsのjsonコードです。 MyStateMachine001 { "Comment": "This is your state machine", "StartAt": "Lambda Test001", "States": { "Lambda Test001": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:test001:$LATEST", "Payload.$": "$" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "End": true, "Comment": "あいうえお" } } } Step FunctionsのWorkflow Studioです。 実行時のペイロードです 実行ステータスが成功と表示されればStep Functionsとしては成功しています Lambda test001 の実行ログにStep Functions実行時のJSONデータが記録されています(期待した挙動) ペイロードとは ペイロードとは、StepFunctions 実行時に渡すJSON形式のデータのこと。 どうやら引数みたいなものらしい。 ペイロードには3つの設定があります。 設定 概要 Use state input as payload StepFuncitons 実行時に設定 Enter payload StepFuncitonsコード内に設定 No payload ペイロード不要 フローとは フローとは、Step Functions に用意された条件分岐などのプログラム処理のことです。 2021年8月時点では7種類のフローがあります。 フロー 概要 Choice 条件分岐 If文みたいなもの。 Parallel 並行処理 Map ループ処理 For文に似てる。同時実行数を設定できる。 Pass 情報を渡すだけ。入力値をもとに出力値を作成できる。デバッグする際に便利 Wait 指定した時間(秒)処理と止める。指定した日時まで処理を止めることもできる。 Success,Fail Step Functions の実行結果を設定できる。 フロー使ってみる 7種類のフローを使ってみます 【Choice】使ってみる Choiceは条件分岐の処理です。実行結果によって、実行する処理を変えることができます。 作成したStep Functionsのjsonデータです。 { "Comment": "This is your state machine", "StartAt": "Lambda Test001", "States": { "Lambda Test001": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:test001:$LATEST", "Payload.$": "$" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "Comment": "あいうえお", "Next": "Choice" }, "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$.HOGE", "StringEquals": "HOGE 200", "Next": "Lambda HOGE 200" } ], "Default": "Lambda Not HOGE 200" }, "Lambda HOGE 200": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:test001:$LATEST", "Payload": { "FUGA": "FUGA 100" } }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "End": true }, "Lambda Not HOGE 200": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:test001:$LATEST", "Payload": { "FUGA": "FUGA 200" } }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "End": true } } } 実行時のペイロード① <実行結果①> 実行時のペイロード② <実行結果②> 【Parallel】使ってみる 平行処理ができる様子 【Map】【Wait】使ってみる lambda test001 を指定した回数実行する処理を作成します。 項目配列へのパスに、JSONデータの中の配列情報をもつ箇所を指定します。 最大同時実行を 1 に設定し、1回のループで1つ処理を行うようにしました。 lambda test001に S3バケットにファイル出力する仕組みを追加 (Lambda)test001 import json import boto3 from datetime import datetime s3 = boto3.resource('s3') def lambda_handler(event, context): bucket = 'tmp-hokdvhvwzw' # S3バケット名 key = 'test_' + datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '.txt' file_contents = str(event) # ファイルの内容 obj = s3.Object(bucket,key) # S3バケット名とパスを指定 obj.put( Body=file_contents ) # S3バケットにファイル出力 print("key = {0}, event = {1}".format(key, event) ) return (event) 必要なIAMロール CluodWatch Logs CreateLogGroup CreateLogStream PutLogEvents S3バケット AmazonS3FullAccess ↑ ホントはここまで強い権限は必要ない 実行時に設定するJSONデータ { "detail": { "shipped": [ { "prod": "E01", "dest-code": 1001, "note": "AAA" }, { "prod": "E02", "dest-code": 1002, "note": "BBB" }, { "prod": "E03", "dest-code": 1003, "note": "CCC" }, { "prod": "E04", "dest-code": 1004, "note": "DDD" }, { "prod": "E05", "dest-code": 1005, "note": "EEE" } ] } } S3バケットに出力されたファイル PS C:\Users\user01> cat .\test_2021-08-08-23-18-06.txt {'prod': 'E01', 'dest-code': 1001, 'note': 'AAA'} PS C:\Users\user01> cat .\test_2021-08-08-23-18-36.txt {'prod': 'E02', 'dest-code': 1002, 'note': 'BBB'} PS C:\Users\user01> cat .\test_2021-08-08-23-19-06.txt {'prod': 'E03', 'dest-code': 1003, 'note': 'CCC'} PS C:\Users\user01> cat .\test_2021-08-08-23-19-36.txt {'prod': 'E04', 'dest-code': 1004, 'note': 'DDD'} PS C:\Users\user01> cat .\test_2021-08-08-23-20-07.txt {'prod': 'E05', 'dest-code': 1005, 'note': 'EEE'} PS C:\Users\user01> 【Pass】使ってみる Passの次に実行するlambda test001 に渡すJSONデータを加工します。 lambdaはこれ ↓ を使用しました。 test001 import json def lambda_handler(event, context): print("test desu {0}".format(event) ) return (event) Passの設定で、HOGE,FUGA の値をもつJSONデータを作成します。FUGAの値は、ペイロードのJSONデータから取得します。 実行時に設定するJSONデータ { "FUGA": "FUGA 123" } Step Functions 実行後のLambda test001 のログ 2021-08-09T09:50:24.212+09:00 test desu {'HOGE': 'HOGE-1234567890', 'FUGA': 'FUGA 123'} 【Success】【Fail】使ってみる ペイロードの中のHOGEの値が 100 の場合にSuccessとなり、それ以外はFailになる処理を作りました。 <実行結果> 作ってみた(1):S3バケット上のJSONデータをもとにループ処理する S3バケットに格納したJSONデータを取得し、そのJSONデータの情報をもとにループ処理を実行します。 Lambda testLoadJSONfroms3 testLoadJSONfroms3 import boto3 import json def lambda_handler(event, context): print(event) s3 = boto3.resource("s3") bucket = s3.Bucket( event["bucketName"] ) jsonFile = bucket.Object(event["prefix"] + "/" + event["json"]) jsonData = jsonFile.get() jsonInfo = jsonData['Body'].read() return jsonInfo test001 import json def lambda_handler(event, context): print("test desu {0}".format(event) ) return (event) ※IAMロールの見直し※ 作成したStep Functionsに2種類(testLoadJSONfroms3,test001)のLambdaを実行する権限があるかIAMロールを要確認 StepFunctions { "Comment": "This is your state machine", "StartAt": "Lambda LoadJSONfromS3", "States": { "Lambda LoadJSONfromS3": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { "Payload.$": "$", "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:testLoadJSONfromS3:$LATEST" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "Next": "Pass (Debug)" }, "Pass (Debug)": { "Type": "Pass", "Next": "Pass (Filter)" }, "Pass (Filter)": { "Type": "Pass", "InputPath": "$.Payload", "Next": "Map" }, "Map": { "Type": "Map", "End": true, "Iterator": { "StartAt": "Lambda TEST001", "States": { "Lambda TEST001": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "Payload.$": "$", "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:test001:$LATEST" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "Next": "Wait" }, "Wait": { "Type": "Wait", "Seconds": 5, "End": true } } }, "MaxConcurrency": 1 } } } ペイロードには、jsonデータがあるS3バケットとプレフィックス(ディレクトリ)を指定します。 ペイロード { "bucketName": {{S3バケット名}} "prefix": {{プレフィックス}} "json": {{jsonファイル名}} } S3バケットにあるJSONデータ S3バケットにあるJSONデータ [ {"StackName": "test001-vpc","Code": "vpc.yml", "PJPrefix": "Project1","VPCCIDR": "10.11.0.0/16"}, {"StackName": "test001-subnet1","Code": "subnet-public.yml", "PJPrefix": "Project1","NetworkName": "Net001","PublicSubnetCIDR": "10.11.1.0/24", "AZName": "ap-northeast-1a"}, {"StackName": "test001-sg","Code": "sg.yml", "PJPrefix": "Project1","ServiceName": "ServiceA","SGNo": "001"} ] 作ってみた(2):RunCommandを実行し結果を取得する EC2に対しRunCommandでOSコマンドを実行し、その結果取得するStep Functionsを作ります。 Step Functions のjsonコードはこちら。 StepFunctions { "Comment": "This is your state machine", "StartAt": "Lambda RunCommand", "States": { "Lambda RunCommand": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:testRunCommand:$LATEST", "Payload.$": "$" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "Next": "Wait" }, "Wait": { "Type": "Wait", "Seconds": 1, "Next": "Lambda RunCommand2" }, "Lambda RunCommand2": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "Payload.$": "$", "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:testRunCommand2:$LATEST" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "Next": "Choice" }, "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$.com_status", "StringEquals": "Success", "Next": "Lambda RunCommand3" }, { "Variable": "$.com_status", "StringEquals": "InProgress", "Next": "Wait" } ], "Default": "Fail" }, "Lambda RunCommand3": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "OutputPath": "$.Payload", "Parameters": { "Payload.$": "$", "FunctionName": "arn:aws:lambda:ap-northeast-1:000000000000:function:testRunCommand3:$LATEST" }, "Retry": [ { "ErrorEquals": [ "Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException" ], "IntervalSeconds": 2, "MaxAttempts": 6, "BackoffRate": 2 } ], "End": true }, "Fail": { "Type": "Fail" } } } Lambda RunCommand はRunCommandを実行します RunCommand import json import boto3 ssm = boto3.client('ssm') def lambda_handler(event, context): command = event["command"] instance_id = event["instance_id"] r = ssm.send_command( InstanceIds = [instance_id], DocumentName = "AWS-RunShellScript", Parameters = { "commands": [ command ] } ) command_id = r['Command']['CommandId'] print("command_id = {0}".format(command_id) ) return { 'command_id': command_id } Lambda RunCommand2 RunCommandの実行結果を取得します。 invocations[0]['Status']は、処理中なら InProgress 実行成功なら Success 実行失敗なら Failedとなります。 RunCommand2 import json import boto3 ssm = boto3.client('ssm') def lambda_handler(event, context): command_id = event["command_id"] res = ssm.list_command_invocations( CommandId = command_id, Details = True ) invocations = res['CommandInvocations'] com_status = invocations[0]['Status'] output = "output={0}".format(invocations[0]['CommandPlugins'][0]['Output']) return { 'command_id': command_id, 'com_status': com_status, 'output': output } Lambda RunCommand3 は特になにも処理していません。 RunCommand3 import json def lambda_handler(event, context): output = event["output"] return { 'statusCode': 200 } StepFunctions実行時のペイロード ペイロードには、実行するコマンドと実行するEC2のインスタンスIDを設定します。 ペイロード(コマンド成功) { "command": "ifconfig", "instance_id": "i-XXXXXXXXXXX" } ペイロード(コマンド失敗) { "command": "BADcommand", "instance_id": "i-XXXXXXXXXXX" } <実行結果> さいごに AWS Step Functionsの使い方がちょっとわかった気がします。
- 投稿日:2021-08-09T20:11:28+09:00
Ubuntu 20.04にPython3.xを複数インストールして切り替える方法(PPA・apt)
はじめに 本書では、Ubuntu 20.04に、Pythonの環境をインストールする方法をまとめます。 案件によってPythonのバージョンが違うこともあるため、複数のバージョンをインストールして、alternativesで切り替えられるようにします。 また、Pythonインストール後には、venvで、アプリケーション等で使用する環境を作成することとします。 本書では、Python 3.8がもともと入っているため、3.6、3.7、3.9、更に、古いアプリを動かすために2.7もインストールすることを想定します。 インストールした中で、最新の3.9を有効にし、「newest」という環境をvenvで作成する手順をまとめます。 インストール手順 リポジトリ追加 sudo apt-get install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa For nightly builds, see ppa:deadsnakes/nightly https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly More info: https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa Press [ENTER] to continue or Ctrl-c to cancel adding it. と表示されたらEnterキーを押します。 apt update sudo apt update Python 各バージョンインストール sudo apt install -y python3.9 python3.9-venv sudo apt install -y python3.7 python3.7-venv sudo apt install -y python3.6 python3.6-venv sudo apt install -y python2.7 python2.7-venv alternativesでバージョン切り替え 定義 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.9 130 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.8 120 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.7 110 sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 10 切り替え方法 sudo update-alternatives --config python There are 5 choices for the alternative python (providing /usr/bin/python). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/bin/python3.9 130 auto mode 1 /usr/bin/python2.7 10 manual mode 2 /usr/bin/python3.6 100 manual mode 3 /usr/bin/python3.7 110 manual mode 4 /usr/bin/python3.8 120 manual mode 5 /usr/bin/python3.9 130 manual mode Press <enter> to keep the current choice[*], or type selection number: のように表示されるので、Selectionの番号を入れてEnterキーを押すと、そのバージョンが有効になります。 ※上記はPython 3.9が有効になっている状態です。 python -V Python 3.9.6 のように、選択したバージョンになっていればOKです。 venv (Python 3.x以降) ※ここでは、Python 3.9にnewestという環境を作る手順をまとめます。 Pythonのバージョンを選択 sudo update-alternatives --config python Python3.9のバージョンをSelectionの番号で指定します。 venv定義のディレクトリ作成 mkdir -p ~/venv/python3.9 環境を作成 ここでは、ホームディレクトリ以下全体で使用するnewestという環境名を定義します。 cd ~/venv/python3.9/ 次に、venv環境を作成します。 python -m venv newest 作成した環境を有効化 source ~/venv/python3.9/newest/bin/activate ※プロンプトの左端に(newest)のように環境名が表示されれば、正しく参照されています。
- 投稿日:2021-08-09T19:06:57+09:00
50代でデータサイエンティストを目指す
今まで25年ほどデータベースエンジニアとして生きてきたが 技術者として限界を感じた。 なんとかこの業界で生きていきたいので 「データサイエンティスト」を 50代にして目指してみる。 最終的には組織から離れてフリーランスで食っていきたい。 やるぞ〜 今日は以下のインプットを実施。 1.「AI・データ分析プロジェクトのすべて」を読み始めた。 AI・データ分析プロジェクトのすべて[ビジネス力×技術力=価値創出] 大城 信晃(監修・著者) https://www.amazon.co.jp/dp/4297117584/ref=cm_sw_r_tw_dp_2A575AG94EACNB0KZ3FC @amazonJPより 2.統計検定2級取得へ向けての勉強 3.英語の勉強 以下のアウトプットを実施。 COVID-19に関するオープンデータを用いた分析のツイート
- 投稿日:2021-08-09T18:32:49+09:00
*執筆途中【Python】アルゴリズム実装で多用する記法
はじめに Pythonでアルゴリズムを実装する際によく使う書き方や他言語であまり見かけない便利だった書き方の備忘録です。 注 ) 執筆途中です。ちょっとずつ増えます。 注 ) Pythonをほぼ趣味で使っております。業務で用いていたりアルゴリズムPythonガチ勢というわけではございません。ご了承を。 基本的に標準ライブラリのみを用いております。numpy などは用いません。 面倒だったのでまだスライス関係やリスト内包表記はありません。 演算子 インクリメント 他言語でよく見る i++ のようなインクリメント(/デクリメント)記法が存在しない。 i += 1 あくまでこのような記法。 掛け算と累乗 2 * 3 # print(2 * 3) => 6 アスタリスク1つ * で多言語同様、通常の掛け算。 2 ** 3 # print(2 ** 3) => 8 アスタリスク2つ ** で累乗を行う。 割り算 Pythonには浮動小数点を残す割り算と切り捨てる割り算が存在する。 5 / 2 # print(5 / 2) => 2.5 スラッシュ1本 / のみで浮動小数点を残す。 (演算結果が必ず float型 となる) 5 // 2 # print(5 // 2) => 2 スラッシュ2本 // で切り捨てを行う。 ( int / int の場合は演算結果が int型 に、 それ以外の場合は演算結果がすべて float型 に) リスト関連 Pythonのリストに関する for文の特徴として、インデックス番号ではなくリストの要素値そのものを周回することができるというものがある。 リスト周回の基本 (value) for val in data: val にリストの中身の各値が定義される。 例 ) data = [2, 4, 6, 8] : print(val) => 2, 4, 6, 8 リスト周回の基本 (index) for i in range(len(data)): i にリストの中身のインデックス番号が定義される。 インデックス番号は i = 0 から始まる。 例 ) data = [2, 4, 6, 8] : print(i) => 0, 1, 2, 3 [補足] len()関数 について len関数は、引数にリストを渡すとそのリストのサイズを返す。 例 ) data = [2, 4, 6, 8] : print(len(data)) => 4 [補足] range()関数 について range関数は、for文でシーケンスを生成する。 第一引数:start値 第二引数:[必須] end値 第三引数:step値 第一、第三引数は省略可能。これらを用いた使用方法は次項目等で。 リスト周回 (開始を指定) for i in range(3, len(data)): range()関数に第一引数を定義することで開始位置を指定可能。 開始位置はインデックス番号 i = 0 から始まる。 例 ) data = [2, 4, 6, 8] : print(i) => 1, 2, 3 これを用いることで for i in range(len(data)): for j in range(i, len(data)): のような計算量の高いソートで多用する書き方が可能。 リスト周回 (逆順value) for v in reversed(data): reversed()関数を用いることで v に逆順でリストの中身の各値を定義する。 例 ) data = [2, 4, 6, 8] : print(v) => 8, 6, 4, 2 リスト周回 (逆順index) for i in range(len(data), 0, -1): range()関数の第三引数を用いることで i に逆順でインデックス番号を定義する。 [注] 開始位置と終了位置が通常と異なることに注意。range(0, len(data), -1) とならない。 [注] 第一、第二引数を無視(省略やアンダースコア使用等)することは不可。 例 ) data = [2, 4, 6, 8] : print(i) => 3, 2, 1, 0 [補足] renge()関数に関する注意事項 range関数の第二引数は、終了index番号ではなく、長さ。 例) data = [2, 4, 6, 8] その1. renge(0, 3) print(i) => 0, 1, 2 その2. renge(0, 4) print(i) => 0, 1, 2, 3 その3. renge(0, len(data)) print(i) => 0, 1, 2, 3 このように、renge()関数でend値を指定するときは注意が必要。 ミス無くすべて周回したいときは、len()関数を用いるのが無難。 (Pythonのリストを参照する際に pointer0-value0--pointer1-value1--pointer2-value2 というイメージを持つと間違いづらくなる。) [追記] 友人が「これってCで言うこの書き方のbだよね」って言ってました。 for(i = a; i < b; i++) それだ。強い。 リスト周回 (value, index の両方使用) for i, v in enumerate(data): enumerate()関数を用いることで i にインデックス番号、v にリストの中身の各値を定義する。 [注] 第一返値:index、第二返値:value 例 ) data = [2, 4, 6, 8] : print(i, v) => 0 2, 1 4, 2 6, 3 8 制御構文 if文の比較演算子 in / not in if val in データ構造: if val not in データ構造: データ構造に値が入っている / 入っていないときに発火するif文記法。 in / not in 自体は比較演算子のため、if文以外でも使用可能。 [注] データ構造がリストの場合、O(n) となるらしく少し遅い。 while文による無限ループ while True: while文の条件式にboolean値の True を入れることで無限ループとなる。 Trueであればなんでも良いので、一応なんでも可能。1 など。 [補足] boolean値について Python は True と 1 が boolean における True。 また、False と 0 が False。 関数 関数アノテーション def 関数名(引数a:型, 引数b:型) -> 返り値の型: 関数の省略記法。ちょっとしたアロー関数。 綺麗なコード スワップ x, y = y, x C言語を始めとした通常の言語における tmp = x; x = y; y = tmp; のような値交換を1行で書くことができる。 チェーン -2 <= x <= 10 不等号の大なり小なりをまとめて書くことができる。 これを踏まえて右の記法は良くない。x <= -2 and x >= 10 入力関連 コマンドライン引数受付 import sys value1 = sys.argv[1] value2 = sys.argv[2] 標準ライブラリ sys をインポートすることで使用可能。 最初のコマンドライン引数が 1、次が 2 となる。0 始まりではない。 おわりに 通常ではあまり使わないけれどもアルゴリズム実装においては汎用性の高い記法等がございましたらぜひ教えてください。Pythonも奥が深いです。
- 投稿日:2021-08-09T18:19:40+09:00
vscを使って,anacondaで作った環境でデバッグする
困りごと jupoyter notebookを使ってたけど,デバッガーが使いづらいのでvscのデバッガー使おうと思ったけど,デバッグするときデフォルトのpythonで起動するから困ってた。 今回の解決策はワークスペースの中でanacondaから呼び出す環境を変える。 anacondaの環境変数はフォルダ変えてるだけだから,pythonを呼び出すpathを適切なやつにすればいい。 1 vscでワークスペースを作る 作れ。 ワークスペース一覧のフォルダに保存しとくと便利な気がするけど,もっといいやり方を知りたい。 2 ワークスペースを開いて,anacondaの環境のパスを設定する ここがどこからすればいいかよくわからなかった。 「ファイル」>「ユーザー設定」>「設定」と選ぶ。 「ワークスペース」のタブを選択する。 検索窓にpython.PythonPathと打ち込む。 下のpythonのところにこのワークスペースで使うAnacondaの環境のパスを教えてあげる。 なお,anacondaの環境のpathはコマンドラインとか立ち上げて,conda info -eで調べるのがいいと思う。 「フォルダ」ってタブもあって,フォルダごとに設定することもできるのでそれもまたいいと思います。
- 投稿日:2021-08-09T18:09:40+09:00
EC2にPython/Flask2を構築
社内でこれからPython/Flaskを教えていくのに、自分なりに整理していこうかと思います。 最終的にはPythonでマルチプロセスなんか記事にしていこうかな まずは練習で簡単な記事から EC2インスタンスの初期構築 ・インスタンスタイプ t2.micro AMI は Amazon Linux 2を選択 後の構成はデフォルト 最初はyumでupdate sudo yum -y update python3をインストール sudo yum -y install python3 Loaded plugins: extras_suggestions, langpacks, priorities, update-motd Package python3-3.7.10-1.amzn2.0.1.x86_64 already installed and latest version おこられた。 今時のAmazon Linux 2にはpython3が最初からインストールされてるっぽい python3 -V [ec2-user@ip-172-31-41-61 ~]$ python3 -V Python 3.7.10 [ec2-user@ip-172-31-41-61 ~]$ 3.7か...可もなく不可もない感じ flaskをインストール pip3 install flask Installing collected packages: MarkupSafe, Jinja2, typing-extensions, zipp, importlib-metadata, click, Werkzeug, itsdangerous, flask Successfully installed Jinja2-3.0.1 MarkupSafe-2.0.1 Werkzeug-2.0.1 click-8.0.1 flask-2.0.1 importlib-metadata-4.6.3 itsdangerous-2.0.1 typing-extensions-3.10.0.0 zipp-3.5.0 成功、成功...ん? Flaskがいつの間にか2.0.1にバージョンアップされてるw 1年ぐらいに更新がないと思っていたら油断した 本家のサイトFlaskを確認したら2.0.2がリリースされているし1系もバージョンアップされてるね。更新内容は後で確認しますか flask用のファイルを作成して起動 ・今回はopt配下にec2-userで作成していく cd /opt sudo mkdir flask sudo chown -R ec2-user:ec2-user flask cd flask mkdir hoge cd hoge ・hogeディレクトリの配下にapp.pyを作成 ver2になってるので本家のサイトFlaskサイトを参考に進めてみると app.py from flask import Flask app = Flask(__name__) @app.route('/') def index(): return "世界のみなさん、こんにちは" ・ファイルを作成したの、適当に動かしてみる flask run 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) ・あれ動いたw curlを投げてみますか curl -i http://127.0.0.1:5000/ HTTP/1.0 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 39 Server: Werkzeug/2.0.1 Python/3.7.10 Date: Mon, 09 Aug 2021 08:44:40 GMT 世界のみなさん、こんにちは ・確かに動いてるぞ どこでapp.pyを判断したんだよ。と思って本家のサイトを見てみると app.py か wsgi.py が存在していると自動で判断して起動してくれるみたい Flaskが2にバージョンアップされて機能が追加されているようなので後で検証してみます
- 投稿日:2021-08-09T17:58:18+09:00
Series データ・インデックスのみを取り出す
概要 作成したSeriesのデータ値のみ、インデックス値のみを取り出す方法がある。 データ値の場合は、.valuesとし、インデックスの場合は、 .indexとする。 import pandas as pd fruits = ["apple", "orange", "banana", "straberry", "kiwifruit"] data = [10, 5, 1, 2, 3] series = pd.Series(data, index = fruits) #データ値 series_values = series.values print(series_values ) print() #インデックス値 series_index = series.index print(series_index) データ値の出力 インデックス値の出力
- 投稿日:2021-08-09T17:48:17+09:00
tkinter 使い方のまとめ(基本編)
はじめに Python tkinterモジュールについての使い方が各所に散らばっていて、参照したいときにあちこち見て回らないといけないことが多かったので、纏めておこうと思い至ったのでこの記事を執筆する。 記事の内容については逐次更新する インストールについて 割愛する。 実行環境 ・macOS High Sierra 10.13.6 ・Python 3.8.8 ・tkinter 8.6.10 ・エディタ Atom ・AtomはデフォルトでPythonの実行をサポートしていないので、必要なパッケージをダウンロードする必要がある tkinterのバージョン確認について 筆者の場合はもともとPython3の環境をAnacondaで構築していたので、確認にあたっては以下のように実行してtkのバージョンを確認すれば良い。 $ conda list (略) tk 8.6.10 (略) Pythonプログラムから version-check.py import tkinter tkinter._test() として実行すると、以下のようなウィンドウが立ち上がる。Tcl/Tkのバージョンは8.6であると確認できる。 tkinterはTk GUIツールキットに対するPythonインターフェイスで、Tk自体がPythonの一部を成すものではない。 部品設計 ウィンドウを作る window.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") ##ウィンドウの表示 root.mainloop() 上のようにすると、以下のように中身を何も持たないウィンドウが作成される。 以降では「root=Tk()」を定義したあとから「root.mainloop()」でウィンドウを表示するまでに、その中身を定義していく ラベルを表示する label.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") label = ttk.Label(root,text ='Hello Python!') label.pack() #ウィンドウの表示 root.mainloop() 以下のように表示される。 オブジェクト名 = ttk.Label(親要素,text ='表示したい内容') としてラベルを定義し、 オブジェクト名.pack() とすることで作成したオブジェクトを配置している。「オブジェクト名.pack()」と「root.mainloop()」以降に書いた場合には、ラベルは表示されない。 入力ボックスを作る entry.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") #オブジェクトの定義 label = ttk.Label(root,text ='Hello Python!') entry = ttk.Entry(root) #レイアウト label.pack() entry.pack() #ウィンドウの表示 root.mainloop() ラベルを作成したときとあまり変わらないかもしれない。以上のようにすると簡単に入力ボックス(Entryウィジェット)が作成される。 ボタンを作る button.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") #オブジェクトの定義 label = ttk.Label(root,text ='Hello Python!') entry = ttk.Entry(root) button = ttk.Button(root,text = 'OK') #レイアウト label.pack() entry.pack() button.pack() #ウィンドウの表示 root.mainloop() やはりラベルを作成したときと変わらない。ただし、ここで作成したボタンは今の所なんの機能も持たない 各オブジェクトを掘り下げて見る オブジェクトの作り方の基礎がだいたいわかってきたので、各ウィジェットについてもう少し細かく見ていく Labelウィジェット Label-2.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") #オブジェクトの定義 rock_climbing_woman = PhotoImage(file='./images/rock_climbing_woman.png') label = ttk.Label( root, text = 'あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。 またそのなかでいっしょになったたくさんのひとたち、ファゼーロとロザーロ、羊飼のミーロや、顔の赤いこどもたち、地主のテーモ、山猫博士のボーガント・デストゥパーゴなど、いまこの暗い巨きな石の建物のなかで考えていると、みんなむかし風のなつかしい青い幻燈のように思われます。では、わたくしはいつかの小さなみだしをつけながら、しずかにあの年のイーハトーヴォの五月から十月までを書きつけましょう。', foreground = '#1155ee', padding = (5,10), font = ('Times New Roman',20), wraplength = 400, image = rock_climbing_woman, compound = 'right') entry = ttk.Entry(root) button = ttk.Button(root,text = 'OK') #レイアウト label.pack() entry.pack() button.pack() #ウィンドウの表示 root.mainloop() さて、Labelウィジェットのオプションが増えてきたので改行して適切なインデントを与えて記述していく。 foreground = '#16進数によるテキストのカラーの選択' paddiing = (パディング値の設定) padding = (引数1) :上下左右すべて同じパディングをとる padding = (引数1,引数2):左右のパディングを引数1だけ、上下のパディングを引数2だけとる padding = (引数1,引数2,引数3,引数4):左、上、右、下の順に引数1からパッディングをとる font = (フォント名,サイズ) wraplength = :テキストの折返しをピクセルで指定している。上の場合は400ピクセルに達すると改行される image = イメージオブジェクト:これより前でPhotoImageにて、相対的に指定したイメージを引用している compound = 'right':イメージを表示する位置を指定している Entryウィジェット Entry-2.py from tkinter import * from tkinter import ttk root = Tk() root.title("はじめてのtkinter") #オブジェクトの定義 entry_1 = ttk.Entry( root, justify = 'center') entry_2 = ttk.Entry( root, #justify = show = '★') button = ttk.Button(root,text = 'OK') #レイアウト label.pack() entry_1.pack() entry_2.pack() button.pack() #ウィンドウの表示 root.mainloop() Entryウィジェットについてはそれほど掘り下げるべき項目はない。強いて言えば「justify」で、入力された文字列の水平位置をどうするのかということと、パスワードを取得するときに、入力されたパスワードの文字列がわからないように変わりに表示する文字を指定することについてくらいだ。それ以外のことについては実践編で記述する。 具体的な実践例 上の例では、OKボタンを押しても何も起こらない。何も定義していないのだから当然だ。 ときにはOKボタンを押してEntryウィジェットに入力された値を取得して何かやりたいことがあるかもしれない。では、どのようにすれば、Entryウィジェットに入力された文字を取得することができるだろうか? Entryウィジェットに入力された文字列を取得する get_Entry.py from tkinter import * from tkinter import ttk #関数定義 def get_id_pass(): id = text_1.get() password = text_2.get() print('ID:',id) print('Pass:',password) def set_clear(): text_1.set('') text_2.set('') root = Tk() root.title("はじめてのtkinter") #オブジェクトの定義 label_1 = ttk.Label(root,text='ユーザ名') text_1 = StringVar() entry_1 = ttk.Entry(root,textvariable=text_1) label_2 = ttk.Label(root,text='パスワード') text_2 = StringVar() entry_2 = ttk.Entry(root,show='*',textvariable=text_2) button_1 = ttk.Button(root,text = 'OK',command=lambda:get_id_pass()) button_2 = ttk.Button(root,text = 'Clear',command=lambda:set_clear()) button_3 = ttk.Button(root,text = 'Quit',command=quit) #レイアウト label_1.grid(row=0,column=0) entry_1.grid(row=0,column=1) label_2.grid(row=1,column=0) entry_2.grid(row=1,column=1) button_1.grid(row=2,column=0) button_2.grid(row=2,column=1) button_3.grid(row=2,column=2) #ウィンドウの表示 root.mainloop() レイアウトについては.packではなく.grid()にて指定した。エクセルの表の位置を指定するように縦・横の位置関係でウィジェットを配置することを手助けしてくれる。 Entryウィジェットを定義する前に、StringVar()型のオブジェクトを定義している。これをEntryウィジェットのtextvariable属性の値として指定している text_1 = StringVar() entry_1 = ttk.Entry(root,textvariable=text_1) 次にボタンを定義している。command=で、そのボタンが押されたときに実行したい関数を呼び出している。 button_1 = ttk.Button(root,text = 'OK',command=lambda:get_id_pass()) button_2 = ttk.Button(root,text = 'Clear',command=lambda:set_clear()) button_3 = ttk.Button(root,text = 'Quit',command=quit) get_id_pass()関数 id = text_1.get() とすることで、entry_1の値が可変となったtext_1の値を取得してidに代入している。 def get_id_pass(): id = text_1.get() password = text_2.get() print('ID:',id) print('Pass:',password) 以上のように、Entryウィジェットに入力された値を取得できた。 次にClearボタンの呼び出し関数を見てみると .set('') として、入力された文字列を長さのない文字列に置き換えているので、このボタンを押すと入力がすべてクリアーされることがわかる。また、button_3のコマンドは、プログラムを終了させるものである。 def set_clear(): text_1.set('') text_2.set('') 参考リンク ・TkDocs ・TkinterによるGUIプログラミング
- 投稿日:2021-08-09T17:13:48+09:00
PythonでbitFlyerのAPIを利用して資産残高の取得、新規注文、Tickerの取得を行う方法
Pythonで暗号資産(仮想通貨)の自動売買システムを作成した際に、bitFlyerのAPIを使ったので、利用方法についてまとめました。 bitFlyer について bitFlyerは日本の暗号資産の取引所です。 Cryptowatchというサイトにも対応している取引所なので、自動売買を行うのであれば使いやすい取引所かと思います。 今回利用するAPIについて bitFlyer LightningのHTTP APIとRealtime API を使用します。 こちらがAPIのドキュメントですが、日本語にも対応されているのでとても見やすいです。 今回は以下のAPIの実行方法をまとめています。 * 取引所のアカウントは既に作成されていることを前提として進めます。 APIキーの作成 資産残高を取得 - HTTP API 新規注文を出す - HTTP API Ticker情報を取得 - Realtime API 1.APIキーの作成 こちらのページから「新しいAPIキーを作成」をクリック 次のような画面が表示されるので、必要なチェックボックスにチェックを入れます。 今回は入出金はAPIで行わないので、全てチェックを外しています。 作成されたAPIキーの情報はGitHubなどにあげてしまわないように注意してください。 2.資産残高を取得 pybitflyer をインストール pip install pybitflyer main.py import logging import sys from bitflyer import APIClient logging.basicConfig(level=logging.INFO, stream=sys.stdout) if __name__ == "__main__": API = APIClient('API Key', 'API Secret') balance = API.get_balance('JPY') print(balance.__dict__) balance = API.get_balance('BTC') print(balance.__dict__) bitflyer.py import logging import pybitflyer logger = logging.getLogger(__name__) class Balance(object): def __init__(self, currency, available): self.currency = currency self.available = available class APIClient(object): def __init__(self, api_key, api_secret): self.api_key = api_key self.api_secret = api_secret self.client = pybitflyer.API(api_key=api_key, api_secret=api_secret) def get_balance(self, currency) -> Balance: try: resp = self.client.getbalance() balance = (list(filter(lambda x: x['currency_code'] == currency, resp)))[0] except Exception as e: logger.error(f'action=get_balance error={e}') raise currency = balance['currency_code'] available = balance['available'] return Balance(currency, available) 上記のコードを記述して、main.py を実行していただければ、指定した通貨の残高が取得できるかと思います。 get_balanceに指定する通貨はJPYやBTC以外でも取得可能です。 参考リンク:HTTP API 資産残高を取得 3.新規注文を出す main.py import logging import sys from bitflyer import APIClient logging.basicConfig(level=logging.INFO, stream=sys.stdout) if __name__ == "__main__": API = APIClient('API Key', 'API Secret') resp = API.send_order("BTC_JPY", "MARKET","BUY", 0.001, 10) bitflyer.py import logging import time import pybitflyer logger = logging.getLogger(__name__) class APIClient(object): def __init__(self, api_key, api_secret): self.api_key = api_key self.api_secret = api_secret self.client = pybitflyer.API(api_key=api_key, api_secret=api_secret) def send_order(self, product_code, child_order_type, side, size, minute_to_expire): try: resp = self.client.sendchildorder(product_code=product_code, child_order_type=child_order_type, side=side, size=size, minute_to_expire=minute_to_expire) except Exception as e: logger.error(f'action=send_order error={e}') raise time.sleep(1) order_id = resp['child_order_acceptance_id'] order = self.wait_order(order_id) if not order: logger.error('action=send_order error=timeout') raise return order def wait_order(self, order_id): count = 0 timeout_count = 10 while True: order = self.get_order(order_id) if order['child_order_state'] == 'COMPLETED': return order time.sleep(1) count += 1 if count > timeout_count: return None def get_order(self, order_id): try: resp = self.client.getchildorders(product_code='BTC_JPY', child_order_acceptance_id=order_id) except Exception as e: logger.error(f'action=get_order error={e}') raise return resp[0] send_order関数 実際に新規注文を出して、API の受付IDを受け取ります。 今回は成行注文を行いましたが、APIは指値注文にも対応しています。 指値注文を行う際はchild_order_typeにLIMITを設定して、priceに価格を設定することで行えます。 wait_order関数 注文が完了になったかどうかを1秒ごとに確認しています。 完了になった場合に、後述のget_orderで取得した情報を返します。 get_order関数 新規注文を行った際に取得した受付IDの注文情報を取得します。 参考リンク:HTTP API 新規注文を出す 参考リンク:HTTP API 注文の一覧を取得 4.Ticker情報を取得 websocket-clientをインストール pip install websocket-client main.py import logging import sys from bitflyer import RealtimeAPI logging.basicConfig(level=logging.INFO, stream=sys.stdout) if __name__ == "__main__": url = "wss://ws.lightstream.bitflyer.com/json-rpc" channel = "lightning_ticker_BTC_JPY" json_rpc = RealtimeAPI(url=url, channel=channel) bitflyer.py import logging import websocket import json import pybitflyer logger = logging.getLogger(__name__) class RealtimeAPI(object): def __init__(self, url, channel): self.url = url self.channel = channel self.connect() def connect(self): self.ws = websocket.WebSocketApp(self.url,header=None,on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close) self.ws.run_forever() logger.info('Web Socket process ended.') """ Below are callback functions of websocket. """ # when we get message def on_message(self, ws, message): resp = json.loads(message)['params']['message'] self.set_realtime_ticker(resp) # when error occurs def on_error(self, ws, error): logger.error(error) # when websocket closed. def on_close(self, ws): logger.info('disconnected streaming server') # when websocket opened. def on_open(self, ws): logger.info('connected streaming server') input_data = json.dumps( {'method' : 'subscribe', 'params' : {'channel' : self.channel} } ) ws.send(input_data) def set_realtime_ticker(self, resp): timestamp = resp['timestamp'] product_code = resp['product_code'] bid = float(resp['best_bid']) ask = float(resp['best_ask']) volume = float(resp['volume']) ticker = [product_code, timestamp, bid, ask, volume] logger.info(f'ticker={ticker}') WebSocketを使用して、Realtime APIの情報を取得しています。 参考リンク:Realtime API Ticker 以上がbitFlyerのAPIを利用するコードの紹介となります。 作成した自動売買の運用記録などはnoteに投稿しているので、もしご興味があればご覧ください! note
- 投稿日:2021-08-09T17:01:36+09:00
PythonからVOICEVOXを使う
VOICEVOX 商用利用可能なAIベースの音声合成ソフトウェア ボイボと呼びましょう。 APIなのかと思ったら、ダウンロードして.exeを実行すると、ローカルにサーバーが立つみたい。 curlでも利用できるとのことなので、pythonで叩いてみた。 あらかじめ上記リンクから、VOICEVOX本体をダウンロードして実行しておいてください。 実行環境 当方の実行環境は以下 CPU: i7 8550u GPU: Nvidia MX150 Python = 3.9.5 CPUで実行しました。 ソースコード 合成されるファイルは映像系のサンプルレート(モノラルの24000[Hz])なので注意 voicevox.py import json import requests import wave def generate_wav(text, speaker=1, filepath='./audio.wav'): host = 'localhost' port = 50021 params = ( ('text', text), ('speaker', speaker), ) response1 = requests.post( f'http://{host}:{port}/audio_query', params=params ) headers = {'Content-Type': 'application/json',} response2 = requests.post( f'http://{host}:{port}/synthesis', headers=headers, params=params, data=json.dumps(response1.json()) ) wf = wave.open(filepath, 'wb') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(24000) wf.writeframes(response2.content) wf.close() if __name__ == '__main__': text = 'こんにちは!' generate_wav(text) 感想 上記のPythonからしかためしていないので、GUIからだと解決できる機能があるかもですが、以下が簡単な感想です。 合成にはそこそこ時間がかかるので、リアルタイムでの合成には向いていなさそう(マシンスペックによる?) 英語をアルファベット読みされてしまうことがある(言語指定とかで回避できる?) 音声自体の品質は高い方だと思う 日本語にて、あらかじめ合成しておいて使うような用途であれば、現時点でベストプラクティスかもしれない 音声合成はいつもライセンス周りで困るので、ラピットプロトタイピングできるようになるのはありがたい 他の音声合成でもありがちだが、合成されたファイルは、音声部分のみ再生するとプツっと切れてしまう。 "、"や"。"で無音を置けたり、SSMLでpauseを入れたりできるとよくなりそう。
- 投稿日:2021-08-09T16:54:02+09:00
【AWS】Secrets Manager でクレデンシャルを管理する
qiita への投稿を取得して twitter に投稿するプログラムを作ったのですが、 access key や token の管理にAWS Secrets Managerを使用したので、シークレットキーの作成から python での実装までを記載します。 シークレットキーの作成 AWS CLI で Secrets Manager で管理するキーと値のペアを登録してみます。キーと値のペアは json ファイルに記載して、 cli 実行時に指定するようにします。 creds.json { "CONSUMER_KEY": "xxx", "CONSUMER_SECRET": "xxx", "ACCESS_TOKEN": "xxx", "ACCESS_TOKEN_SECRET": "xxx", "QIITA_TOKEN": "xxx" } create-secret コマンドで secret を作成します。 > aws secretsmanager create-secret --name bot-credential --secret-string file://creds.json 出力: { "ARN": "arn:aws:secretsmanager:ap-northeast-1:xxx:secret:bot-credential-xxx", "Name": "bot-credential", "VersionId": "xxx-xxx-xxx-xxx-xxx" } 作成した内容を確認してみます。 > aws secretsmanager get-secret-value --secret-id bot-credential 出力: { "ARN": "arn:aws:secretsmanager:ap-northeast-1:xxx:secret:bot-credential-xxx", "Name": "bot-credential", "VersionId": "xxx-xxx-xxx-xxx-xxx", "SecretString": "{\n \"CONSUMER_KEY\": \"xxx\",\n \"CONSUMER_SECRET\": \"xxx\",\n \"ACCESS_TOKEN\": \"xxx-xxx\",\n \"ACCESS_TOKEN_SECRET\": \"xxx\",\n \"QIITA_TOKEN\": \"xxx\"\n}", "VersionStages": [ "AWSCURRENT" ], "CreatedDate": "2021-08-08T01:28:20.349000+09:00" } シークレットキーが作成されています。 コンソール画面からも確認できます。 secret を作成すると、コンソール画面でサンプルソースコードを確認することができます。 今回はサンプルコードをそのまま利用します。 secrets_manager.py # Use this code snippet in your app. # If you need more information about configurations or implementing the sample code, visit the AWS docs: # https://aws.amazon.com/developers/getting-started/python/ import boto3 import base64 from botocore.exceptions import ClientError def get_secret(): secret_name = "bot-credential" region_name = "ap-northeast-1" # Create a Secrets Manager client session = boto3.session.Session() client = session.client( service_name='secretsmanager', region_name=region_name ) # In this sample we only handle the specific exceptions for the 'GetSecretValue' API. # See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html # We rethrow the exception by default. try: get_secret_value_response = client.get_secret_value( SecretId=secret_name ) except ClientError as e: if e.response['Error']['Code'] == 'DecryptionFailureException': # Secrets Manager can't decrypt the protected secret text using the provided KMS key. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InternalServiceErrorException': # An error occurred on the server side. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidParameterException': # You provided an invalid value for a parameter. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'InvalidRequestException': # You provided a parameter value that is not valid for the current state of the resource. # Deal with the exception here, and/or rethrow at your discretion. raise e elif e.response['Error']['Code'] == 'ResourceNotFoundException': # We can't find the resource that you asked for. # Deal with the exception here, and/or rethrow at your discretion. raise e else: # Decrypts secret using the associated KMS CMK. # Depending on whether the secret is a string or binary, one of these fields will be populated. if 'SecretString' in get_secret_value_response: secret = get_secret_value_response['SecretString'] else: decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary']) # Your code goes here. return secret 実装 qiita から記事を取得して twitter に投稿するプログラムは別記事に書きましたのそちらをご覧ください。 環境変数で取得していた secret key や token を Secrets Manager から取得するように修正します。secrets_manager.py の get_secret_value_response['SecretString'] の値は辞書型では文字列型なので、文字列を辞書型に変換する ast モジュールの literal_eval を使用します。 https://dev.classmethod.jp/articles/secrets_manager_tips_get_api_key/ tweet.py +import ast import tweepy import qiita import os +import secrets_manager -consumer_key = os.environ['CONSUMER_KEY'] -consumer_secret = os.environ['CONSUMER_SECRET'] -access_token = os.environ['ACCESS_TOKEN'] -access_token_secret = os.environ['ACCESS_TOKEN_SECRET'] +secret = ast.literal_eval(secrets_manager.get_secret()) +consumer_key = secret['CONSUMER_KEY'] +consumer_secret = secret['CONSUMER_SECRET'] +access_token = secret['ACCESS_TOKEN'] +access_token_secret = secret['ACCESS_TOKEN_SECRET'] auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth) ...
- 投稿日:2021-08-09T16:53:17+09:00
matplotlibの上下異なるエラーバー付きグラフ
概要 matplotlibで上下異なるエラーバー付きグラフを描く。 プログラム import matplotlib.pyplot as plt x = [1,2,3,4,5] y = [10,20,30,40,50] yerr = [[2, 1, 3, 3, 2], [4, 3, 4, 2, 4]] plt.errorbar(x, y, yerr,label="", capsize=3, fmt='.', markersize=10, ecolor="black", elinewidth=0.5, markeredgecolor="black", color="w") plt.plot(x, y, label="sample value", color="k", linewidth=1) plt.legend(loc="lower right", fontsize=13) plt.xlabel("x sample", size=13) plt.ylabel("y sample", size=13) plt.grid(linestyle="--") errorbar エラーバーはplt.errorbar()で出力される。 x, yはplt.plot()と同じ値を出力し、yerrにエラーバーに表示したい値を入力する。 yerrに入力する値が二次元の場合、 1次元目が中心アイコンからエラーバーの下まで距離の値、 2次元目が中心アイコンからエラーバーの上まで距離の値 になる。 参考 qiitaのerrorbar記事: errorbarの中心のアイコンfmt: 以下のリンクのformat setting errorbarのlineの太さなど:
- 投稿日:2021-08-09T16:18:11+09:00
【誰でも/無料で/簡単に】Google Colaboratoryの使い方
目次 1. はじめに 2. Google Colaboratoryとは 3. 動かしてみよう 4. セルの追加 5. セルのその他操作 5.1 セルの並び替え 5.2 セルの削除 6. ツールバーの操作(ファイルの保存および出力) 7. ツールバーの操作 8. さいごに 1. はじめに 機械学習環境の構築で、エラーで動かず...ということはよくあります。 簡単に機械学習環境を用意したい!という方のために 環境構築が不要、ブラウザ上で利用できるGoogle Colaboratoryを紹介します。 2. Google Colaboratoryとは Colaboratory(略称: Colab)は、ブラウザから Python を記述、実行できるサービスです。次の特長を備えています。 ・環境構築が不要 ・GPU への無料アクセス ・簡単に共有 引用: Colaboratory へようこそ こちらの記事に詳しい内容がまとまっています。 3. 動かしてみよう 前提 Googleアカウントにログインしていること。 ブラウザにChromeを使用していること。※推奨 簡単なのでまずは動かしてみましょう。 Google Colaboratoryにアクセスします。 ページ左上の[ファイル]-[ノートブックを新規作成]を選択します。 新しいウィンドウでノートブックが開きます。この画面上でノートブックの操作を行います。 セルと呼ばれる部分にコードを入力します。 サンプルコード print("Hello Google Colaboratory.") 5. 実行ボタンを押下します。 6. 結果が出力されます。 4. セルの追加 1. ページ左上かマウスカーソルをセルの下部分に合わせると表示される[+ コード]をどちらか押下します。 ※テキストセルの場合は[+ テキスト]を押下します。 2. セルが追加されます。 5. セルのその他操作 赤枠内のアイコンを押下することでセルの各操作を行うことができます。 ※画像はコマンドセルのアイコンです。 この記事では2つの操作を紹介します。 5.1 セルの並び替え 1. 並び替えを行いたいセルの右上部[↑]を押下します。 ※セルを下に移動させたい場合は[↓]を押下します。 2. セルが移動されます。 5.2 セルの削除 1. 削除を行いたいセルの右上部ゴミ箱アイコンを押下します。 2. セルが削除されます。 6. ツールバーの操作(ファイルの保存および出力) 1. Googleドライブに保存する場合は[保存]を押下します。 ローカルPCに保存する場合は[ダウンロード]にカーソルを合わせます。 2. 2種類の形式から選べるのでどちらかを選択します。 ※ローカルPCに保存する時のみ 7. ツールバーの操作 [ファイル]タブやその他タブにもいくつかの操作メニューがあります。 タブ 説明 ファイル ノートブックの操作(作成、保存、出力など) 編集 各セルの操作 表示 ノートブックの表示方法を設定 ランタイム セルの実行 ツール ショートカットの設定 ヘルプ FAQなどのヘルプ 8. さいごに 制約はありますが、無料かつ簡単にGPU/TPUを利用できる機械学習環境が用意できます。 動作環境についても、ブラウザが動作すればよいです。 今回紹介した機能は一部ですので、実際に触ってみてください。
- 投稿日:2021-08-09T16:13:27+09:00
Pythontexでランダムに単語テストを作成する
はじめに pythontexに関する記述はあまり見ないので、それの説明。 pythontexっていうのは、簡単にいうとplatex上でpythonコードが動くよっていう話。 古のplatexでも、lualatexでも動くからうまく使えれば便利。 今日はこれを利用して単語テストを作ってみたよ。 満たしたい条件は 1.あらかじめ単語リストを作っておき、そこからランダムに選んで並べて問題を作る 2.答えも一緒に作れるようにする 3.コマンドみたいに呼べれば最高 の3点。 では行ってみよう。 pycodeの部分 まずはlatex上で動かす、pythonのコード部分の作成。 読み込む単語リストの形式はこんな感じ。 word_list.csv(一部) 51 money 金 apple りんご table 机 light 光 left 左 right 右 one 1 two 2 three 3 text テキスト free 自由 busy 忙しい beautiful 美しい read 読む break 壊す eat 食べる wash 洗う hand 手 old 年を取った young 若い window 風 banana バナナ cook 料理をする . . . why なぜ 最初の1行目が読み込む単語数。二行目から単語が一行ずつあって、英語と日本語の間はタプルで区切ってある。 これを読み込んでいく。 test.tex \begin{pycode} #!/usr/bin/env python3 # -*- coding: utf-8 -*- import random class WordTest: def __init__(self,filename,first,last,questions): self.filename = filename # 読み込むファイル名 self.first = first # 問題番号最初 self.last = last # 問題番号最後 self.questions = questions # 問題数 self.tango_list = [] # ファイルの単語データはここにいれる with open(filename,encoding="shift_jis") as f: num = f.readline() # 単語の個数 for i in range(int(num)): text = f.readline() # 一行ずつ読み込み texts = text.split("\t") # 文字列をタブで区切ってリスト化 self.tango_list.append(texts) def random_test(self): '''呼び出されるたびに、ランダムのテストを出力''' self.tango_num = list(range(self.first,self.last+1)) # 元の問題番号は1初めに調整 self.tango_num = random.sample(self.tango_num,len(self.tango_num)) # ランダムに並び替える print(r"\twocolumn[{\large 単語テスト} \\ ]") print(r"\begin{tabular}{llr}") for i,q in enumerate(self.tango_num[:self.questions]): #先頭から問題数分だけの単語を表示 if (i+1) % 51 == 0 : # 50問で折り返し処理 print(r"\end{tabular}") print(r"\newpage") print(r"\begin{tabular}{llr}") print(str(i+1) + " & ") print(self.tango_list[q-1][0] + " & ") # 元も問題番号は1から print(r"( \hspace{50pt} )"+ r"\\") print(r"\end{tabular}") def ansewr(self): '''現在保存されているランダムテストの答えを出力''' print(r"\twocolumn[{\large 答え}]") print(r"\begin{tabular}{ll}") for i,q in enumerate(self.tango_num[:self.questions]): if (i+1) % 51 == 0 : print(r"\end{tabular}") print(r"\newpage") print(r"\begin{tabular}{llr}") print(str(i+1) + " & ") print(self.tango_list[q-1][1] + r"\\") print(r"\end{tabular}") # newcommandがしやすいようにカプセル化する def make_wordtest(objname,filename,first,last,questions): globals()[objname] = WordTest(filename,first,last,questions) return "" def random_test(objname): globals()[objname].random_test() return "" def ansewr(objname): globals()[objname].ansewr() return "" \end{pycode} これで読み込む。使用するライブラリはrandomだけ。 WordTestクラスからオブジェクトを作って、random_testでテストの本体作成、ansewrで答えを作成。 latexのnewcommandを利用しやすくするために、関数でカプセル化をしておいた。 ちなみにglobals()[文字列]は文字列のグローバル変数を作成できる禁じ手。 print(r"")はエスケープシーケンスを無視させて出力させる。print出力がtexのデータ扱いされるから、pythontexではよく使う。 関数化された部分がreturn ""なのはこれをしとかないと、勝手にNoneが出力されるから。 tex本体 pycodeの部分が出来たので、texデータ本体の作成。 word_test.tex \documentclass[a4j,twocolumn,9pt]{ltjsarticle} % PythonTeX \usepackage[]{pythontex} \input{test} \newcommand{\readlist}[2]{\py{make_wordtest("#1","#2",1,51,51)}} \newcommand{\maketest}[1]{\py{random_test("#1")}} \newcommand{\makeansewr}[1]{\py{ansewr("#1")}} % %余白調整 \usepackage[margin=15mm]{geometry} \title{} \author{tukiusa} \date{\today} \pagestyle{empty} \begin{document} \readlist{try}{word_list.csv} \maketest{try} \newpage \makeansewr{try} \end{document} こんな感じで完成。 使ったのはLuaLatex。なので最初のdocumentclassの部分がltjsarticleになってる。 文字の大きさは9ptにしてある。この余白の設定だとちょうどA4半分に50問のせれる感じになる。 そして、pythonが使えるようにpythontexを読み込んで、 input{test}でpycodeの本体が記入されてるtest.texを読み込み。 さっきカプセル化した関数をtex上で呼べるようにnewcommandで設定。 \readlistでtryっていうオブジェクトをword_list.csvから作成。 \maketestでテスト本体を出力。 \makeansewrで答えを出力。 あとは、これらをdocument環境内で呼んで完成!! このtexファイルをlualatex → pythontex → lualatexの順で処理をすれば、 とちゃんと出力されてる さいごに 結構簡単に要件を満たすことが出来た。別に単語テストだけではなく、問題をランダムにしたテストの作成ならいろんな場所で使えると思うのでぜひ、興味があれば試してみてください。 エクセルとかlatexだけでも似たようなことは出来るっぽいけど、コードの可視性、応用が効くとかの長所はあるはず・・・
- 投稿日:2021-08-09T15:09:15+09:00
PyGrADSのインストール・使い方
はじめに 気象・大気などの地球科学系の研究室なら、よく使うGrADS。 GrADSの拡張ライブラリであるpyGrADSが、python 3系に対応してライブラリとして使えるようになったらしい。 2021-07-05 Arlindo da Silva dasilva@opengrads.org, Version 3.0.b1 * First major revision support Python 3 * Python 2 support has been dropped * pygrads and ipygrads.py have been updated to latest IPython (ChangeLogより) 端くれ大学院生として、誰かの役に立てれば幸いです。 (レスポンスもらえれば、書き足していきます。LGTMください(笑)) 動作確認環境 python 3.8.5 pygrads 3.0.b1 インストール この章の目標:pyGrADSのインストールを行い、.pyファイルで使えるようにする ダウンロード こちらにアクセスし、 赤枠をクリックし、インストールを開始してください。 解凍 ダウンロードしたファイルは圧縮ファイル(.tar.gz)ですので、解凍を行います。 今回は、Linux環境で行いましたので、 tar -zxvf pygrads-3.0.b1.tar.gz で解凍しました。 Windows環境ですと、lhaplusとかで(多分)いけると思います。(未確認) 解凍が完了すると、フォルダーが作成されます。 インストール ターミナルなどを用いて、フォルダーに移動してください。 ex.解凍済みフォルダーがデスクトップにある時 cd Desktop/pygrads-3.0.b1 移動ができたら、 python setup.py install でインストールを行ってください。 インストールが完了すれば、 test.py import grads でインポートができるはずです。 使い方 要望が多ければ作ります。 LGTMください。(笑) 最終更新日 2021/08/09
- 投稿日:2021-08-09T14:54:16+09:00
にゃんこスイーパー
pythonで書かれたterminalのにゃんこスイーパーです。 nyankosweeper.py #!/usr/bin/python3 import curses import random import locale import select import sys xsize = 40 ysize = 23 vvram = [[0 for i in range(ysize)] for j in range(xsize)] mask = [["#" for i in range(ysize)] for j in range(xsize)] itod = " 123456789" ac = "#??" def getkey(): c = stdscr.getkey() if c == 'KEY_UP': c = '8' elif c == 'KEY_DOWN': c = '2' elif c == 'KEY_RIGHT': c = '6' elif c == 'KEY_LEFT': c = '4' return c def putchar(x, y, a): stdscr.addstr(y, x*2, a, curses.A_REVERSE) def putcharr(x, y, a): stdscr.addstr(y, x*2, a) def putmines(n): for i in range(n): while(True): (x, y) = (random.randint(0, xsize-1), random.randint(0, ysize-1)) if (vvram[x][y] == 0): vvram[x][y] = '*' break # def calccell(x, y): if (vvram[x][y] == 0): cnt = 0 for dx in range(-1, 2): for dy in range(-1, 2): if x+dx >= 0 and y+dy >= 0 and x+dx < xsize and y+dy < ysize: cnt += 1 if vvram[x+dx][y+dy] == '*' else 0 vvram[x][y] = cnt return(cnt) def reveal(x, y): if mask[x][y] != ' ': mask[x][y] = ' ' if vvram[x][y] == 0: for dx in range(-1, 2): for dy in range(-1, 2): if x+dx >= 0 and y+dy >= 0 and x+dx < xsize and y+dy < ysize: reveal(x+dx, y+dy) def cursor(x, y): putcharr(x, y, retchar(x, y)) def printmines(): for x in range(xsize): for y in range(ysize): c = vvram[x][y] if c == '*': putchar(x, y, "?") elif (c >= 0 and c <= 9): putchar(x, y, itod[c]) def retchar(x, y): m = mask[x][y] if m in ac: return(m) else: c = vvram[x][y] if c >= 0 and c <= 9: return(itod[c]) def drawchar(x, y): putchar(x, y, retchar(x, y)) def drawscreen(): for x in range(xsize): for y in range(ysize): drawchar(x, y) def calcfield(): for x in range(xsize): for y in range(ysize): calccell(x, y) def endcheck(): c = 0 for x in range(xsize): for y in range(ysize): c += 1 if mask[x][y] in ac else 0 return(c == mines) def mainloop(): (x, y) = (xsize//2, ysize//2) while True: drawscreen() if endcheck(): printmines() putchar(0, 23, "You win. hit key") getkey() return(0) cursor(x, y) stdscr.refresh() c = getkey() if c == '4': x = x-(1 if x != 0 else 0) elif c == '6': x = x+(1 if x != xsize-1 else 0) elif c == '8': y = y-(1 if y != 0 else 0) elif c == '2': y = y+(1 if y != ysize-1 else 0) elif (c == 'z' or c=='m') and mask[x][y] != ' ': mask[x][y] = ac[(ac.index(mask[x][y])+1) % 3] elif c == ' ': if vvram[x][y] == '*': printmines() putchar(x, y, "X") putchar(0, 23, "You lose. hit key") getkey() return(-1) reveal(x, y) elif c == 'q': return(1) def game(): putmines(mines) calcfield() mainloop() return def main(): try: game() except (Exception, KeyboardInterrupt) as e: curses.endwin() raise e else: curses.endwin() argvs = sys.argv argc = len(argvs) if argc == 2: mines = int(argvs[1]) if mines > 300: print("too many nyankos") exit(1) else: mines = 58 locale.setlocale(locale.LC_ALL, '') stdscr = curses.initscr() stdscr.keypad(True) curses.curs_set(0) main() exit(0)
- 投稿日:2021-08-09T14:52:53+09:00
【AtCoder解説】PythonでABC213のA,B,C,D,E問題を制する!
ABC213のA,B,C,D,E問題を、Python3でなるべく丁寧に解説していきます。 ただ解けるだけの方法ではなく、次の3つのポイントを満たす解法を解説することを目指しています。 シンプル:余計なことを考えずに済む 実装が楽:ミスやバグが減ってうれしい 時間がかからない:パフォが上がって、後の問題に残せる時間が増える ご質問・ご指摘はコメントかツイッター、その他のご意見・ご要望などはマシュマロまでお気軽にどうぞ! Twitter: u2dayo マシュマロ: https://marshmallow-qa.com/u2dayo ほしいものリスト : プレゼントしていただけると、やる気が出ます! よかったらLGTMや拡散していただけると喜びます! 目次 ABC213 まとめ A問題『Bitwise Exclusive Or』 B問題『Booby Prize』 C問題『Reorder Cards』 D問題『Takahashi Tour』 E問題『Stronger Takahashi』 アプリ AtCoderFacts を開発しています コンテストの統計データを見られるアプリ『AtCoderFacts』を作りました。 現在のところ、次の3つのデータを見ることができます。 - レート別問題正解率 - パフォーマンス目安 - 早解きで上昇するパフォーマンス 今後も機能を追加していく予定です。使ってくれると喜びます。 ABC213 まとめ 全提出人数: 8091人 パフォーマンス パフォ AC 点数 時間 順位(Rated内) 200 AB------ 300 24分 6029(5800)位 400 AB------ 300 8分 4976(4747)位 600 ABC----- 600 73分 4117(3890)位 800 AB-D---- 700 24分 3233(3008)位 1000 ABCD---- 1000 66分 2413(2190)位 1200 ABCD---- 1000 37分 1738(1518)位 1400 ABCD---- 1000 8分 1213(1005)位 1600 ABCDE--- 1500 72分 824(629)位 1800 ABCDE--- 1500 52分 551(367)位 2000 ABCDE--- 1500 37分 345(197)位 2200 ABCDE--- 1500 18分 200(98)位 2400 ABCDEF-- 2000 81分 123(46)位 色別の正解率 色 人数 A B C D E F G H 灰 3459 90.4 % 92.8 % 22.4 % 12.3 % 0.5 % 0.0 % 0.0 % 0.0 % 茶 1413 98.4 % 98.8 % 71.9 % 45.8 % 3.0 % 0.3 % 0.1 % 0.1 % 緑 1128 99.7 % 99.7 % 91.6 % 81.2 % 15.9 % 0.6 % 0.1 % 0.2 % 水 694 99.7 % 99.7 % 99.0 % 96.0 % 56.8 % 3.8 % 0.6 % 0.1 % 青 391 99.7 % 99.7 % 99.2 % 98.5 % 84.4 % 14.8 % 2.3 % 1.0 % 黄 176 96.6 % 96.0 % 94.9 % 95.5 % 84.1 % 31.2 % 5.7 % 2.3 % 橙 37 97.3 % 97.3 % 94.6 % 94.6 % 89.2 % 54.0 % 24.3 % 8.1 % 赤 21 95.2 % 95.2 % 95.2 % 95.2 % 90.5 % 90.5 % 66.7 % 33.3 % ※表示レート、灰に初参加者は含めず A問題『Bitwise Exclusive Or』 問題ページ:A - Bitwise Exclusive Or 灰コーダー正解率:90.4 % 茶コーダー正解率:98.4 % 緑コーダー正解率:99.7 % やることはXORを足し算や引き算に置き換えた問題と同じですが、初心者の方はXORを知らない人のほうが多いと思います。正解率もB問題より低いです。 考察 XORは演算子^を使うと計算できます。たとえば $A\mbox{ xor }C$ を計算したいときは、A ^ Cと書けばいいです。これだけ知っていれば、XORが何か知らなくても解けます。 実装1: for文で全探索 問題文に「 $C$ はただ $1$ つ存在し、$0$ 以上 $255$ 以下であることが証明される」とあるので、$A \mbox{ xor }C=B$ となる $C$ を、$0$ ~ $255$ までforループで全探索すればいいです。この方法はXORについて何も知らなくても解けます。 コード1: for文で全探索 A, B = map(int, input().split()) for C in range(256): if A ^ C == B: print(C) 実装2: XORの性質を利用して式変形 XORの性質を利用して式を変形すると、全探索せずに答えを求められます。その性質とは $2$ つあって $X\mbox{ xor }X =0$ $0\mbox{ xor }X = X$ です。$A\mbox{ xor } C = B$ の両辺を $\mbox{xor } A$ することで $A\mbox{ xor }A \mbox{ xor } C = A \mbox{ xor }B$ $0\mbox{ xor }C = A\mbox{ xor }B$ $C = A\mbox{ xor }B$ となります。したがって、A ^ Bを出力すれば正解になります。 コード2: XORの性質を利用して式変形 A, B = map(int, input().split()) print(A ^ B) B問題『Booby Prize』 問題ページ:B - Booby Prize 灰コーダー正解率:92.8 % 茶コーダー正解率:98.8 % 緑コーダー正解率:99.7 % 考察 $2$ 番目に大きいスコアを求める $2$ 番目に大きいスコアが元のリストの何番目か求めて、+1番の人(リストのインデックスは0スタートのため) 実装 ソートして後ろから $2$ 番目の要素が $2$ 番目に大きいスコア A.index(b) + 1 コード N = int(input()) A = list(map(int, input().split())) A_sorted = sorted(A) b = A_sorted[-2] # 2番目に大きいスコア print(A.index(b) + 1) # 1始まりにするために+1する C問題『Reorder Cards』 問題ページ:C - Reorder Cards 灰コーダー正解率:22.4 % 茶コーダー正解率:71.9 % 緑コーダー正解率:91.6 % 考察 例えば、この図の場合を考えてみましょう。( $H=9,W=6,N=6$ ) 左図の赤い数字の書いてあるマスに数字の書いてあるカードがあります。 カードの置いていない行・列を削除すると、マス目は右図の状態になります。カードの位置を $(行, 列)$ とあらわすと、操作後のカードの位置はカード $1$ から順に$(1,2),(2,2),(2,3),(3,1),(4,4),(5,3)$になります。(右の赤い数字が書いてあるマスの位置) 左図のカードの置いてある行・列に、小さい順に割り振った青い数字と、右図の青い数字が対応しています。 この図の場合、 カードの置いてある行番号 $(2,3,5,6,9)$ を $(1,2,3,4,5)$ カードの置いてある列番号 $(1,2,3,4)$ を $(1,2,3,4)$ に変換できればいいです。 解き方 行と列の操作はお互いに関係がないので、行と列で $2$ 回同じことをすれば解けます。 カードの行番号のリストRを受け取る Rから重複を省いて、小さい順にソートしたリストRsを用意する Rsをつかって、元の行番号が上から何番目に対応するかの辞書Rdを作る なお、これは『座標圧縮』と呼ばれるテクニックです。 実装 Rから重複を省いてソートしたリストRsは、Rs = sorted(set(R))と書けば作れます。set型に変換して重複を省き、それをsortedでソートされたリストに変換します。 Rdは、Rs[i]: i + 1のdictです。rangeかenumerateを使って作ります。 コード H, W, N = map(int, input().split()) R = [] C = [] for _ in range(N): r, c = map(int, input().split()) R.append(r) C.append(c) Rs = sorted(set(R)) # `set`で重複を省いてソートしたリスト`Rs` Cs = sorted(set(C)) # Rd = {Rs[i]: i+1 for i in range(len(Rs))} と同じです Rd = {x: i for i, x in enumerate(Rs, 1)} Cd = {x: i for i, x in enumerate(Cs, 1)} for r, c in zip(R, C): print(Rd[r], Cd[c]) D問題『Takahashi Tour』 問題ページ:D - Takahashi Tour 灰コーダー正解率:12.3 % 茶コーダー正解率:45.8 % 緑コーダー正解率:81.2 % 考察 『いまいる都市と道路で直接つながっている都市のうち、まだ訪れたことがない都市が存在するとき、そのような都市のうち番号が最も小さい都市へ移動する』とあるので、忘れずに辺をソートしておきます。 DFS(深さ優先探索)をして、答えのリストに頂点を追加していけば解けますが、以下の $2$ つの場合にリストに頂点を追加する必要があります。 その頂点にはじめて訪問したとき その頂点から他の頂点に行って帰ってくるたび なお、この問題で答える頂点番号の列のことを、オイラーツアーと呼びます。 実装 PyPyは再帰に弱いので、Pythonで提出してください。 sys.setrecursionlimitで再帰の深さの上限を増やさないとRE(実行時エラー)になります。この問題では最大で20万程度になるので、適当にそれ以上の数字を設定します。(20万の都市が一直線につながっている場合、深さが20万程度になる) 解説はしませんが、PyPyでも通せる非再帰版の実装も用意しました。 コード 再帰DFS def main(): import sys sys.setrecursionlimit(10 ** 6) # 最大で再帰の深さが2 * 10 ** 5程度になるので、それ以上に設定します # 木上のDFSなので、どの頂点から来たかを引数pで渡せば、訪問済みの頂点を覚えなくても済みます def dfs(u, p): ans.append(u) # はじめて訪問したとき for v in edge[u]: if v != p: dfs(v, u) ans.append(u) # 他の頂点に行って帰ってくるたび N = int(input()) edge = [[] for _ in range(N + 1)] for i in range(N - 1): a, b = map(int, input().split()) edge[a].append(b) edge[b].append(a) # 小さい順にsortを忘れるとWAです for i in range(N + 1): edge[i].sort() ans = [] dfs(1, -1) print(*ans) if __name__ == '__main__': main() 非再帰版 がんばって非再帰で実装すると、PyPyでも通せます。 def main(): def dfs(): for i in range(N + 1): edge[i].sort(reverse=True) stack = [1] seen = [0] * (N + 1) seen[1] = 1 while stack: u = stack.pop() if u > 0: ans.append(u) # はじめて訪問したとき for v in edge[u]: if not seen[v]: seen[v] = 1 stack.append(-u) stack.append(v) if u < 0: ans.append(-u) # 他の頂点に行って帰ってくるたび N = int(input()) edge = [[] for _ in range(N + 1)] for i in range(N - 1): a, b = map(int, input().split()) edge[a].append(b) edge[b].append(a) ans = [] dfs() print(*ans) if __name__ == '__main__': main() E問題『Stronger Takahashi』 問題ページ:E - Stronger Takahashi 灰コーダー正解率:0.5 % 茶コーダー正解率:3.0 % 緑コーダー正解率:15.9 % 考察 グリッドの問題ではなくグラフの問題と考えて、頂点間に以下の $2$ 種類の辺があるとみなします。 上下左右の . のマスにコスト $0$ で移動できる辺 マンハッタン距離が $3$ 以下のマスにコスト $1$ で移動できる辺(マスの状態は . # どちらでも構わない) マンハッタン距離が $3$ 以下のマスとは、下図の@に対するoのマスのことです。つまり、上下左右に $3$ 回移動して到達できるマスのことです。周りがすべて壁であっても o のマスには $1$ 回の壁破壊で移動できます。逆に、それ以外のマスには $1$ 回の壁破壊では移動できません。 xooox ooooo oo@oo ooooo xooox もしかすると o のなかに壁を壊さずコスト $0$ で移動できるマスもあるかもしれませんが、そのような場合もコスト $1$ でも移動できることにして構いません。(後述の01BFS、ダイクストラ法では、最短経路にはならない辺が含まれていても問題なく答えを求められます) 01BFS(またはダイクストラ法)で解ける グラフの辺の貼り方がわかったので、ダイクストラ法か01BFSを使えばこの問題を解くことができます。今回は01BFSで解くことにします。 01BFSは、グラフにコストが $0$ か $1$ の辺だけあるときに使える、ダイクストラ法の亜種です。 優先度付きキューの代わりに、BFSで使う両端キュー(deque)を使います。キューに要素を追加するときに、コスト $0$ の辺を使う場合は先頭に、コスト $1$ の辺を使う場合は末尾に要素を追加します。すると、優先度付きキューと同じことを両端キューで実現できます。 優先度付きキューのコードを貼る手間がなくなり、計算量が $O(HW\ logHW)$ から $O(HW)$ に改善されます。 コード def main(): # 上下左右1マス移動と、上下左右3マス以内移動の方法をあらかじめ作っておく from itertools import product # mov3の内包表記で二重ループを書きたくなかったので dr = [1, -1, 0, 0] dc = [0, 0, 1, -1] mov1 = tuple((r, c) for r, c in zip(dr, dc)) mov3 = tuple((r, c) for r, c in product(range(-2, 3), repeat=2) if 1 <= abs(r) + abs(c) <= 3) def solve(): from collections import deque dist = [[10 ** 7] * W for _ in range(H)] dist[0][0] = 0 deq = deque() deq.appendleft((0, 0, 0)) while deq: cost, r, c = deq.popleft() for ar, ac in mov1: nr, nc = r + ar, c + ac if 0 <= nr < H and 0 <= nc < W: if grid[nr][nc] == "." and cost < dist[nr][nc]: dist[nr][nc] = cost deq.appendleft((cost, nr, nc)) for ar, ac in mov3: nr, nc = r + ar, c + ac if 0 <= nr < H and 0 <= nc < W: if cost + 1 < dist[nr][nc]: dist[nr][nc] = cost + 1 deq.append((cost + 1, nr, nc)) return dist[-1][-1] H, W = map(int, input().split()) grid = [] for _ in range(H): _r = input() grid.append(_r) print(solve()) if __name__ == '__main__': main()
- 投稿日:2021-08-09T14:27:20+09:00
Algorithm | 優先度付きキュー(heapq)をPython3で解説(例題あり)
優先度付きキューとは 優先度付きキュー(priority queue)とは、優先度にしたがって、優先度の高いものから順番に取り出すことができるものだ。 その代表例がヒープキュー(heapq)である。公式ドキュメントはこちら。(https://docs.python.org/ja/3/library/heapq.html) 特徴は大きく分けて2つある。 最小値、最大値を素早く取り出すことができる 値の挿入順は気にしなくても良い 主に使うメソッドは次のとおりだ。 heapq.heapify(heap, item):優先度付きキューの作成 heapq.heappop(heap):最小値の取り出し heapq.heapush(heap, item):キューへの挿入 heapq.heappop(heap)、heapq.heapush(heap, item)の計算量はどちらも$O(logN)$なので、通常のリストでの操作よりも少ない計算量で実装できる。 # heapq import heapq numbers = [3, 5, 1, 6, 2, 4] heapq.heapify(numbers) # 優先度付きキューの作成 print(numbers) print(heapq.heappop(numbers)) # 最小値の取り出し print(heapq.heappop(numbers)) print(numbers) heapq.heappush(numbers, 1) # キューへの挿入 heapq.heappush(numbers, 2) print(numbers) print(heapq.heappop(numbers)) # 最小値の取り出し print(heapq.heappop(numbers)) print(numbers) >> [1, 2, 3, 6, 5, 4] >> 1 >> 2 >> [3, 4, 5, 6] >> [1, 3, 2, 6, 4, 5] >> 1 >> 2 >> [3, 4, 5, 6] 例題1: D - Summer Vacation def main(): import heapq n, m = map(int, input().split()) jobs = [[] for _ in range(m+1)] for _ in range(n): # 各日のリストに各仕事の報酬を追加 a, b = map(int, input().split()) if a <= m: jobs[a].append(b) heap = [] heapq.heapify(heap) total = 0 for day in range(1, m+1): for i in jobs[day]: heapq.heappush(heap, -i) # 最大値を取れるように-iでpush if len(heap) > 0: # もし値があれば、 total += abs(heapq.heappop(heap)) # 最大値を取得 print(total) if __name__ == '__main__': main() 例題2: D - Powerful Discount Tickets def main(): import heapq n, m = map(int, input().split()) alist = list(map(lambda x: int(x)*(-1), input().split())) heapq.heapify(alist) for _ in range(m): temp = heapq.heappop(alist)*(-1)//2 heapq.heappush(alist, temp*(-1)) print(sum(map(lambda x: -x, alist))) if __name__ == '__main__': main() 例題3: D - Querying Multiset def main(): import heapq q = int(input()) sack = [] heapq.heapify(sack) total = 0 for i in range(q): query = list(map(int, input().split())) if query[0] == 1: heapq.heappush(sack, query[1]-total) elif query[0] == 2: total += query[1] else: ans = heapq.heappop(sack) print(ans+total) if __name__ == '__main__': main() 例題4: F - タスクの消化 def main(): import heapq n = int(input()) tasks = [[] for _ in range(n+1)] for _ in range(n): a, b = map(int, input().split()) tasks[a].append(b) heap = [] heapq.heapify(heap) total = 0 for day in range(1, n+1): for i in tasks[day]: heapq.heappush(heap, -i) # 最大値の取得 if len(heap) > 0: total += abs(heapq.heappop(heap)) # 最大値を加算 print(total) if __name__ == '__main__': main() 例題5: D - Chat in a Circle def main(): import heapq n = int(input()) alist = list(map(int, input().split())) alist.sort(reverse=True) total = 0 heap = [] heapq.heappush(heap, alist[0]*(-1)) # 最初に到着した人 for i in range(1, n): friendly = heapq.heappop(heap)*(-1) # 輪の中で最大値を取る total += friendly # 心地よさに加算する heapq.heappush(heap, alist[i]*(-1)) # 輪に次の人を入れる heapq.heappush(heap, alist[i]*(-1)) print(total) if __name__ == '__main__': main() まとめ heapq は実装こそ難しくないので、使い慣れると最大の武器となる。 問題に行き詰まったら、heapが使えないか確認する癖をつけておこう。
- 投稿日:2021-08-09T14:07:54+09:00
Python 元号(和暦)年を西暦に変換する
和暦を西暦に変換する 文章中からなるべく正規表現を使わずにdictを使った変換処理を考えてみた。 これにより元号が増えても、処理時間に影響はない、はず。変換対象文字数に対して線形時間 日付までの処理は非対応 年数の変換に特化 大化の改新から令和まで対応 各元号の開始年はWikipediaの始期を参考 python3オンリー # coding=utf8 from datetime import datetime, timedelta g2d = { '令和': datetime(2019, 5, 1), 'R.': datetime(2019, 5, 1), '平成': datetime(1989, 1, 8), 'H.': datetime(1989, 1, 8), '昭和': datetime(1926, 12, 25), 'S.': datetime(1926, 12, 25), '大正': datetime(1912, 7, 30), 'T.': datetime(1912, 7, 30), '明治': datetime(1868, 10, 23), 'M.': datetime(1868, 10, 23), '慶応': datetime(1865, 5, 1), '元治': datetime(1864, 3, 27), '文久': datetime(1861, 3, 29), '万延': datetime(1860, 4, 8), '安政': datetime(1855, 1, 15), '嘉永': datetime(1848, 4, 1), '弘化': datetime(1845, 1, 9), '天保': datetime(1831, 1, 23), '文政': datetime(1818, 5, 26), '文化': datetime(1804, 3, 22), '享和': datetime(1801, 3, 19), '寛政': datetime(1789, 2, 19), '天明': datetime(1781, 4, 25), '安永': datetime(1772, 12, 10), '明和': datetime(1764, 6, 30), '宝暦': datetime(1751, 12, 14), '寛延': datetime(1748, 8, 5), '延享': datetime(1744, 4, 3), '寛保': datetime(1741, 4, 12), '元文': datetime(1736, 6, 7), '享保': datetime(1716, 8, 9), '正徳': datetime(1711, 6, 11), '宝永': datetime(1704, 4, 16), '元禄': datetime(1688, 10, 23), '貞享': datetime(1684, 4, 5), '天和': datetime(1681, 11, 9), '延宝': datetime(1673, 10, 30), '寛文': datetime(1661, 5, 23), '万治': datetime(1658, 8, 21), '明暦': datetime(1655, 5, 18), '承応': datetime(1652, 10, 20), '慶安': datetime(1648, 4, 7), '正保': datetime(1645, 1, 13), '寛永': datetime(1624, 4, 17), '元和': datetime(1615, 9, 5), '慶長': datetime(1596, 12, 16), '文禄': datetime(1593, 1, 10), '天正': datetime(1573, 8, 25), '元亀': datetime(1570, 5, 27), '永禄': datetime(1558, 3, 18), '弘治': datetime(1555, 11, 7), '天文': datetime(1532, 8, 29), '享禄': datetime(1528, 9, 3), '大永': datetime(1521, 9, 23), '永正': datetime(1504, 3, 16), '文亀': datetime(1501, 3, 18), '明応': datetime(1492, 8, 12), '延徳': datetime(1489, 9, 16), '長享': datetime(1487, 8, 9), '文明': datetime(1469, 6, 8), '応仁': datetime(1467, 4, 9), '文正': datetime(1466, 3, 14), '寛正': datetime(1461, 2, 1), '長禄': datetime(1457, 10, 16), '康正': datetime(1455, 9, 6), '享徳': datetime(1452, 8, 10), '宝徳': datetime(1449, 8, 16), '文安': datetime(1444, 2, 23), '嘉吉': datetime(1441, 3, 10), '永享': datetime(1429, 10, 3), '正長': datetime(1428, 6, 10), '応永': datetime(1394, 8, 2), '明徳': datetime(1390, 4, 12), '康応': datetime(1389, 3, 7), '嘉慶': datetime(1387, 10, 5), '至徳': datetime(1384, 3, 19), '永徳': datetime(1381, 3, 20), '康暦': datetime(1379, 4, 9), '永和': datetime(1375, 3, 29), '応安': datetime(1368, 3, 7), '貞治': datetime(1362, 10, 11), '康安': datetime(1361, 5, 4), '延文': datetime(1356, 4, 29), '文和': datetime(1352, 11, 4), '観応': datetime(1350, 4, 4), '貞和': datetime(1345, 11, 15), '康永': datetime(1342, 6, 1), '暦応': datetime(1338, 10, 11), '元中': datetime(1384, 5, 18), '弘和': datetime(1381, 3, 6), '天授': datetime(1375, 6, 26), '文中': datetime(1372, 5, 1), '建徳': datetime(1370, 8, 16), '正平': datetime(1347, 1, 20), '興国': datetime(1340, 5, 25), '延元': datetime(1336, 4, 11), '建武': datetime(1334, 3, 5), '正慶': datetime(1332, 5, 23), '元弘': datetime(1331, 9, 11), '元徳': datetime(1329, 9, 22), '嘉暦': datetime(1326, 5, 28), '正中': datetime(1324, 12, 25), '元亨': datetime(1321, 3, 22), '元応': datetime(1319, 5, 18), '文保': datetime(1317, 3, 16), '正和': datetime(1312, 4, 27), '応長': datetime(1311, 5, 17), '延慶': datetime(1308, 11, 22), '徳治': datetime(1307, 1, 18), '嘉元': datetime(1303, 9, 16), '乾元': datetime(1302, 12, 10), '正安': datetime(1299, 5, 25), '永仁': datetime(1293, 9, 6), '正応': datetime(1288, 5, 29), '弘安': datetime(1278, 3, 23), '建治': datetime(1275, 5, 22), '文永': datetime(1264, 3, 27), '弘長': datetime(1261, 3, 22), '文応': datetime(1260, 5, 24), '正元': datetime(1259, 4, 20), '正嘉': datetime(1257, 3, 31), '康元': datetime(1256, 10, 24), '建長': datetime(1249, 5, 2), '宝治': datetime(1247, 4, 5), '寛元': datetime(1243, 3, 18), '仁治': datetime(1240, 8, 5), '延応': datetime(1239, 3, 13), '暦仁': datetime(1238, 12, 30), '嘉禎': datetime(1235, 11, 1), '文暦': datetime(1234, 11, 27), '天福': datetime(1233, 5, 25), '貞永': datetime(1232, 4, 23), '寛喜': datetime(1229, 3, 31), '安貞': datetime(1228, 1, 18), '嘉禄': datetime(1225, 5, 28), '元仁': datetime(1224, 12, 31), '貞応': datetime(1222, 5, 25), '承久': datetime(1219, 5, 27), '建保': datetime(1214, 1, 18), '建暦': datetime(1211, 4, 23), '承元': datetime(1207, 11, 16), '建永': datetime(1206, 6, 5), '元久': datetime(1204, 3, 23), '建仁': datetime(1201, 3, 19), '正治': datetime(1199, 5, 23), '建久': datetime(1190, 5, 16), '文治': datetime(1185, 9, 9), '元暦': datetime(1184, 5, 27), '寿永': datetime(1182, 6, 29), '養和': datetime(1181, 8, 25), '治承': datetime(1177, 8, 29), '安元': datetime(1175, 8, 16), '承安': datetime(1171, 5, 27), '嘉応': datetime(1169, 5, 6), '仁安': datetime(1166, 9, 23), '永万': datetime(1165, 7, 14), '長寛': datetime(1163, 5, 4), '応保': datetime(1161, 9, 24), '永暦': datetime(1160, 2, 18), '平治': datetime(1159, 5, 9), '保元': datetime(1156, 5, 18), '久寿': datetime(1154, 12, 4), '仁平': datetime(1151, 2, 14), '久安': datetime(1145, 8, 12), '天養': datetime(1144, 3, 28), '康治': datetime(1142, 5, 25), '永治': datetime(1141, 8, 13), '保延': datetime(1135, 6, 10), '長承': datetime(1132, 9, 21), '天承': datetime(1131, 2, 28), '大治': datetime(1126, 2, 15), '天治': datetime(1124, 5, 18), '保安': datetime(1120, 5, 9), '元永': datetime(1118, 4, 25), '永久': datetime(1113, 8, 25), '天永': datetime(1110, 7, 31), '天仁': datetime(1108, 9, 9), '嘉承': datetime(1106, 5, 13), '長治': datetime(1104, 3, 8), '康和': datetime(1099, 9, 15), '承徳': datetime(1097, 12, 27), '永長': datetime(1097, 1, 3), '嘉保': datetime(1095, 1, 23), '寛治': datetime(1087, 5, 11), '応徳': datetime(1084, 3, 15), '永保': datetime(1081, 3, 22), '承暦': datetime(1077, 12, 5), '承保': datetime(1074, 9, 16), '延久': datetime(1069, 5, 6), '治暦': datetime(1065, 9, 4), '康平': datetime(1058, 9, 19), '天喜': datetime(1053, 2, 2), '永承': datetime(1046, 5, 22), '寛徳': datetime(1044, 12, 16), '長久': datetime(1040, 12, 16), '長暦': datetime(1037, 5, 9), '長元': datetime(1028, 8, 18), '万寿': datetime(1024, 8, 19), '治安': datetime(1021, 3, 17), '寛仁': datetime(1017, 5, 21), '長和': datetime(1013, 2, 8), '寛弘': datetime(1004, 8, 8), '長保': datetime(999, 2, 1), '長徳': datetime(995, 3, 25), '正暦': datetime(990, 11, 26), '永祚': datetime(989, 9, 10), '永延': datetime(987, 5, 5), '寛和': datetime(985, 5, 19), '永観': datetime(983, 5, 29), '天元': datetime(978, 12, 31), '貞元': datetime(976, 8, 11), '天延': datetime(974, 1, 16), '天禄': datetime(970, 5, 3), '安和': datetime(968, 9, 8), '康保': datetime(964, 8, 19), '応和': datetime(961, 3, 5), '天徳': datetime(957, 11, 21), '天暦': datetime(947, 5, 15), '天慶': datetime(938, 6, 22), '承平': datetime(931, 5, 16), '延長': datetime(923, 5, 29), '延喜': datetime(901, 8, 31), '昌泰': datetime(898, 5, 20), '寛平': datetime(889, 5, 30), '仁和': datetime(885, 3, 11), '元慶': datetime(877, 6, 1), '貞観': datetime(859, 5, 20), '天安': datetime(857, 3, 20), '斉衡': datetime(854, 12, 23), '仁寿': datetime(851, 6, 1), '嘉祥': datetime(848, 7, 16), '承和': datetime(834, 2, 14), '天長': datetime(824, 2, 8), '弘仁': datetime(810, 10, 20), '大同': datetime(806, 6, 8), '延暦': datetime(782, 9, 30), '天応': datetime(781, 1, 30), '宝亀': datetime(770, 10, 23), '神護景雲': datetime(767, 9, 13), '天平神護': datetime(765, 2, 1), '天平宝字': datetime(757, 9, 6), '天平勝宝': datetime(749, 8, 19), '天平感宝': datetime(749, 5, 4), '天平': datetime(729, 9, 2), '神亀': datetime(724, 3, 3), '養老': datetime(717, 12, 24), '霊亀': datetime(715, 10, 3), '和銅': datetime(708, 2, 7), '慶雲': datetime(704, 6, 16), '大宝': datetime(701, 5, 3), '朱鳥': datetime(686, 8, 14), '白雉': datetime(650, 3, 22), '大化': datetime(645, 7, 17) } g2yidx = {k[1]:{} for k in g2d} end = datetime(9999, 12, 31) for k, v in g2d.items(): g2yidx[k[1]][k[0]] = [len(k), k, v, end - timedelta(seconds=1) if end else end] end = v g2yidx["."]["R"][3] = datetime(9999, 12, 31) g2yidx["."]["H"][3] = g2d["令和"] - timedelta(seconds=1) g2yidx["."]["S"][3] = g2d["平成"] - timedelta(seconds=1) g2yidx["."]["T"][3] = g2d["昭和"] - timedelta(seconds=1) g2yidx["."]["M"][3] = g2d["大正"] - timedelta(seconds=1) g2ynum = { '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '零': 0, '壱': 1, '弐': 2, '参': 3, ' ': -1, ' ': -1, '十': -1, '拾': -1, '元': 1 } def gengo2date(s, strict = False): slen = len(s) if slen < 3: return s ret = "" i = -1 while (i < slen - 3): i += 1 s1 = s[i + 1] if (s1 not in g2yidx): ret += s[i] continue s0 = s[i] s1idx = g2yidx[s1] if (s0 not in s1idx): ret += s0 continue sidx = s1idx[s0] elen, era, startdt, enddt = sidx i += 2 if (elen == 4): if (s[i - 2] == era[0] and s[i - 1] == era[1]): i += 2 else: ret += s0 + s1 continue sy = s[i] r = 0 while (sy in g2ynum): y = g2ynum[sy] if y != -1: r = 10 * r + y i += 1 sy = s[i] if (r == 0): ret += s0 i -= elen continue if (strict and enddt.year < startdt.year + r - 1): raise ValueError(f"`{era}` ended in B.C.`{enddt.year}`. years") ret += str(startdt.year + r - 1) i -= 1 if (i < slen - 1): ret += s[i + 1:slen] return ret print(gengo2date("令和元年05月1日")) # -> 2019年05月1日 print(gengo2date("平成元年01月1日")) # -> 1989年01月1日 print(gengo2date("令和2年04月1日")) # -> 2020年04月1日 print(gengo2date("令和二十三年04月1日")) # -> 2041年04月1日 print(gengo2date("令和三二年04月1日")) # -> 2050年04月1日 print(gengo2date("令和 三二年04月1日")) # -> 2050年04月1日 print(gengo2date("あいうえお令和 三 二年04月1日かきくけこ")) # -> あいうえお2050年04月1日かきくけこ print(gengo2date("令和 三 十 二 年04月1日")) # -> 2050年04月1日 print(gengo2date("R.4 年04月1日")) # -> 2022年04月1日 print(gengo2date("R.4/04/01")) # -> 2022/04/01 print(gengo2date("R .4-04-01")) # -> R .4-04-01(変換しない) print(gengo2date("平成ほげ年")) # -> 平成ほげ年(変換しない) print(gengo2date("R.H")) # -> R.H(変換しない) print(gengo2date("R.1グランプリ")) # -> 2019グランプリ(こういう場合は変換される) print(gengo2date("R1グランプリ")) # -> R1グランプリ(これは大丈夫) print(gengo2date("初代M.1王者")) # -> 初代1868王者(こういう場合は変換される) print(gengo2date("初代M1王者")) # -> 初代M1王者(これは大丈夫) print(gengo2date("大化1377年")) # -> 2021年(存在しない年数を入れても変換される) print(gengo2date("大化1377年", strict = True)) # -> ValueError: `大化` ended in B.C.`650`. years(オプション付ければありえない年数の場合はエラーを出すことも可能。) In [1]: %timeit gengo2date("平成二十三年04月1日") 2.59 µs ± 51.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 全く面倒なルール作りやがってエコじゃないなあ
- 投稿日:2021-08-09T13:55:32+09:00
Azure Functions で FastAPI/ASGI のAPIを動かす
概要 Flask/WSGI同様にFastAPI/ASGIでもAzureクラウド上では動かすことができる ただし、2021/08現在はローカル環境のfunc startコマンドでは実行できない Flask/WSGI の記事 多分FastAPI/ASGIなどなら azure.functions.AsgiMiddlewareで良いはず。 FastAPIに変えた時の例 import azure.functions as func from fastapi_app.app import app # app = FastAPI() def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse: # ASGI Application return func.AsgiMiddleware(app).handle(req, context) . ├── fastapi_app # FastAPI/ASGI App │ ├── __init__.py │ └── app.py ├── functions_wsgi # Functions entry -> ASGI │ ├── __init__.py │ └── function.json ├── host.json └── requirements.txt Azure Functions Core Tools が古い問題(2021/08現在) Azure Functions Core Toolsの、v3.0.3568やv3.0.3477(brewは古いまま更新されてない) に同梱されている、Azure Functions Python Libraryがv1.7.0で、ASGIに対応したv1.7.1以降が適用されていないので、ローカル実行時の環境ではazure.functions.AsgiMiddlewareが見つからず実行できない。 $ func start Found Python version 3.8.6 (python3). Azure Functions Core Tools Core Tools Version: 3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d (64-bit) Function Runtime Version: 3.0.15584.0 . . . Functions: functions_asgi: [GET,POST] http://localhost:7071/{*route} . . . [2021-08-09T02:24:09.212Z] Executing 'Functions.functions_asgi' (Reason='This function was programmatically called via the host APIs.', Id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) [2021-08-09T02:24:09.261Z] Python HTTP trigger function processed a request. [2021-08-09T02:24:09.332Z] Executed 'Functions.functions_asgi' (Failed, Id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, Duration=139ms) [2021-08-09T02:24:09.333Z] System.Private.CoreLib: Exception while executing function: Functions.functions_asgi. System.Private.CoreLib: Result: Failure [2021-08-09T02:24:09.333Z] Exception: AttributeError: module 'azure.functions' has no attribute 'AsgiMiddleware' [2021-08-09T02:24:09.333Z] Stack: File "/usr/local/Cellar/azure-functions-core-tools@3/3.0.3477/workers/python/3.8/OSX/X64/azure_functions_worker/dispatcher.py", line 399, in _handle__invocation_request [2021-08-09T02:24:09.333Z] call_result = await self._loop.run_in_executor( [2021-08-09T02:24:09.333Z] File "/Users/username/.anyenv/envs/pyenv/versions/3.8.6/lib/python3.8/concurrent/futures/thread.py", line 57, in run [2021-08-09T02:24:09.333Z] result = self.fn(*self.args, **self.kwargs) [2021-08-09T02:24:09.333Z] File "/usr/local/Cellar/azure-functions-core-tools@3/3.0.3477/workers/python/3.8/OSX/X64/azure_functions_worker/dispatcher.py", line 603, in _run_sync_func [2021-08-09T02:24:09.333Z] return ExtensionManager.get_sync_invocation_wrapper(context, [2021-08-09T02:24:09.333Z] File "/usr/local/Cellar/azure-functions-core-tools@3/3.0.3477/workers/python/3.8/OSX/X64/azure_functions_worker/extension.py", line 215, in _raw_invocation_wrapper [2021-08-09T02:24:09.333Z] result = function(**args) [2021-08-09T02:24:09.333Z] File "/Users/usernamew/Workspaces/sample-azure-functions-asgi-fastapi/functions_asgi/__init__.py", line 12, in main [2021-08-09T02:24:09.333Z] return func.AsgiMiddleware(app).handle(req, context) 暫定対応方法 Core Tools 上でのローカルデバッグを諦める Uvicornでデバッグし、Core Toolsのアップデートを待つ。 Core Tools 上の Python Library を最新にアップデートする pip install -tでCore Toolsのworkersディレクトリのものを差し替える。下記はmacOSなので別なプラットフォームの場合は、3.8/OSX/X64 の部分を読み替え。 $ pip install azure-functions -t /usr/local/Cellar/azure-functions-core-tools@3/3.0.3477/workers/python/3.8/OSX/X64 --upgrade Collecting azure-functions Using cached azure_functions-1.7.2-py3-none-any.whl (137 kB) Installing collected packages: azure-functions Successfully installed azure-functions-1.7.2