- 投稿日:2021-10-31T23:28:12+09:00
【Django】Exception Value: type object '' has no attribute '_meta'のエラー処理
はじめに Djangoプロジェクト開発の際にタイトルにあるエラーに遭遇しました.本日はこのエラーメッセージの対処法について自分なりに勉強した内容を記事にしております. ※「とりあえずエラーが解消できれば良い!」という方は,この記事の途中は読み飛ばして「エラー処理方法」だけ読んで頂ければ解決できると思います. エラーの原因は? このエラー文は「モデルクラスに_meta属性があるはずなのに,どこにもない!」と怒ってきています. Djangoプロジェクト内で,モデルを参照することを前提として動いている機能を使う際に,そのモデルの参照が適切にできていないと,このように怒られるようです. _meta属性とは _meta属性はDjango ORM(Object-relational mapping: オブジェクト関係マッピング)を中核を担うAPIです.Djangoプロジェクト内のモデル定義部分以外のシステムがモデルを参照する際に,参照したモデルのメタ情報も理解できるようになります._meta属性のオプションは様々ありますが,今回の主題とは逸れるので,興味のある方はドキュメントで詳細をご覧ください. エラー処理方法 Djangoプロジェクト内で下記のmodel = Hogehogeのようにモデル参照を行った際のhogehogeクラスがmodels.Modelを継承していないことがこのパターンの原因であると考えられます. views.py from django.views.generic import ListView from .models import Hogehoge class Example(ListView): template_name = 'example.html' model = Hogehoge # Hogehogeクラスをモデルとして参照する 上の例のようにモデルを参照する場合,Hogehogeクラスは当然モデルクラスである必要があります. models.py(誤) # これは間違っている例です class Hogehoge(): # model.Modelの継承を忘れてる field_a = ... models.py(正) from django.db import models class Hogehoge(models.Model): # model.Modelの継承をしっかりしてやる field_a = ... ※views.pyはあくまで例であり,formsなど他のシステムでも同じようにmodel = Hogehogeと参照する部分では同じようにエラーが出ると思います. このパターンは,私がやらかした凡ミスを基にしていますが,これとは別に継承クラスの名前を間違えてしまった方やモデルクラスの定義はできていたが,そのクラスを通常のクラスとして再定義してしまった方もいました. 最後に 今までは時間のなさを理由にして,発生したエラーに対して深く調べることはあまりありませんでした.しかし,その場で解決するだけではなくできるだけ調べるクセをつけるために今回の記事を書きました. 至らない部分が多いとは思いますが,「ここ違うよ!」と感じた方がいらっしゃれば是非ご指摘頂けますと幸いです. ※ご意見や疑問点などお待ちしております! 参照 Django: model object “has no attribute '_meta'” in class based view AttributeError: type object 'Team' has no attribute '_meta' Model _meta API | Django documentation Django データベース操作 についてのまとめ オブジェクト関係マッピング - Wikipedia Model Meta Options | Django ドキュメント
- 投稿日:2021-10-31T22:54:01+09:00
Pythonで分数のリストを誤差なしでソートする
はじめに AtCoder Beginner Contest 225 (ABC 225) の E 問題 で、表題のことができずに詰んだので、勉強した結果をまとめます。 普通にソートするときの問題点 分数のリストをソートしたいときは、普通は以下のように分数を少数に直し、小数のリストでソートしますよね。 l = [5 / 7, 3 / 8, 1 / 2] # l = [0.7142857142857143, 0.375, 0.5] l = sorted(l) # l = [0.375, 0.5, 0.7142857142857143] しかし、分母・分子の値が大きくなってくると誤差が出ることがあります。それが原因で、正しく順序がソートされないことがあるのです。誤差が出るパターンを1つ示します。 >>> (10 ** 9 + 1) / 10 ** 9 1.000000001 >>> 10 ** 9 / (10 ** 9 - 1) 1.000000001 $(10^9 + 1) / 10^9$ と $10^9 / (10^9 - 1)$ は異なるにもかかわらず、小数で表すと等しくなってしまいます。 これを避けるためには小数を登場させなければいいです。つまり、 $x_a / y_a$ という分数が $x_b / y_b$ という分数より小さいかどうかを調べるときは、$x_a / y_a < x_b / y_b$ の真偽を確かめるのではなく、 $x_a \times y_b < x_b \times y_a$ の真偽を確かめればよいです。 >>> (10 ** 9 + 1) / 10 ** 9 == 10 ** 9 / (10 ** 9 - 1) True # 本当は等しくない >>> (10 ** 9 + 1) * (10 ** 9 - 1) < 10 ** 9 * 10 ** 9 True # 大小が正しく判定される 誤差なしでソートする方法 組み込みの sorted() 関数の key 引数に functools.cmp_to_key(cmp) を渡してやることで、自前の cmp() 関数で定義した比較関数に従って、ソートすることができます。 cmp_to_key() については こちら を参考にしました。 from functools import cmp_to_key def cmp(a, b): # a = [x_a, y_a], b = [x_b / y_b] # 比較対象の分数 a と b が等しければ 0 を返す if a[0] * b[1] == b[0] * a[1]: return 0 # a, bという順で並んでほしい条件のときは-1を返し、それ以外では1を返す return -1 if a[0] * b[1] < b[0] * a[1] else 1 l = [[5, 7], [3, 8], [1, 2]] # リストの各要素は [分子, 分母] とする l = sorted(l, key=cmp_to_key(cmp)) # l = [[3, 8], [1, 2], [5, 7]] 最後までご覧いただきありがとうございます。
- 投稿日:2021-10-31T22:52:22+09:00
python djangoフレームワークで最初にやること
| 1.プロジェクトの作成 django-admin startproject {project_name} | 2.アプリケーションの作成 python manage.py startapp {app_name} | 3. setttings.pyを編集 | 3.1 作成したアプリケーションを追加 INSTALLED_APPS = []に次の値を入れる app_nameはアプリケーションの名前 'app_name.apps.App_nameConfig' 最初のアプリ名はふつう、二個目のアプリ名は先頭の文字を大文字に | 3.2. 日本語にする LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo' djangoを独学しています。 備忘録のように書いています。
- 投稿日:2021-10-31T22:47:04+09:00
Jupyter labでKernelがないといわれた。
Anacondaを起動してJupyterlabを起動したらNo Kernelとなったのでその原因とその修正方法をここに残します。 経緯 AnacondaのPromptでJupyterlabを起動するとブラウザが立ち上がり表示されるまでは動くがNo kernelとなってプログラムが動かせない状態になった。原因がPromptには表示されていた。 ImportError: cannot import name 'Template' from 'string' 原因 フォルダの中にstring.pyというモジュール名を持ったPythonファイルを作成していたことだった。このファイルは予約された名前であったため、このファイルが参照され動かなくなっていた。 修正方法 string.pyのファイル名をほかの名前にすればいい。
- 投稿日:2021-10-31T21:06:04+09:00
YouTubeでAI・Pythonを学ぼう!
ありがたいことにYouTubeにはたくさんの学べるコンテンツがあります。AIやPythonに関するのコンテンツもどんどん増えてきており、学習中の身としてはありがたい限りです。寝食以外はほぼYouTubeを視聴しているかもしれない私が、AIやPythonを学べるチャンネルおよび動画を紹介していきます。 いまにゅのプログラミング塾 私が受講しているキカガクの元講師の方がやられているチャンネルです。分かりやすい語り口とハンズオン形式で理解しやすい点が特長です。動画内で使用される画像や効果音がポップで少し未来感があるので、最先端技術を学ぶ意欲をかき立ててくれます。 キノコード / プログラミング学習チャンネル 単回帰分析などの数学やPythonによる作業効率化などを紹介しています。ガッツリ学べるコンテンツと「毎日Python」などのプチコンテンツがあり、幅広いニーズに応えてくれているので重宝しています。あまり余談がなく、シンプルな構成でまとめられています。「冗談はいらないから、知識だけちょうだい!」という方にもオススメです(笑) 予備校のノリで学ぶ「大学の数学・物理」 「予備校のノリで」という名前の通り、面白い先生に教えてもらっている感覚で学べるチャンネルです。黒板やホワイトボードに書きながら講義をするので、順を追って理解することができます。例え話や余談も交え、堅苦しさがないのでリラックスして視聴することができます。 Neural Network Console 数式をあまり使用せず、図解で理解を深めてくれます。動画に使われている色や書体、図もクオリティが高く、腰を据えて機械学習を"味わう"ように学ぶことができます。分かりやすい説明、聞き取りやすく心地よい声が、より一層学ぶことを楽しくしてくれます。 AIcia Solid Project チャンネルを見る前は、正直萌系(?)の声でディープラーニングを紹介するかと思っていました。普通に男性の声で驚きましたが、内容はとても分かりやすいです。言葉も簡単なものを選んで話されているので、勉強に疲れた時も、友人と話しているようなリラックス感で視聴することができます。かなり広い機械学習を紹介しているので、いろいろな技術をつまみ食いするのも良いかもしれません。 こいこいの人工知能研究室 「人工知能で遊んでいる」という表現が合うチャンネルだと思います。短編動画が多いため、隙間時間に視聴することができます。個人的にGANに興味があるので、カワウソや新ポケモンを生成する動画が特に楽しめました。細かくコードを紹介しているわけではないので、実装する際は別途自分で調べる必要がありますが、いろいろな技術を楽しく学びたい方におすすめです。 競馬予想で始めるデータ分析・機械学習 正直言うと最初は「ギャンブルに興味ないなぁ」と思い、すぐには視聴しませんでした。しかし、競馬はあくまでお題で(当たり前ですが...)、競馬予想を通してディープラーニングを学ぶことができます。ときには手書きを用いながら、実際にコードを書いて説明してくれます。個人的に、タイピングの音でも勉強意欲が沸くタイプなので、ライブコーディングのように進めてくれるチャンネルはありがたいです。 ウマたん ウマ続きです(笑) 数学・機械学習・プログラミングを一通り学べます。各動画が短いので隙間時間に視聴することができます。線画イラストで説明してくれるので、流れるように知識をインプットできます。膨大な動画数ゆえ、もう少し再生リストが細かく分かれていると、目当ての動画に辿り着きやすいなぁと感じました。逆に言うと、それほどボリューミーなコンテンツを提供してくれています。 はやたす / Pythonチャンネル 語弊があるかもしれないですが、これまで紹介したチャンネルに比べると動画自体には特徴があまりないです(笑) しかし、特徴がないがために視聴しやすく、一歩一歩学習していくことができます。(マリオ的な存在?) Pythonに基本のほか、Webスクレイピングやデータサイエンスなども紹介しており、知識の補完にとても役立ちました。個人的に動画のタイトル画面がシンプルかつ可愛らしくて好きです。 データサイエンス塾 10〜20分程度の動画数点が再生リストにまとまっています。1つの分野を順を追ってしっかり学ぶことができます。コードを書きながら進めていくので、実際に手元でも同じように進めることができます。動画が整理されているので、復習する際も学びたい動画に辿り着きやすいです。 AI教室 AIRS-Lab 毎週月曜日にライブ講義を行っているチャンネル。講師の方は「はじめてのディープラーニング」などの書籍を出していたり、Udemyで動画を提供しています。講師の方が説明しているだけあって、今まで紹介したチャンネルとはまた違った分かりやすさがあります。個人的にはPyTorchを使用している点も気に入っています。1つ注意なのは、完成まで紹介していると思ったら、途中までだったという動画もある点です。途中からUdemyの有料動画を視聴する必要があるので、その点だけ確認しておきましょう。 海外に比べて、日本のAIに関する文献は少ないと聞きますが、YouTubeだけでもかなり学べると感じています。足りない部分はネット記事や書籍で補完する手もあります。ただ、やはりさらに高度な技術のコードなどを知りたい場合は頑張って海外の記事やGitHubを読むことになると思います。私も知識を蓄えて、先人たちのように分かりやすくAIを紹介するコンテンツが作れるよう頑張ります。
- 投稿日:2021-10-31T20:25:27+09:00
【AtCoder解説】PythonでABC225のA,B,C,D問題を制する!
ABC225のA,B,C,D問題を、Python3でなるべく丁寧に解説していきます。 ただ解けるだけの方法ではなく、次の3つのポイントを満たす解法を解説することを目指しています。 シンプル:余計なことを考えずに済む 実装が楽:ミスやバグが減ってうれしい 時間がかからない:パフォが上がって、後の問題に残せる時間が増える ご質問・ご指摘はコメントかツイッター、マシュマロ、Discordサーバーまでお気軽にどうぞ! Twitter: u2dayo マシュマロ: https://marshmallow-qa.com/u2dayo ほしいものリスト: https://www.amazon.jp/hz/wishlist/ls/2T9IQ8IK9ID19?ref_=wl_share Discordサーバー(質問や記事の感想・リクエストなどどうぞ!) : https://discord.gg/jZ8pkPRRMT よかったらLGTMや拡散していただけると喜びます! 目次 ABC225 まとめ A問題『Distinct Strings』 B問題『Star or Not』 C問題『Calendar Validator』 D問題『Play Train』 アプリ AtCoderFacts を開発しています コンテストの統計データを見られるアプリ『AtCoderFacts』を作りました。 現在のところ、次の3つのデータを見ることができます。 レート別問題正解率 パフォーマンス目安 早解きで上昇するパフォーマンス 今後も機能を追加していく予定です。使ってくれると喜びます。 ABC225 まとめ 全提出人数: 6585人 パフォーマンス パフォ AC 点数 時間 順位(Rated内) 200 AB------ 300 32分 4984(4739)位 400 ABC----- 600 124分 4109(3866)位 600 ABC----- 600 65分 3392(3150)位 800 ABC----- 600 19分 2668(2428)位 1000 ABCD---- 1000 81分 2005(1774)位 1200 ABCD---- 1000 55分 1465(1238)位 1400 ABCD---- 1000 37分 1050(827)位 1600 ABCD---- 1000 19分 730(521)位 1800 ABCDE--- 1500 87分 481(303)位 2000 ABCDE--- 1500 63分 317(160)位 2200 ABCDE--- 1500 48分 212(78)位 2400 ABCDE--- 1500 37分 148(35)位 色別の正解率 色 人数 A B C D E F G H 灰 2699 96.4 % 76.9 % 33.8 % 8.3 % 0.3 % 0.0 % 0.0 % 0.0 % 茶 1281 99.2 % 98.4 % 80.4 % 38.2 % 0.6 % 0.1 % 0.0 % 0.0 % 緑 970 99.2 % 99.0 % 91.8 % 75.5 % 7.2 % 0.4 % 0.0 % 0.0 % 水 559 99.6 % 99.6 % 98.2 % 94.1 % 31.3 % 0.4 % 0.0 % 0.0 % 青 355 99.2 % 99.2 % 98.9 % 98.3 % 64.2 % 3.7 % 2.8 % 0.6 % 黄 189 93.1 % 92.6 % 93.1 % 92.6 % 68.8 % 12.2 % 18.0 % 1.6 % 橙 35 97.1 % 97.1 % 94.3 % 94.3 % 91.4 % 40.0 % 51.4 % 2.9 % 赤 28 100.0 % 100.0 % 100.0 % 96.4 % 92.9 % 64.3 % 82.1 % 50.0 % ※表示レート、灰に初参加者は含めず A問題『Distinct Strings』 問題ページ:A - Distinct Strings 灰コーダー正解率:96.4 % 茶コーダー正解率:99.2 % 緑コーダー正解率:99.2 % 入力 $S$ : 英小文字のみからなる長さ $3$ の文字列 考察 $S$ に何種類の文字が出てくるかで答えが決まります。 文字の種類数で答えがいくつになるかは、すべてサンプルに答えがあります。 $1$ 種類 : 1 (①①①) $2$ 種類 : 3 (②①①・①②①・①①② ${}{3} \mathrm{C}{1} = 3$ 通り) $3$ 種類 : $6$ (①②③・①③②・②①③・②③①・③①②・③②① $3!=3\times{2}\times{1}=6$ 通り) 実装 if文でがんばって文字の種類数を求めてもいいですが、len(set(S))と書くことで文字の種類数が簡単にわかります。setで重複を省いたあと、lenで重複なしの要素数=種類数を求めています。 コード S = input() N = len(set(S)) if N == 1: print(1) elif N == 2: print(3) else: print(6) B問題『Star or Not』 問題ページ:B - Star or Not 灰コーダー正解率:76.9 % 茶コーダー正解率:98.4 % 緑コーダー正解率:99.0 % 入力 $N$ : 木の頂点数 $a_i,b_i$ : $i$ 番目の辺は $a_i$ と $b_i$ を結んでいる 考察 『ある $1$ つの頂点から、他のすべての頂点に $1$ 本ずつ辺が出ている木』かどうか判定すればいいです。 言い換えると、『他の $N-1$ 個の頂点に $1$ 本ずつ辺が出ている頂点がある』かどうか判定すればいいです。 各頂点が何回辺に出現するかカウントして、$N-1$ 回出てくる頂点があれば Yes です。 コード N = int(input()) counter = [0] * (N + 1) for _ in range(N - 1): a, b = map(int, input().split()) counter[a] += 1 counter[b] += 1 print("Yes" if N - 1 in counter else "No") C問題『Calendar Validator』 問題ページ:C - Calendar Validator 灰コーダー正解率:33.8 % 茶コーダー正解率:80.4 % 緑コーダー正解率:91.8 % 入力 $N,M$ : 矩形(長方形)領域の行数・列数 $B_{i,j}$ : 矩形領域の、$i$ 行目 $j$ 列目に書いてある数字 考察 行列 $B$ が、カレンダーから矩形領域を向きを変えずに切り出したものである条件は、以下の $3$ つです。 カレンダーの一番左上が $1$ で始まっている 左右に隣り合う要素の差は $1$ 上下に隣り合う要素の差は $7$ 要素の差はforループで確かめればいいです。 一番左上が $1$ になる条件は、 要素を $7$ で割った余りが $0$ の要素が切り出した領域の一番右に来ていることです。(日曜日の右に月曜日があるカレンダーは、左上が月曜日になりません) コード def judge(): for col in range(M): if B[0][col] % 7 == 0 and col != M - 1: return False for row in range(N): for col in range(M - 1): if B[row][col] + 1 != B[row][col + 1]: return False for row in range(N - 1): for col in range(M): if B[row][col] + 7 != B[row + 1][col]: return False return True N, M = map(int, input().split()) B = [] for _ in range(N): _b = list(map(int, input().split())) B.append(_b) print("Yes" if judge() else "No") D問題『Play Train』 問題ページ:D - Play Train 灰コーダー正解率:8.3 % 茶コーダー正解率:38.2 % 緑コーダー正解率:75.5 % 入力 $N$ : 電車の数 $Q$ : クエリの数 クエリ $1$ $x,y$ : 電車 $x$ の後部と、電車 $y$ の前部を連結させる(電車 $x$ の後ろに電車 $y$ が来るように連結する) クエリ $2$ $x,y$ : 電車 $x$ の後部と、電車 $y$ の前部を分離させる(分離したあと、電車 $x$ が最後尾で、電車 $y$ が先頭になる) クエリ $3$ $x$ : 電車 $x$ が含まれる連結成分の電車の番号を、先頭から順番にすべて出力する ただし、クエリ $3$ で出力する電車の番号の個数の合計は、$10^6$ 以下 考察 双方向連結リストというデータ構造を実装すればいいです。 各電車ごとに、『前にある電車の番号』『後ろにある電車の番号』を記録しておきます。先頭で前に電車がない、最後尾で後ろに電車がない場合は、それがわかるように -1 を入れておきます。 クエリ $3$ が来たら、『前にある電車の番号』をたどって、先頭の電車まで移動します。その後、『後ろにある電車の番号』をたどって、順番に出力していけばいいです。 コード def main(): N, Q = map(int, input().split()) _next = [-1] * (N + 1) # すぐ後ろの電車の番号 _prev = [-1] * (N + 1) # すぐ前の電車の番号 for _ in range(Q): query = list(map(int, input().split())) q = query[0] if q == 1: x, y = query[1:] _next[x] = y # xの後ろにyがくる _prev[y] = x # yの前にxがくる elif q == 2: x, y = query[1:] _next[x] = -1 # xが新しい最後尾 _prev[y] = -1 # yが新しい先頭 else: x = query[1] ans = [] curr = x # まず電車を先頭までたどります while _prev[curr] != -1: curr = _prev[curr] # 先頭から最後尾までたどっていきます while curr != -1: ans.append(curr) curr = _next[curr] print(len(ans), *ans) # 連結成分の大きさも出力することに注意 if __name__ == '__main__': main()
- 投稿日:2021-10-31T19:52:54+09:00
djangoのcleaned_dataについてまとめてみた
今回のお題 今回はdjangoに用意されているcleaned_dataという変数についてまとめます。 この記事を書くに至った理由はCustomUserの実装にあたってcleaned_dataが必要だったからであり、基本的には自分用のメモとして残します。 cleaned_dataとは まず、cleaned_dataとは何かというと、「あるオブジェクトの属性値の中でバリデーションをクリアしたものだけを辞書形式で格納したもの」になります。 具体的にみていきましょう(ソースコードのimport部分は省略しています)。 class Hoge(models.Model): name = models.CharField(null=False, max_length=20) age = IntegerField() hoge = Hoge() hoge.name = "hoge" hoge.age = "30" print(hoge.cleaned_data) # 結果:{} # バリデーション前なのでcleaned_dataは空 hoge.is_valid() print(hoge.cleaned_data) # 結果:{ "name": "hoge" } # バリデーションをクリアしたnameのみが格納されている。 上記の例ではHogeクラスのインスタンスを作り、そこに対してバリデーションを実行しています。 ageはIntegerFieldであるにもかかわらず文字列"30"が代入されたためにcleaned_dataからはじかれています。 また、is_valid()関数が呼び出される前の時点ではcleaned_dataは空であることに気をつけてください。 終わりに 以上でcleaned_dataの解説を終わります。 この知識はCustom Userの実装過程でsaveメソッドを上書きするために必要です。 そちらについても近々まとめるのでよろしければお読みください。
- 投稿日:2021-10-31T18:48:51+09:00
[Python] Docker+Python+Seleniumでスクレイピング
はじめに 前回の記事でDockerコンテナでPython環境を作成しSeleniumを操作してみました。 今回はWebページのフォームで値を入力し、その結果の表示確認をしてみます。 SeleniumでWebページを操作する基本のような部分を備忘録として残すために記載したので見づらいかもしれませんが、そこはご了承ください。 実施すること 今回は下記の項目を実施してみようと思います。 1. Seleniumを使用しChromeを起動する。 2. Webページを表示する。 3. ページ内のボタン要素をクリックし、ページ遷移する。 4. 遷移後のページで必要な要素が表示されるまで待機する。 5. ページのp要素からテキストを取得する。 6. ページ内のボタン要素をクリックし、ダイアログボックスを表示させる。 7. ダイアログボックス内のinput要素に値を入力する。 8. ダイアログボックス内のdropdownメニューから値をセレクトする。 9. ダイアログボックス内のボタン要素をクリックし、データを送信する。 10. 送信完了のアラートが表示されるまで待機する。 11. アラート内のOKボタンをクリックする。 12. ページ内の情報を更新するため、ページのリロードを行う。 13. リロード後のページで必要な要素が表示されるまで待機する。 14. ページのp要素からテキストを取得する。 DockerでPythonのSelenium環境を構築する方法については前回記事([Python] Dockerコンテナでseleniumを使ってみる)を参考にしてみてください。 Seleniumを使ってみる 実装コード 今回実装したコードはこちらになります。 docker_selenium.py import time from selenium import webdriver from selenium.webdriver.common.alert import Alert from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # webのフォームに入力する内容を変数に格納 MILAGE = 38.38 ELEVATION = 218 WEATHER = ['晴れ', '曇り', '雨'] # webdriverのオプションを設定 options = webdriver.ChromeOptions() options.add_argument('--headless') print('Connect remote browser...') driver = webdriver.Remote(command_executor='172.21.0.3:4444/wd/hub', options=options) print('remote browser connected...') try: # ブラウザでWebページを開く driver.get('https://xxxxxxxx.herokuapp.com/') print('current URL: ', driver.current_url) # ボタンをクリックしページ遷移する処理 bicycle_button = driver.find_element(By.XPATH, '//input[@id="button_bicycle"]') bicycle_button.click() print('current URL: ', driver.current_url) # ページの読み込みのための最大待ち時間 print('Max 5 secound wait...') wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) # 新規データ入力前の今月の走行距離のテキストを取得する bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') print('bicycle_page_message_2 before: ', bicycle_page_message_2) # データをインプットするためのダイアログを開く処理 input_dialog_button = driver.find_element(By.XPATH, '//*[@id="button_to_input_dialog"]') input_dialog_button.click() print('input dialog open...') # ページの読み込みのための待ち時間 print('3 secound wait...') time.sleep(3) # 走行距離を入力するinputタグ要素を取得 input_milage = driver.find_element(By.XPATH, '//*[@id="input_milage"]') # 入力内容をクリア(何も入力されていないけど念のため) input_milage.clear() # 入力内容をセット input_milage.send_keys(MILAGE) # 獲得標高を入力するinputタグ要素を取得し、値を入力 input_elevation = driver.find_element(By.XPATH, '//*[@id="input_elevation"]') input_elevation.clear() input_elevation.send_keys(ELEVATION) # ドロップダウン要素を取得する dropdown = driver.find_element(By.XPATH, '//*[@id="select_weather"]') # ドロップダウン要素からSelectオブジェクトを作成する select_weather = Select(dropdown) # ドロップダウンの値を表示されているテキストを選択して記入する select_weather.select_by_visible_text(WEATHER[0]) # DBへの入力(Input)ボタンをクリックする処理 input_button = driver.find_element(By.XPATH, '//*[@id="button_input_to_db"]') input_button.click() print('Input button click...') # データが送信されalert表示のための最大待ち時間 print('Max 10 secound wait...') alert_wait = WebDriverWait(driver, 10) alert_wait.until(EC.alert_is_present()) # アラートのokをクリックする処理 Alert(driver).accept() print('alert ok button click...') # ページのリロード driver.refresh() print('reload page...') # ページの読み込みのための最大待ち時間 print('Max 5 secound wait...') wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) # 新規データ入力後の今月の走行距離のテキストを取得する bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') print('bicycle_page_message_2 after: ', bicycle_page_message_2) except Exception as e: print('Exception: ', e) finally: # リモートサーバーとの接続を終了する print('Connection stop...') driver.quit() 都合上、WebサイトのURLを一部非表示にしています。 try,except,finally文についてはドライバーを確実に終了させる目的で記載しましたので正確な位置に記述されていないかもしれません。 おかしな点についてはご指摘等いただければありがたいです。 それでは各項目の処理について詳細を見てみます。 XPathについて 各項目の処理を見る前に、Seleniumで要素を指定する際によく使用されるXPathについて解説します。 XPathはXML文章中の要素、属性値などを指定するための言語で、XML文章をツリーとして捉えることで、要素や属性の位置を指定します。 要素の指定はロケーションパスを指定する方式を使用し、属性の指定には@マークを使用します。 また、HTMLもXMLの一種とみなすことができるため、XPathを使ってHTML文章中の要素を指定することができます。 ● 記述方法 対象の要素にid, class等の属性がある場合と無い場合で若干記述の仕方が変わります。 対象の要素にid, class等の属性がある場合 //p[@id="sum_milage"] 対象の要素を指定する形で記述します。 分解して内容を見てみると、 // : ノードpathを記述する。(「//」はルートからのpathの省略形) p : 要素の種類を記述 @id : 「@」で要素の属性を指定する。classを指定する時は@class="" ノードpathはhtmlからのフルパスで記述しても問題ありません。 → 例). html/body/div/p 対象の要素に属性がない場合 //*[@id="top_block"]/p 対象の要素に属性がない場合はその要素の親要素を指定し、その親要素の配下にある〇〇要素という形で記述します。 同じくこの要素を分解すると、 * : 全ての要素を選択 [@id=""]: 対象要素の親要素を指定する /p : 「/」の前で取得した要素の配下の要素を取得している 今回の処理では全ての要素の中からidがtop_blockの要素を取得し、その配下のpタグ要素を取得しています。 同じタグ要素が複数ある時はリスト形式で表現します。 → 例) //*[@id="top_block"]/p[3] ● Chromeの検証ツールからコピーする方法 XPathは下記手順でChromeの検証ツールからコピーしてくることも出来ます。 1. 対象ページの開発者ツールのElementを確認する 2. 必要な要素を選択する 3. 右クリックでコンテキストメニューを開く 4. Copy → Copy XPATHを選択する 5. エディタに貼り付ける 比較的簡単にコピーできるし、ノードpathや属性のスペルミスも無くなるのでこの方法も便利です。 XPathの他のメソッドについてはこちらのサイト(クローラ作成に必須!XPATHの記法まとめ)を参考にしてみてください。 それでは実際の処理を見てみます。 webページを表示する 正確にはブラウザドライバーでリクエストしたwebページのレスポンスを取得する処理です。表現の都合上、表示と記載します。 抜粋 from selenium import webdriver options = webdriver.ChromeOptions() driver = webdriver.Remote(command_executor='172.21.0.3:4444/wd/hub', options=options) # ブラウザでWebページを開く driver.get('https://xxxxxxxx.herokuapp.com/') ライブラリからseleniumのwebdriverをimportします。 オプションでどのブラウザを起動するか指定します。今回はChromeドライバーを設定。 別コンテナに作成しているリモートサーバーのオブジェクトを生成し、get()メソッドにURLを引数として渡してあげればWebページを表示できます。 ボタン要素をクリックする 赤枠内のボタンをクリックする処理です。 抜粋 bicycle_button = driver.find_element(By.XPATH, '//input[@id="button_bicycle"]') bicycle_button.click() ボタン要素をクリックするには、find_element()メソッドで対象要素のオブジェクトを生成し、click()メソッドを呼び出します。 要素からテキストを取得する 赤枠内のp要素から青文字のテキストを取得する処理です。 抜粋 bicycle_page_message_2 = driver.find_element(By.XPATH, '//p[@id="sum_milage"]').get_attribute('textContent') 要素のテキストを取得するにはfind_element()メソッドで対象要素のオブジェクトを生成し、get_attribute()メソッドを呼び出します。 他にも.textでテキストを取得できるようですが、属性で非表示に指定されている場合(display:none 等)は取得できないようなので、属性に関係なくテキストを取得できるget_attribute()メソッドが良いかと思います。 input要素に値を入力する 画像の赤枠内の①と②に値を入力する処理です。 抜粋 # 入力したい値を変数に格納 MILAGE = 38.38 ELEVATION = 218 # ①の処理 # 走行距離を入力するinputタグ要素を取得 input_milage = driver.find_element(By.XPATH, '//*[@id="input_milage"]') # 入力内容をクリア(何も入力されていないけど念のため) input_milage.clear() # 入力内容をセット input_milage.send_keys(MILAGE) # ②の処理 # 獲得標高を入力するinputタグ要素を取得し、値を入力 input_elevation = driver.find_element(By.XPATH, '//*[@id="input_elevation"]') input_elevation.clear() input_elevation.send_keys(ELEVATION) ①と②は同じ処理をしています。 input要素に値を入力するにはfind_element()メソッドで対象要素のオブジェクトを生成し、send_keys()メソッドを呼び出し、引数に入力したい値を渡し実行すれば値の入力が出来ます。 input要素に値がセットされている場合には事前にclear()メソッドで値をクリアしますが、値が入力されていない要素でもバグ防止のため実施しておいた方が無難かと思います。 dropdownメニューで項目を選択する 画像の赤枠内③のdropdownメニューで項目を選択する処理です。 抜粋 # 必要なライブラリのimport from selenium.webdriver.support.select import Select # 入力したい値を変数に格納 WEATHER = ['晴れ', '曇り', '雨'] # ドロップダウン要素を取得する dropdown = driver.find_element(By.XPATH, '//*[@id="select_weather"]') # ドロップダウン要素からSelectオブジェクトを作成する select_weather = Select(dropdown) # ドロップダウンの値を表示されているテキストを選択して記入する select_weather.select_by_visible_text(WEATHER[0]) dropdown要素で値を選択するには、ライブラリからSelectクラスをimportしSelectインスタンスを作成します。 Selectインスタンスで値を選択する方法はいくつかありますが、今回はdropdownメニューの項目に表示されているテキストで指定する方法でコーディングしています。 Selectクラスに用意されているselect_by_visible_text()メソッドに選択したいメニューのテキストを渡してあげればその項目が選択された状態になります。 他の方法についてはこちらのサイト(Selenium API(逆引き)・・・Selenium APIを利用目的から検索できます)を参考にしてみてください。 Alertの「OK」ボタンをクリックする 画面に表示されるAlertのオブジェクトを取得し「OK」ボタンをクリックする処理です。 スクショは取れなかったので画像はありません。 抜粋 from selenium.webdriver.common.alert import Alert # データが送信されalert表示のための最大待ち時間 alert_wait = WebDriverWait(driver, 10) alert_wait.until(EC.alert_is_present()) # アラートのokをクリックする処理 Alert(driver).accept() ライブラリからAlertクラスをimportします。 Alertインスタンスを生成する際にdriverオブジェクトを渡し、accept()メソッドを実行すればOKボタンのクリックが完了します。 またAlertが画面に表示されるのを待つ処理について、Alertの表示確認にはalert_is_present()メソッドを使用します。 最大待ち時間を指定して処理を止める ページ内の要素の読み込みやAlertの表示を待ったりと、ページ内処理を待つ際に使えるメソッド。 抜粋 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 5) wait.until(EC.visibility_of_element_located((By.XPATH, '//p[@id="sum_milage"]'))) まずライブラリからWebDriverWaitとexpected_conditionsをimportします。 expected_conditionsをECとするのはSeleniumのお作法みたいですね。 WebDriverWaitインスタンスを作成し、waitオブジェクトを作成します。その際、第二引数に処理が完了するまでの「最大」待ち時間を指定します。 そしてオブジェクトの持っているuntil()メソッドで望む状態になっているか確認します。 until()メソッドの引数には、expected_conditionsのメソッドでXPathで指定した要素に対する期待動作を記述します。 今回はvisibility_of_element_located()メソッドで、指定要素が表示されているかを確認しています。 XPathで指定した要素が画面に表示されているのが確認できたらuntil()メソッドは正常終了し次の処理に移ります。 もしWebDriverWaitインスタンスを作成する際に指定した最大待ち時間を過ぎても指定要素が画面に表示されなかったらExceptionが返されます。 *標準ライブラリtimeのsleep()との違いは、処理の完了が確認できれば最大時間待つことなく次の処理に移ることです。 ページをリロードする 抜粋 driver.refresh() ページをリロードするには、driverオブジェクトのrefresh()メソッドを使用します。 実行してみる 実際に処理が動作するか、コードを実行して確認してみます。 まずターミナル上で確認してみます。 root@9f672ec558d8:/work# python3 docker_selenium.py Connect remote browser... remote browser connected... current URL: https://xxxxxxxx.herokuapp.com/ current URL: https://xxxxxxxx.herokuapp.com/bicycle_contents Max 5 secound wait... bicycle_page_message_2 before: 今月は 2036.59 km 走っています。 input dialog open... 3 secound wait... Input button click... Max 10 secound wait... alert ok button click... reload page... Max 5 secound wait... bicycle_page_message_2 after: 今月は 2074.97 km 走っています。 Connection stop... bicycle_page_message_2のbefore, afterで走行距離が変化していますね。 その他の処理も問題なく動いているようです。 次にデータベース(MySQL)のテーブル内のデータも実行前後で確認してみます。 実行前 MySQL | 12044 | 2021-10-29 | 22.75 | 318 | 71 | 224 | | 12054 | 2021-10-29 | 22.27 | 173 | 71 | 224 | +-------+---------------------+--------+-----------+------------+-----------+ 1197 rows in set (9.14 sec) mysql> 実行後 MySQL | 12044 | 2021-10-29 | 22.75 | 318 | 71 | 224 | | 12054 | 2021-10-29 | 22.27 | 173 | 71 | 224 | | 12064 | 2021-10-30 | 38.38 | 218 | 71 | 224 | +-------+---------------------+--------+-----------+------------+-----------+ 1198 rows in set (9.16 sec) mysql> rowの数が1つ増え、新しい日付のデータが追加されています。 送信ボタンがタップすることでデータが送信されてテーブルに反映されていることが確認できますね。 最後に画面上でも確認してみます。 赤枠内の青文字のp要素のデータが更新されていることが確認できました。 ページをリロードする処理が正常に動作し、新しいテキストデータを取得できることが確認できました。 おわりに たぶん今回試してみたことが出来ればSeleniumでスクレイピングやテストを行うための基本的な部分はカバーできるかと思います。 あとは記事としてアップするか分かりませんが、マウスオーバーに対する処理やテスト時のUIチェックのための動画撮影等は応用的な感じになるかと思いますが、応用可能だと思います。 ご覧いただきありがとうございました。 参考サイト クローラ作成に必須!XPATHの記法まとめ XPathの記法について詳しく記載されています。 要素の指定の際、かなり参考になりました。ありがとうございます。 Selenium API(逆引き)・・・Selenium APIを利用目的から検索できます seleniumの画面操作について細かく記載されていて、かなり参考になりました。ありがとうございます。 現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル Udemy, Youtube, Twitterで活躍中の酒井潤さんのpython入門。 内容が濃くかつ教え方が丁寧なのですごく分かりやすいです。 pythonについて学びたい方は一度は見た方が良いお勧めの動画です。 pythonの実装で迷った時やコーディングスタイルで迷った時はいつもこの動画で動画で復習しています。ありがとうございます。
- 投稿日:2021-10-31T18:04:02+09:00
細かいつまずいたことをメモしておく(10月編)
はじめに 2021年10月も仕事や勉強でつまづいたことをまとめて自分用のメモとしてあげておきます。 調べればすぐわかる程度で私が記事にする必要はないなと思ったもです。 今月は仕事の繁忙期を乗り切り(来月再来予定)、比較的落ち着いた1か月となりました。 しかし、Windows11が始まるなどのトラブルが起きており、それに関連した問題に対処しました。 問題 1. GoogleColabratoryでMeCab+Neologdを使いたい こちらの記事を参考にした。 インストールは以下のコマンド !apt-get -q -y install sudo file mecab libmecab-dev mecab-ipadic-utf8 git curl python-mecab > /dev/null !git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git > /dev/null !echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n > /dev/null 2>&1 !pip install mecab-python3 > /dev/null !ln -s /etc/mecabrc /usr/local/etc/mecabrc 作りながら学ぶ!PyTorchによる発展ディープラーニングで以下のNeologdのパスを指定していたがColabだと若干違うので修正。 7-1_Tokenizer.ipynb import MeCab m_t = MeCab.Tagger('-Ochasen -d /usr/lib/mecab/dic/mecab-ipadic-neologd') text = '機械学習が好きです。' print(m_t.parse(text)) ↓ import MeCab m_t = MeCab.Tagger('-Ochasen -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd') text = '機械学習が好きです。' print(m_t.parse(text)) パスは以下のコマンドで分かりました。 !echo `mecab-config --dicdir`"/mecab-ipadic-neologd" 2. AttributeError: module 'torchtext.data' has no attribute 'Field' torchtextを利用したところ AttributeError: module 'torchtext.data' has no attribute 'Field' というエラーが発生した。このエラーは新しいバージョンで起こるらしい。 解決方法としてネットにあったのは from torchtext import data ↓ from torchtext.legacy import data に変更するのだが、今のバージョンではエラーが発生する。 バージョンを下げてインストールすることで解決(?)した pip install torchtext==0.8.1 3. Railsでモデルで名前付きルーティングを使う Rails.application.routes.url_helpersを使う 4. PytorchでGPUを使えるようにしたかった 自分で持っているGPUがGTX 1650と1080Tiで、書籍を読んでいるとRTXの2000系と3000系の設定の仕方はあるが、GTXは載っていなかった。 調べたところGTXの1000番台はそもそも対応していないっぽい ただし、GTX 1650なんかは2000番台。それだけは使えた。 【GPGPU】くだすれCUDAスレ part8【NVIDIA】 [無断転載禁止]©2ch.net 5. Railsでredirect先を変数の値によって変更したい A(a_path)、B(b_path)、C(c_path)という画面があり、そこには会員一覧があるとします。 どのページかを開いて、会員を1人削除すると、destroyのアクションが実行されて、会員が削除されたあとに、削除したときに開いていたページにリダイレクトしたかったとします。 そこで、destoryにパラメータでどのページかの情報を与え、params[:page](Aならparams[:page]=a_path)で取得できるようにして、 redirect_to params[:page] のようにしましたが、うまくいきませんでした。このように変数を利用したい場合は、redirect_to action: "アクション名"をすることでできます。 params[:page]にアクション名をいれて渡します。そのあと以下のようにします。 redirect_to action: params[:page], .... action: をしていすれば文字列が使えることがわかりました。 6. jupyter_notebook_config.pyを読み込まない jupyter_notebook_config.pyをJupyterが読み込まなくなっていた。 原因はコピーの場所だった。 COPY ./config/jupyter_notebook_config.py /root/.jupyter/jupyter_notebook_config.py ↓ COPY ./settings/jupyter_notebook_config.py .jupyter/ 7. Windows11による影響 ブルースクリーン多発 Windows 10 Insider Preview 10.0.22478.1012 (rs_prerelease) をインストールしたこと起動不可に。とりあえずインストールを戻しました。 VSCodeでターミナルがおかしい。 いつもPowershellでcmdコマンドでコマンドプロンプトを立ち上げるが、なぜかコマンドが効かない。 普通にPowershellを開くと問題なくできる。 コマンドプロンプトもなぜかcondaコマンドが効かない。普通に立ち上げるとできる。 設定を同期したうえでVSCodeをインストールしなおしたところできるようになった。 ビルドの期限が切れます ビルドの期限が切れるとのことでアップデートしてもブルースクリーンになってしまった。 そこで、devチャンネルからbetaチャンネルに乗り移ったところ解決。 なぜかdevしか選択できなかったので以下のサイトを参考に変更した。 Windows 11 Insider、チャネル変更が出来ない場合の対処方法。 検索窓で「レジストリ エディタ」を開いて設定が可能。 8. Rspecでリダイレクトしてしまうからエラーが調べられない そうだ、flash[:alert]をみればいいんだった。 おわりに 今月は比較的忙しくなく、与えられたタスクをこなしながら忘年会の準備をしておりました。 このメモ書きですが、案外確認することがありまとめておくべきだなと思いました。 最近わからないことができたらTwitterで検索することが多く、たまに解決することもあるのでツイートでまとめておくのも手段だなと思いました。忙しくてもアウトプットすることは忘れずにやっていきたいです。 ついに来月から案件が再度盛り上がる予定なので、がんばっていきたいところです。それと1年半続いた在宅も終了で出社となりました。。 Windows11のビルドが10月いっぱいで対応したつもりですが、明日パソコンが起動できなくなっていませんようにと一応お祈りして終わりにしたいと思います。 参考 Google ColabにMeCabとipadic-NEologdをインストールする 第8章 p.433 AttributeError: module 'torchtext.data' has no attribute 'Field' ModuleNotFoundError: No module named 'torchtext.data.field' 【GPGPU】くだすれCUDAスレ part8【NVIDIA】 [無断転載禁止]©2ch.net Windows 11 Insider、チャネル変更が出来ない場合の対処方法。
- 投稿日:2021-10-31T17:57:55+09:00
ABC225 チャレンジ結果
ABC225結果:AB(3)C3完 今回から後半問題の取り組みも期待してC++でやってみた...が手にまだ馴染んでないようで途中からPythonに切り替えたりとてんやわんやであった. A:ちょっとした高校数学. 3! = 6 or 3!/2! = 3 or 3!/3! = 1のいずれか. Distinct_Strings.cpp #include <iostream> #include <vector> #include <algorithm> #include <set> using namespace std; int main(){ set<char> L; string S; cin >> S; for (int i=0;i<3;i++){ L.insert(S[i]); } int sz = L.size(); //cout << sz; if (sz == 3) cout << 6; if (sz == 2) cout << 3; if (sz == 1) cout << 1; } B:入力されたタイミングでその情報を格納すれば瞬殺だった. だが入力を愚直にvectorに入れ, vectorの要素の重複個数を数え, それがN-1になったとき...とやってるとTLEを喰らいました. B問題でかつC++で書いてTLEは焦りを隠せなかった. Star_or_Not.cpp #include <iostream> #include <vector> #include <algorithm> #include <set> #include <map> using namespace std; int main(){ int N; cin >> N; vector<int> a; map<int, int> T; for (int i=0;i<N-1;i++){ int ai, bi; cin >> ai >> bi; T[ai]++; T[bi]++; //a.push_back(ai); //a.push_back(bi); } for (int i=1;i<=N;i++){ if (T[i] == N-1){ cout << "Yes" << endl; break; } else if (T[i] >= 2){ cout << "No" << endl; break; } } } C:Bで焦ってC問題は馴染んでるPythonでやることに. やっぱPythonすんげえ楽 Calendar_Validator.py N, M = list(map(int, input().split())) B = [] for i in range(N): X = list(map(int, input().split())) B.append(X) O = B[0][0] n = (O - 1) // 7 + 1 m = (O - 1) % 7 + 1 flag = True C = [] if m + M > 8: flag = False print("No") else: for i in range(N): Y = [O + 7 * i + j for j in range(M)] if Y != B[i]: flag = False print("No") break if flag == True: print("Yes") Dは連結リストの考え方そのままやん!!と思いながらもコードに落とせず無念. レートはちょっと上がってギリギリ茶色になりました. 最近停滞気味なので次こそは!
- 投稿日:2021-10-31T17:57:29+09:00
AWS boto3サンプルコード
はじめに boto3でAWSリソースにアクセスするサンプルコードの覚書きです。 S3 S3のサンプルコード集 KeyObjectの全取得 指定バケットのすべてのContentsをListで取得します。 list_objects_v2は一度に最大1000件まで取得するので、バケット内のObjectをすべて取得するにはTokenで制御する必要があります。 python:sample.py from typing import List import boto3 def get_s3_objects(bucket: str, key_prefix: str = "", **kwargs) -> List[dict]: result: list = [] params: dict = {"Bucket": bucket, "Prefix": key_prefix} params.update(kwargs) while True: response = s3_client.list_objects_v2(**params) if "Contents" in response: result.extend(response["Contents"]) if "NextContinuationToken" in response: params["ContinuationToken"] = response["NextContinuationToken"] else: break return result Contentsには、ObjectKey名のほかSizeなどが含まれます。 Contentsの内容は公式ドキュメント参照: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.list_objects_v2 KeyObjectの全取得(応用) KeyObjectの全取得で得た結果をファイル名とファイルサイズに整形するサンプル。 結果にはフォルダタイプのオブジェクトが含まれているので除外し、ファイルサイズは人読みやすいようにマネジメントコンソール画面での表記(ex: 12.3 KB)でフォーマットします。 python:sample.py def get_s3_files_size(bucket: str, key_prefix: str = "", **kwargs) -> dict: objects = get_s3_objects(bucket, key_prefix, **kwargs) # Remove objects end with "/", means not file. return { o["Key"]: set_number_unit(o["Size"]) for o in objects if not o["Key"].endswith("/") } def set_number_unit(num: int) -> str: units = { "TB": 1024 ** 4, "GB": 1024 ** 3, "MB": 1024 ** 2, "KB": 1024, "B": 1, } for unit, size in units.items(): if num >= size: return f"{(num/size):.1f} {unit}" else: return f"{num} B"
- 投稿日:2021-10-31T17:43:18+09:00
LINE Message API でメッセージするテスト
タイトルの通りですが、初めてやったので、メモです。 LINE Developerの登録 → チャネル作成 いろんなところに記事がありますので、省略しますが、 ログイン後、「プロバイダ」を作り、「チャネル」(BOTですね)を作ります。 アクセストークンの取得 「Message API」タブの下のほうで、「チャネルアクセストークン(長期)」を取得しておきます。 また、 「チャネル基本設定」タブの下の方の「あなたのユーザID」を控えておきます。 メッセージするプログラム 今回は、Pythonで作ってみました。 Macで作ったんですが、ちょっと手間取りました。 ライブラリのインストール line-bot-sdkをインストールします。 % pip install line-bot-sdk % pip show line-bot-sdk ソース 「line-push.py」として保存しました。 import sys sys.path.append('/Users/[ほげほげユーザー]/Library/Python/3.8/lib/python/site-packages') from linebot import LineBotApi from linebot.models import TextSendMessage from linebot.exceptions import LineBotApiError LINE_ACCESS_TOKEN= "アクセストークン" LINE_USER_ID= "自分のチャネルのUser_id" line_bot_api = LineBotApi(LINE_ACCESS_TOKEN) text_message ="テストでメッセージを送るのです。" try: # User_idは配列で指定します。 line_bot_api.multicast(['送りたい人のUser_id'], TextSendMessage(text=text_message) ) except LineBotApiError as e: # エラーのとき print(e) 最初、1−2行目がなかったんですが、動かなくって、いろいろググった結果、前項の % pip show line-bot-sdk コマンドで確認したパスを、行頭に付け足すことで改善しました。 「送りたい人のUser_id」 には、別投稿で取得したUser_idを入れます。 実行 % python3 line-push.py 送信できました! やったー。 おしまい。
- 投稿日:2021-10-31T16:26:20+09:00
LambdaからRDSへ接続してみる
ご挨拶 タイトルの通りLambdaからRDSへ接続してみたので記事を書いていきます。 最近業務でLambdaを使用することが増えてきたので勉強になればと思いやってみました。 やること LambdaでプライベートサブネットにあるRDS MySQLに向けてSQLを実行して実行結果を確認します。 今回RDSの作成はCloudFormationを使用しています。 事前にプライベートサブネットを二つ作成しています。 以下の手順で進んでいきます。 Lambda関数の作成 IAMロールの編集 RDSの作成 LambdaをRDSに接続できるように設定 テスト実行 構成図 参考にしたサイト チュートリアル: Amazon VPC の Amazon RDS にアクセスする Lambda 関数の設定 pymysqlのドキュメント やってみる 1. Lambda関数の作成 マネジメントコンソールからLambdaへ移動 移動後、関数→関数の作成をクリック クリック後上から順に以下の画像のように選択と入力を行います 関数名はお好みで大丈夫です。 今回はPythonを使用して関数を作成するのでランタイムはPython3.9を選択しました。 IAMロールは新しく作成してくれるものを選択することでCloudWatch logsへのアクセス権をいい感じに絞ったものが作成されます。 作成が完了したら一旦マネジメントコンソールでの作業は終わりでコードを作成します。 コード作成 pythonでMySQLの操作をするのにpymysqlというモジュールを使用します。 pymysqlはLambdaのデフォルトでは使用できないのでモジュールをzipにしてアップロードします。 lambda_function.py import os import pymysql host = os.environ['RDS_HOST_NAME'] user = os.environ['USER'] password = os.environ['PASS'] db = os.environ['DB'] connection = pymysql.connect(host=host, user=user, password=password, database=db) def lambda_handler(event, context): with connection.cursor() as cursors: cursors.execute('show databases') print(cursors.fetchall()) 作成されているスキーマを取得するものを作成しました。 execute()でSQLを実行して結果をfetchall()で取ってきています。 ここまで出来たらファイルを保存します。 保存したディレクトリ(フォルダ)と同じ階層にpymysqlを入れます。 以下のコマンドを実行します。 powershell pip install PyMySQL -t /ファイルがあるディレクトリ もしくはファイルがあるディレクトリに移動して以下のコマンド実行 pip install PyMySQL -t ./ インストールができたらファイルとモジュールを一つのzipにします。 zipができたらマネジメントコンソールに戻ってアップロードします。 以下の画面からアップロード元→.zipファイル→アップロードの順でクリックして上記手順で作成したzipファイルをアップロードします。 2. IAMロールの編集 上記1まで出来たらIAMロールを編集します。 RDSにアクセスできるようにするにはENIが必要なためIAMでアクセス権を与えます。 以下の画面の設定→アクセス権限→実行ロールのところでLambdaにアタッチされているIAMロールが確認できます。 そこからIAMロールをクリックすると編集ができます。 クリックするとIAMの画面に移動します。 移動したら「ポリシーをアタッチします」をクリックしてIAMポリシーを選択します。 選択するポリシーはAWSLambdaVPCAccessExecutionRoleです。 アタッチできたらIAMの設定は終了です。 3. RDSの作成 RDSの作成は以下のyamlで作成しました。 CloudFormationの使用方法は以下の記事の「CloudFormationでスタック作成」をご覧ください。 【AWS】 CloudFormationで基本的な構成のEC2とRDSを作る create_rds.yml AWSTemplateFormatVersion: "2010-09-09" Description: create rds #パラメータ Parameters: vpcid: Type: AWS::EC2::VPC::Id Description: Enter vpcid subnetid1: Type: AWS::EC2::Subnet::Id Description: Enter RDS subnetid subnetid2: Type: AWS::EC2::Subnet::Id Description: Enter RDS subnetid rdssubnetgroupname: Type: String Description: Enter RDS subnet groupname rdsdbname: Type: String Description: Enter RDS DBname rdsusername: Type: String Description: Enter RDS username AllowedPattern: '[a-zA-Z0-9]*' rdspassword: Type: String Description: Enter RDS password MinLength: '8' MaxLength: '41' AllowedPattern: '[a-zA-Z0-9]*' #リソース Resources: lambdasecuritygroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: lambda-securitygroup SecurityGroupEgress: - IpProtocol: -1 FromPort: -1 ToPort: -1 CidrIp: 0.0.0.0/0 VpcId: !Ref vpcid rdssecuritygroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: rds-securitygroup SecurityGroupEgress: - IpProtocol: -1 FromPort: -1 ToPort: -1 CidrIp: 0.0.0.0/0 SecurityGroupIngress: - IpProtocol: tcp FromPort: 3306 ToPort: 3306 SourceSecurityGroupId: !Ref lambdasecuritygroup VpcId: !Ref vpcid rdssubnetgroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: rdssubnetgroup SubnetIds: - !Ref subnetid1 - !Ref subnetid2 DBSubnetGroupName: !Ref rdssubnetgroupname Tags: - Key: Name Value: rdssubnetgroup rdsinstance: Type: AWS::RDS::DBInstance Properties: AllocatedStorage: 20 DBInstanceClass: db.t2.micro DBSubnetGroupName: !Ref rdssubnetgroup DBName: !Ref rdsdbname Engine: mysql EngineVersion: "5.7.34" MasterUsername: !Ref rdsusername MasterUserPassword: !Ref rdspassword StorageType: gp2 Tags: - Key: Name Value: rdsinstance VPCSecurityGroups: - !Ref rdssecuritygroup 上記のテンプレートを使用してプライベートサブネットにRDSを作成します。 4. LambdaをRDSに接続できるように設定 RDSの作成ができたら環境変数を登録していきます。 Lambda関数の設定画面から環境変数→編集をクリック キーと値には以下の4つを設定します。 キー 値 RDS_HOST_NAME RDSのエンドポイント USER MySQLのユーザ (CloudFormationで作成時に入力したユーザ) PASS MySQLのパスワード (CloudFormationで作成時に入力したパスワード) DB DB名 (CloudFormationで作成時に入力したDB名) 次にVPCの設定を行います。 Lambdaの設定→VPC→編集をクリック クリックしたら上からVPC、サブネット、セキュリティグループを選択します。 VPCはRDSと同じVPCを選択します。 サブネットはプライベートサブネットを選択します。 セキュリティグループはCloudFormationで作成したlambda-securitygroupを選択します。 5. テスト実行 上記まで完了したらテスト実行してみます。 コード→Testをクリックします。 テストを実行してFunction Logsに以下のデータベースが出力されれば成功です。 データベース (('information_schema',), ('innodb',), ('lambda_to_rds',), ('mysql',), ('performance_schema',), ('sys',)) 感想 information_schemaが見れるので各テーブルのレコード数やAutoIncrementの数値を取ってきてCloudWatchメトリクスに登録するなどの活用ができそうだと思いました。(小並感) 使用方法によってはRDS Proxyというものを使用してLambdaからの大量の接続を維持することも検討する必要があるようです。 AWS LambdaでAmazon RDS Proxyを使用する
- 投稿日:2021-10-31T16:20:53+09:00
オムロン環境センサ(2JCIE-BU)をラズパイで使ってみた。(8)
Juliusをサーバとして動作させる。 -moduleオプションをつけてJuliusを起動するとsocket接続したアプリケーションに認識結果をXML形式のデータで出力するようになります。 pi@raspberrypi:~ $ julius -C ~/julius/dictation-kit-4.5/am-gmm.jconf -nostrip -gram ~/julius/dict/sensor -module 実行ログ pi@raspberrypi:~ $ julius -C ~/julius/dictation-kit-4.5/am-gmm.jconf -nostrip -gram ~/julius/dict/sensor -module STAT: include config: /home/pi/julius/dictation-kit-4.5/am-gmm.jconf WARNING: m_chkparam: "-lmp" only for N-gram, ignored WARNING: m_chkparam: "-lmp2" only for N-gram, ignored STAT: jconf successfully finalized STAT: *** loading AM00 _default Stat: init_phmm: Reading in HMM definition Stat: binhmm-header: variance inversed Stat: read_binhmm: has inversed variances Stat: read_binhmm: binary format HMM definition Stat: read_binhmm: this HMM does not need multipath handling Stat: init_phmm: defined HMMs: 8443 Stat: init_phmm: loading binary hmmlist Stat: load_hmmlist_bin: reading hmmlist Stat: aptree_read: 42857 nodes (21428 branch + 21429 data) Stat: load_hmmlist_bin: reading pseudo phone set Stat: aptree_read: 3253 nodes (1626 branch + 1627 data) Stat: init_phmm: logical names: 21429 in HMMList Stat: init_phmm: base phones: 43 used in logical Stat: init_phmm: finished reading HMM definitions STAT: pseudo phones are loaded from binary hmmlist file Stat: hmm_lookup: 12 pseudo phones are added to logical HMM list STAT: *** AM00 _default loaded STAT: *** loading LM00 _default STAT: reading [/home/pi/julius/dict/sensor.dfa] and [/home/pi/julius/dict/sensor.dict]... Stat: init_voca: read 8 words STAT: reading additional forward dfa [/home/pi/julius/dict/sensor.dfa.forward] STAT: done STAT: Gram #0 sensor registered STAT: Gram #0 sensor: new grammar loaded, now mash it up for recognition STAT: Gram #0 sensor: extracting category-pair constraint for the 1st pass STAT: Gram #0 sensor: installed STAT: Gram #0 sensor: turn on active STAT: grammar update completed STAT: *** LM00 _default loaded STAT: ------ STAT: All models are ready, go for final fusion STAT: [1] create MFCC extraction instance(s) STAT: *** create MFCC calculation modules from AM STAT: AM 0 _default: create a new module MFCC01 STAT: 1 MFCC modules created STAT: [2] create recognition processing instance(s) with AM and LM STAT: composing recognizer instance SR00 _default (AM00 _default, LM00 _default) STAT: Building HMM lexicon tree STAT: lexicon size: 120+0=120 STAT: coordination check passed STAT: multi-gram: beam width set to 120 (guess) by lexicon change STAT: wchmm (re)build completed STAT: SR00 _default composed STAT: [3] initialize for acoustic HMM calculation Stat: outprob_init: state-level mixture PDFs, use calc_mix() Stat: addlog: generating addlog table (size = 1953 kB) Stat: addlog: addlog table generated STAT: [4] prepare MFCC storage(s) STAT: [5] prepare for real-time decoding STAT: All init successfully done Stat: server-client: socket ready as server /////////////////////////////// /// Module mode ready /// waiting client at 10500 /////////////////////////////// /// Stat: server-client: connect from 127.0.0.1 STAT: ###### initialize input device ----------------------- System Information begin --------------------- JuliusLib rev.4.6 (fast) Engine specification: - Base setup : fast - Supported LM : DFA, N-gram, Word - Extension : LibSndFile - Compiled by : gcc -g -O2 -fPIC Library configuration: version 4.6 - Audio input primary A/D-in driver : alsa (Advanced Linux Sound Architecture) available drivers : alsa wavefile formats : various formats by libsndfile ver.1 max. length of an input : 320000 samples, 150 words - Language Model class N-gram support : yes MBR weight support : yes word id unit : short (2 bytes) - Acoustic Model multi-path treatment : autodetect - External library file decompression by : zlib library - Process hangling fork on adinnet input : no - built-in SIMD instruction set for DNN NONE AVAILABLE, DNN computation may be too slow! - built-in CUDA support: no ------------------------------------------------------------ Configuration of Modules Number of defined modules: AM=1, LM=1, SR=1 Acoustic Model (with input parameter spec.): - AM00 "_default" hmmfilename=/home/pi/julius/dictation-kit-4.5/model/phone_m/jnas-tri-3k16-gid.binhmm hmmmapfilename=/home/pi/julius/dictation-kit-4.5/model/phone_m/logicalTri-3k16-gid.bin Language Model: - LM00 "_default" grammar #1: dfa = /home/pi/julius/dict/sensor.dfa dict = /home/pi/julius/dict/sensor.dict Recognizer: - SR00 "_default" (AM00, LM00) ------------------------------------------------------------ Speech Analysis Module(s) [MFCC01] for [AM00 _default] Acoustic analysis condition: parameter = MFCC_E_D_N_Z (25 dim. from 12 cepstrum + energy, abs energy supressed with CMN) sample frequency = 16000 Hz sample period = 625 (1 = 100ns) window size = 400 samples (25.0 ms) frame shift = 160 samples (10.0 ms) pre-emphasis = 0.97 # filterbank = 24 cepst. lifter = 22 raw energy = False energy normalize = False delta window = 2 frames (20.0 ms) around hi freq cut = OFF lo freq cut = OFF zero mean frame = OFF use power = OFF CVN = OFF VTLN = OFF spectral subtraction = off cep. mean normalization = yes, real-time MAP-CMN, updating initial mean with last 500 input frames initial mean from file = N/A beginning data weight = 100.00 cep. var. normalization = no base setup from = Julius defaults ------------------------------------------------------------ Acoustic Model(s) [AM00 "_default"] HMM Info: 8443 models, 3090 states, 3090 mpdfs, 49440 Gaussians are defined model type = context dependency handling ON training parameter = MFCC_E_N_D_Z vector length = 25 number of stream = 1 stream info = [0-24] cov. matrix type = DIAGC duration type = NULLD max mixture size = 16 Gaussians max length of model = 5 states logical base phones = 43 model skip trans. = not exist, no multi-path handling AM Parameters: Gaussian pruning = none (full computation) (-gprune) short pause HMM name = "sp" specified, "sp" applied (physical) (-sp) cross-word CD on pass1 = handle by approx. (use average prob. of same LC) ------------------------------------------------------------ Language Model(s) [LM00 "_default"] type=grammar DFA grammar info: 4 nodes, 8 arcs, 8 terminal(category) symbols category-pair matrix: 56 bytes (896 bytes allocated) additional forward DFA grammar info: 4 nodes, 8 arcs, 8 terminal(category) symbols category-pair matrix: 0 bytes (0 bytes allocated) Vocabulary Info: vocabulary size = 8 words, 40 models average word len = 5.0 models, 15.0 states maximum state num = 27 nodes per word transparent words = not exist words under class = not exist Parameters: found sp category IDs = ------------------------------------------------------------ Recognizer(s) [SR00 "_default"] AM00 "_default" + LM00 "_default" Lexicon tree: total node num = 120 root node num = 8 leaf node num = 8 (-penalty1) IW penalty1 = +0.0 (-penalty2) IW penalty2 = +0.0 (-cmalpha)CM alpha coef = 0.050000 Search parameters: multi-path handling = no (-b) trellis beam width = 120 (-1 or not specified - guessed) (-bs)score pruning thres= disabled (-n)search candidate num= 1 (-s) search stack size = 500 (-m) search overflow = after 2000 hypothesis poped 2nd pass method = searching sentence, generating N-best (-b2) pass2 beam width = 30 (-lookuprange)lookup range= 5 (tm-5 <= t <tm+5) (-sb)2nd scan beamthres = 80.0 (in logscore) (-n) search till = 1 candidates found (-output) and output = 1 candidates out of above IWCD handling: 1st pass: approximation (use average prob. of same LC) 2nd pass: loose (apply when hypo. is popped and scanned) all possible words will be expanded in 2nd pass build_wchmm2() used lcdset limited by word-pair constraint short pause segmentation = off fall back on search fail = off, returns search failure ------------------------------------------------------------ Decoding algorithm: 1st pass input processing = real time, on-the-fly 1st pass method = 1-best approx. generating indexed trellis output word confidence measure based on search-time scores ------------------------------------------------------------ FrontEnd: Input stream: input type = waveform input source = microphone device API = default sampling freq. = 16000 Hz threaded A/D-in = supported, on zero frames stripping = off silence cutting = on level thres = 2000 / 32767 zerocross thres = 60 / sec. head margin = 300 msec. tail margin = 400 msec. chunk size = 1000 samples FVAD switch value = -1 (disabled) long-term DC removal = off level scaling factor = 1.00 (disabled) reject short input = off reject long input = off ----------------------- System Information end ----------------------- Notice for feature extraction (01), ************************************************************* * Cepstral mean normalization for real-time decoding: * * NOTICE: The first input may not be recognized, since * * no initial mean is available on startup. * ************************************************************* ------ ### read waveform input Stat: capture audio at 16000Hz Stat: adin_alsa: latency set to 32 msec (chunk = 512 bytes) Error: adin_alsa: unable to get pcm info from card control Warning: adin_alsa: skip output of detailed audio device info STAT: AD-in thread created STAT: 00 _default: 8 generated, 8 pushed, 4 nodes popped in 94 STAT: 00 _default: 8 generated, 8 pushed, 4 nodes popped in 69 STAT: 00 _default: 8 generated, 8 pushed, 4 nodes popped in 78 STAT: 00 _default: 8 generated, 8 pushed, 4 nodes popped in 115 STAT: 00 _default: 8 generated, 8 pushed, 4 nodes popped in 111 socket error, connection closed コマンドが長いので、以下のようにシェルスクリプトにしておきました。 pi@raspberrypi:~/julius $ vi run.sh pi@raspberrypi:~/julius $ chmod +x run.sh``` ```bash:run.sh #!/usr/bin/bash julius -C ~/julius/dictation-kit-4.5/am-gmm.jconf -nostrip -gram ~/julius/dict/sensor データを取得するクライアントアプリを作る Juliusをサーバとして起動すると以下のような表示がされますので、起動したマシンへポート番号10500でアクセスします。 /////////////////////////////// /// Module mode ready /// waiting client at 10500 /////////////////////////////// 今回は、Juliusとクライアントのスクリプトは同じラズパイで起動させるので、IPアドレスはlocalhostにしてsoketを作ります。 Juliusを起動後に以下のサンプルスクリプトを実行します。 Juliusが音声を認識するとサンプルスクリプトへXML形式でデータが送られてくるのでXML ElementTreeを使ってパースします。 認識した言葉はWHYPOのWORDという属性のところに入ってくるので、サンプルスクリプトは、それを抜き出して表示しています。 sensor.py # -*- coding: utf-8 -*- import socket import xml.etree.ElementTree as ET host = 'localhost' port = 10500 # connect sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) while True: recv_data = '' while (recv_data.find('\n.') == -1): recv_data += sock.recv(1024).decode() recv_data = recv_data.strip('.\n') #print(recv_data) root = ET.fromstring(recv_data) for i in root.iter('WHYPO'): word = i.attrib['WORD'] cm = i.attrib['CM'] if word != '[s]' and word != '[/s]': print('WORD = ' + word + ' : CM = ' + cm ) 次回は、WORDで受け取った項目をセンサーのデータを取り出し、OpenJTalkで喋らせる予定。
- 投稿日:2021-10-31T16:16:42+09:00
pyenv + pipenv環境構築(python)から、pythonファイル実行まで
python環境構築 pythonの環境を構築していく。今回のpythonの環境はpyenv + pipenvを使用する。 homebrewは入っている状態のパソコンで作業する。 https://qiita.com/zaburo/items/29fe23c1ceb6056109fd pyenvとは pyenvとは、システム上に複数バージョンのPythonを同居させて使い分けられるようにするものであり、pyenvを用いることでpythonのさまざまな環境を使い分けることができる。 pyenvインストール # brewが入っているコマンドラインで brew install pyenv pipenvとは pipenvとはpip と venv の両方の機能を兼ね備えたサードパーティ製のパッケージ管理ツールである(下記urlより) https://rinatz.github.io/python-book/ch04-05-pipenv/ pipはpythonのパッケージを管理するためのツールであり venvとは、Pythonが動作する仮想的な環境(virtual environment)を作り出すことができるツールである。 要するに、pipenvはpythonのパッケージを管理できる要素とpython環境を構築できる要素を兼ね備えたツールである。 pipenvインストール brew install pipenv 環境構築 // pyenvに入っているversion確認 % pyenv versions # system # 3.6.3 # 3.6.5 # 3.6.7 # 3.6.8 #* 3.7.4 (set by /Users//.pyenv/version) // 使いたいversion指定 % pyenv local 3.6.7 //もう一回version確認。versionが変わっている % pyenv versions # system # 3.6.3 # 3.6.5 #* 3.6.7 (set by /Users//Self-development/qiita/pipenv/.python-version) # 3.6.8 # 3.7.4 //pipenvの環境を作る % pipenv --python 3.6.7 #・ #・ #・ #Creating a Pipfile for this project... // pipfile生成された % ls # Pipfile // 作ったpipenv環境に入る pipenv shell # Launching subshell in virtual environment... # ・・・ パッケージインストール // パッケージインストール (pipenv) pipenv % pipenv install pandas #Installing pandas... # Adding pandas to Pipfile's [packages]... # ✔ Installation Succeeded # Pipfile.lock not found, creating... # Locking [dev-packages] dependencies... # Locking [packages] dependencies... # Building requirements... # Resolving dependencies... # ✔ Success! # Updated Pipfile.lock (8943a4)! # Installing dependencies from Pipfile.lock (8943a4)... # ? ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00 python実行 // panda.pyの中身確認 pipenv % cat panda.py # import pandas as pd # d = {'col1': [1, 2], 'col2': [3, 4]} # df = pd.DataFrame(data=d) # print(df) pipenv % python panda.py # col1 col2 #0 1 3 #1 2 4
- 投稿日:2021-10-31T15:41:10+09:00
python: 内包表記で組合せ(コンビネーション)を列挙する
異なるn個のものからr個のものを選び出す組合せ(コンビネーション)を列挙する。 5個から2個を選ぶ [[a, b] for a in range(5) for b in range(a)] [[1, 0], [2, 0], [2, 1], [3, 0], [3, 1], [3, 2], [4, 0], [4, 1], [4, 2], [4, 3]] 5個から2個を選ぶ(ビットパターン) [1<<a | 1<<b for a in range(5) for b in range(a)] [3, 5, 6, 9, 10, 12, 17, 18, 20, 24] 6個から3個を選ぶ [[a, b, c] for a in range(6) for b in range(a) for c in range(b)] [[2, 1, 0], [3, 1, 0], [3, 2, 0], [3, 2, 1], [4, 1, 0], [4, 2, 0], [4, 2, 1], [4, 3, 0], [4, 3, 1], [4, 3, 2], [5, 1, 0], [5, 2, 0], [5, 2, 1], [5, 3, 0], [5, 3, 1], [5, 3, 2], [5, 4, 0], [5, 4, 1], [5, 4, 2], [5, 4, 3]]
- 投稿日:2021-10-31T15:32:32+09:00
UNICORNプログラミングコンテスト2021(ABC225) A~D問題 ものすごく丁寧でわかりやすい解説 python 灰色~茶色コーダー向け #AtCoder
ABC225(AtCoder Beginner Contest 225) A~D問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 その他のABC解説、動画などは以下です。 UNICORN株式会社様について UNICORN株式会社様は広告主や広告代理店が従来行ってきた“人による細かな運用や数値分析”に費やす時間を削減し、“人にしか行えない業務”に専念できる環境を整えると共に、さらにマーケティング本来の目的である製品やサービスの売上拡大をミッションとして、全自動マーケティングプラットフォーム「UNICORN」の開発・提供を実現しています。 興味のある方はホームページをご覧ください。 AtCoder Jobsにて求人情報も掲載されています。 A - Distinct Strings Sのうち ・3文字が同じ→1種類 ・2文字が同じ→3種類 ・同じ文字がない→6種類 となります。 これをifで条件分岐します。 pythonでは最初の文字を0文字目、左から2つ目は1文字目、左から3つ目は2文字目、...と数えます。 Sのx文字目はS[x]で確認できます。 ゆえに ・3文字が同じ S[0]==S[1]==S[2]: ・2文字が同じ(0文字目と1文字目 または 1文字目と2文字目 または 2文字目と0文字目が同じ) S[0]==S[1] or S[1]==S[2] or S[2]==S[0]: ・同じ文字がない 上記以外 として判定します。 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # 入力の受け取り S=input() # 0文字目、1文字目、2文字目が同じなら if S[0]==S[1]==S[2]: # 「1」を出力 print(1) # 0文字目と1文字目 または 1文字目と2文字目 または 2文字目と0文字目 が同じなら elif S[0]==S[1] or S[1]==S[2] or S[2]==S[0]: # 「3」を出力 print(3) # そうでなければ(すべて違う文字ならば) else: # 6を出力 print(6) B - Star or Not 「スター」である条件が問題文だけではよくわからないと思います。 こういうときはまず入力例を見て、紙に書いてみましょう。 入力例1は N:5 a1 b1:1 4 a2 b2:2 4 a3 b3:3 4 a4 b4:4 5 となっています。 このグラフは「スター」です。 図にすると以下のようになります。 確かに1つの頂点(=④)から他の全ての頂点に1本ずつ辺が出ています。 頂点ごとに辺の本数を数えて、その本数が(N-1)本の頂点があれば「Yes」、なければ「No」を出力します。 countというリストを用意し、入力を受け取りながら辺の本数をカウントしていきます。 例:頂点④から出ている辺の数が3ならcount[4]=3 countが作れたらそれぞれの頂点について辺の本数を確認します。 一つでもN-1本の頂点があれば「Yes」を出力して終了です。 途中で終了する場合はexit()とします。 辺の本数が(N-1)本の頂点が一つもなければ「No」を出力します。 【提出】 # 入力の受け取り N=int(input()) # 辺の本数を数える count=[0]*(N+1) # N-1回受け取り for i in range(N-1): # 入力の受け取り a,b=map(int, input().split()) # a,bから出ている辺の数を数える count[a]+=1 count[b]+=1 # i=1~Nまで for i in range(1,N+1): # 頂点iから出ている辺の数がN-1なら if count[i]==N-1: # 「Yes」を出力 print("Yes") # プログラムの終了 exit() # 「No」を出力 print("No") C - Calendar Validator 大きなカレンダーAがあって、Bはそれを一部切り出したものであるか?を判定する問題です。 以下の手順で確認します。 (1)Bの一番上の行はAのある行を切り出してできるものか (2)Bの1行目(上から2段目)以降は上の段+7となっているか 例として以下の入力を考えましょう。 N:2 M:3 B: 9 10 11 16 17 18 まずAがどんなふうになっているのか見てみましょう。 このようにカレンダーが延々と続くような形をしています。 (1)Bの一番上の行はAのある行を切り出してできるものか Bの一番上の行は「9 10 11」です。 Aを見ればわかるようにこれはAの2行目を切り出して作れます。 切り出して作れないパターンは2つあります。 ・2行にまたがる Bの一番上の行が「13 14 15」であればAから切り出しては作れません。 ・連続になっていない Bの一番上の行が「9 11 13」であればAから切り出しては作れません。 それぞれを判定する方法を考えます。 ・2行にまたがる Bの一番上の行、左端(=B[0][0])を見ればそれがAの何行目にあるかはわかります。 Aの一番上の行を0行目、上から2番目の行を1行目、...とすれば、計算は (B[0][0]-1)//7 となります。 例で考えましょう。 Bの一番上の行、左端は「9」です。計算式に当てはめると (9-1)//7=1 で1行目であることがわかります。 つまりBの一番上の行はAの1行目を切り出したものであるということです。 次にAのその行における左端、右端を確認します。 左端=(行数)*7+1 右端=左端+6 となります。 「9」は1行目だったので 左端=1*7+1=8 右端=8+6=14 であることがわかります。 あとはBの一番上の行の値(9,10,11)がそれぞれが左端~右端(8~14)に収まっているか確認すればOKです。 収まっていなければ2行にまたがっているので「No」を出力して終了です。 ・連続になっていない この判定は簡単で、単に隣の数字と差が1になっているか確認するだけです。 具体的には列番号をretuとして if B[0][retu]!=B[0][retu-1]+1: とすればよいです。 (2)Bの1行目(上から2段め)以降は上の段+7となっているか 一番上の行がAを切り出したものだとわかれば、Bの1行目以降について「上の段+7」になっているか愚直に確認すればOKです。 具体的には行番号をgyou、列番号をretuとして if B[gyou][retu]!=B[gyou-1][retu]+7: として確認します。 【提出】 # 入力の受け取り N,M=map(int, input().split()) # B記録用リスト B=[] # N回受け取り for i in range(N): # i行目をリストとして受け取り B_tmp=list(map(int, input().split())) # Bへ格納 B.append(B_tmp) # (1)Bの一番上の行はAのある行を切り出してできるものか # Aの行数 A_gyou=(B[0][0]-1)//7 # Aの左端 left=A_gyou*7+1 # Aの右端 right=left+6 # retu=0~(M-1)まで for retu in range(M): # Aの左端~右端の間になければ if B[0][retu]<left or right<B[0][retu]: # 「No」を出力 print("No") # 終了 exit() # retu=1~(M-1)まで for retu in range(1,M): # 0行目i列目 が 0行目(i-1)列目+1 でなければ if B[0][retu]!=B[0][retu-1]+1: # 「No」を出力 print("No") # 終了 exit() # (2)Bの1行目(上から2段目)以降は「上の段+7」となっているか # gyou=1~(N-1)まで for gyou in range(1,N): # retu=0~(M-1)まで for retu in range(M): # 「上の段+7」になっていなければ if B[gyou][retu]!=B[gyou-1][retu]+7: # 「No」を出力 print("No") # 終了 exit() # 「Yes」を出力 print("Yes") D - Play Train 車両ごとに前にある車両番号、後ろにある車両番号を記録します。 クエリ③が与えられたら記録した情報からまず先頭の車両を探す→後ろに向かって進みながら答えを記録するという手順を踏みます。 まず前後の車両番号を記録するリストを作ります。 前の車両番号:front 後ろの車両番号:back 前後に車両がない場合は0で記録します。 クエリ① 車両を連結 例:車両3の後ろに車両5を連結 front[5]=3 back[3]=5 クエリ② 車両を分離 例:車両3、車両5を分離 front[5]=0 back[3]=0 クエリ③ frontをたどって先頭の車両番号を確認(front[x]=0になったらxが先頭) backをたどって順に後ろの車両番号を記録(back[x]=0になったらxが最後尾) 記録した車両番号を出力 クエリ③については最悪のケースでO(N)かかるので一見TLEしそうですが、制約に 3 xの形式のクエリで出力する電車の番号の個数の合計は10^6以下 とあるので間に合います。 ※この条件がなければこの解法は使えません。 【提出】 # 入力の受け取り N,Q=map(int, input().split()) # 前の車両番号を記録 なにもなければ0 front=[0]*(N+1) # 後ろの車両番号を記録 なにもなければ0 back=[0]*(N+1) # Q回受け取り for i in range(Q): # クエリをリストとして受け取り query=list(map(int, input().split())) # クエリ1 if query[0]==1: x=query[1] y=query[2] # yの前にx front[y]=x # xの後ろにy back[x]=y # クエリ2 elif query[0]==2: x=query[1] y=query[2] # 連結を外す=0にする front[y]=0 back[x]=0 # クエリ3 elif query[0]==3: x=query[1] # 先頭の車両番号 最初はx head=x # 前の車両番号が0になるまで while front[head]!=0: # headを前の車両番号で更新 head=front[head] # 答えの格納用 ans=[] # 今の車両 now=head # 車両番号が0になるまで while now!=0: # 答えに今の車両を格納 ans.append(now) # 後ろへ now=back[now] # 長さ、ansを出力 print(len(ans),*ans) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 値段:100円(Kindle Unlimited対象) 【kindle】 【booth(pdf)】 1~24問目まではサンプルとして無料公開しています
- 投稿日:2021-10-31T15:05:52+09:00
kivyMDチュートリアル其の肆什 Components - Tabs篇
ハロー、ワールド!みなさん開発してますか?(言い方としてはセコムのように) はい、ということで今日も懲りもせずKivyMDのお時間が参りました。ちょっと使い方は 違うのですが、前に失敗して痛い目をして繰り返すという意味においては間違いではなか ろうかと思います。 今週のニュースはというと、やっぱり選挙のことではないでしょうか。このページも投稿 するころにはもう結果も出ているのではと推測するばかりです。そんなことはないかな。 結果の方も注目したいところです。ITエンジニアに勤務助成金とか出すよと言っている 党にはすぐさま投票したいばかりです。ほんと出ないかなぁ。 馬鹿なことを言っていますが、助成金を獲得できるためにも(無理)私たちはガリガリ コードを書くしかありません。ということで今日も元気にやっていきましょう。今日は タイトルにもある通り、Tabs篇となります。 Tabs 一旦気持ちを震わせてはいましたが、マテリアルデザインのリンクは飛ばしましょう。 手を抜けるときに手を抜くのも、ITエンジニアのなせる技です。 ですが、マニュアルに記載のあるものは目が離せません。冒頭にはこう記載があります。 Tabs organize content across different screens, data sets, and other interactions. 言わんとすることは分かる、というのは多くの方が思っていることではないでしょうか。 スマホアプリだけではなく、デスクトップアプリやwebアプリなど多くの方が触れたので はないでしょうか。 なにやら、他にも書かれていますね。 Module provides tabs in the form of icons or text. ほう、アイコンとテキストをセットできる、というかそんなことは書いてないのですが 少し訳を改変しています。これは試してみるしかないようです。しかしなかなか訳すのが 難しいですね。主語がモジュールだけのように見える。。 Usage では、さっそく使用方法について入っていきたいと思います。 ここでも説明がありますね。見てみましょう。 To create a tab, you must create a new class that inherits from the MDTabsBase class and the Kivy container, in which you will create content for the tab. これまで見てきた方には「はいはい、みなまで言うな」と言いたくなるかもしれません。 少し注意したいところは、MDTabsBase以外にもkivyコンテナーを継承する必要がある ということです。これも後で実際に見てみましょう。また、分からないよーとなった方は DeepLとかGoogle翻訳などで翻訳してみてください。すぐ言わんとしてることは分かる と思います。 コードも貼り付けられているので見てみましょう。 class Tab(MDFloatLayout, MDTabsBase): '''Class implementing content for a tab.''' content_text = StringProperty("") <Tab> content_text MDLabel: text: root.content_text pos_hint: {"center_x": .5, "center_y": .5} ともう1つがこれになります。 All tabs must be contained inside a MDTabs widget: Root: MDTabs: Tab: title: "Tab 1" content_text: f"This is an example text for {self.title}" Tab: title: "Tab 2" content_text: f"This is an example text for {self.title}" ... まず前半2つのコードについては、タブクラス及びレイアウト(ウィジェット)の書き方 になります。クラス側には、MDFloatLayoutと先程の説明にあったMDTabsBaseが継承 されていることが分かります。いや、MDFloatLayoutってなによとなった方はKivyコン テナーと書かれていたことを思い出して頂ければと思います。ややこしいね。 で、後半はなんぞよということですが、後半はコンテンツのところになりますね。これも 説明文の後半(ややこしい)に書かれていたところが合致しています。ルートウィジェット → MDTabs → Tab(さきほどのもの)と分かりやすく1、2、フィニッシュが決まっています。 テストや資格などがあれば絶対に覚えておかないとダメなところですね。 Example with tab icon 使用方法が分かれば、動かしてみたくなるというのが人間の本能というもの。まぁ、かな らずしもそうではありませんが。まず初っ端のサンプルコードになります。アイコンを何個 か(同時にコンテンツへも)セットしておいて、切り替わるときにタブ・コンテンツ両方を切り 替えるというアプリになります。 xl/tabs_tabicon.py from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.tab import MDTabsBase from kivymd.uix.floatlayout import MDFloatLayout from kivymd.icon_definitions import md_icons KV = ''' MDBoxLayout: orientation: "vertical" MDToolbar: title: "Example Tabs" MDTabs: id: tabs on_tab_switch: app.on_tab_switch(*args) <Tab> MDIconButton: id: icon icon: root.icon user_font_size: "48sp" pos_hint: {"center_x": .5, "center_y": .5} ''' class Tab(MDFloatLayout, MDTabsBase): '''Class implementing content for a tab.''' class Example(MDApp): icons = list(md_icons.keys())[15:30] def build(self): return Builder.load_string(KV) def on_start(self): for tab_name in self.icons: self.root.ids.tabs.add_widget(Tab(icon=tab_name)) def on_tab_switch( self, instance_tabs, instance_tab, instance_tab_label, tab_text ): ''' Called when switching tabs. :type instance_tabs: <kivymd.uix.tab.MDTabs object>; :param instance_tab: <__main__.Tab object>; :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>; :param tab_text: text or name icon of tab; ''' # get the tab icon. count_icon = instance_tab.icon # print it on shell/bash. print(f"Welcome to {count_icon}' tab'") Example().run() まず手始めなので、ここはしっかり見ていきます。いつものように、importや kv、クラス側といったように見ていこうと思います。 import文 importで必要なものを以下に抜粋しておきます。 from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.tab import MDTabsBase from kivymd.uix.floatlayout import MDFloatLayout from kivymd.icon_definitions import md_icons BuilderやMDAppオブジェクトに関してはいつもと同じです。今回においてはそれ 以外のMDTabsBaseとMDFloatLayout、md_iconsオブジェクトをインポートして います。なぜこれらが必要なのかが分かる方は、この触れ込みを見る必要がないかも しれません。 kv側 続いてkvレイアウトに入ります。Usageのところで既出ではありますが、ここでは どのように使われているかを見てみます。おさらいでもありますし。 KV = ''' MDBoxLayout: orientation: "vertical" MDToolbar: title: "Example Tabs" MDTabs: id: tabs on_tab_switch: app.on_tab_switch(*args) <Tab> MDIconButton: id: icon icon: root.icon user_font_size: "48sp" pos_hint: {"center_x": .5, "center_y": .5} ''' まぁ、まんまですよね。ここで気をつけたいところでいうと、MDFloatLayoutが さっき出てきたけど今回はMDBoxLayoutを使うんだねーははーん、という方は惜し いですね。そう思う方がどれだけいるのかということもありますが、思った方は再度 Usageのところと見比べてみてください。 また、Usageからの差分でMDToolbarやMDBoxLayoutなどが定義されていますが、 この辺りについては以下の過去のページもしくは公式マニュアルの該当ページをご覧 下さい。 また、気をつけたいところですが、Tabレイアウトの下にあるMDIconButtonはあく までコンテンツであってタブが実際にButtonになっているかと言われるとそうでは ないということが挙げられます。 クラス側 最後にクラス側となります。ここまで見ると全体像が浮かび上がってくるのではない でしょうか。 class Tab(MDFloatLayout, MDTabsBase): '''Class implementing content for a tab.''' class Example(MDApp): icons = list(md_icons.keys())[15:30] def build(self): return Builder.load_string(KV) def on_start(self): for tab_name in self.icons: self.root.ids.tabs.add_widget(Tab(icon=tab_name)) def on_tab_switch( self, instance_tabs, instance_tab, instance_tab_label, tab_text ): ''' Called when switching tabs. :type instance_tabs: <kivymd.uix.tab.MDTabs object>; :param instance_tab: <__main__.Tab object>; :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>; :param tab_text: text or name icon of tab; ''' # get the tab icon. count_icon = instance_tab.icon # print it on shell/bash. print(f"Welcome to {count_icon}' tab'") Example().run() TabクラスはUsageからそれほど変化はありませんね。ただし、Usageの方では コンテンツのテキストプロパティを配置していました。 で、メインのExampleクラスですが、まずmd_iconオブジェクトの15番目から 29番目のキーを取り出しiconsリストに格納しています。 また、あとは3つのメソッドがありますが、build・on_startメソッドなんかは これまでも見てきた方も多いのではないでしょうか。 on_startメソッドは何をやっているかと言うと、先程のiconsリストをイテレート して、テキストを取り出しそのままTabオブジェクトのiconに指定しています。こう することでTabオブジェクトのタブ(ややこしい)をアイコンと指定できるようになり ます。また、TabレイアウトのMDIconButtonもこのアイコンとリンクが出来ている みたいです。これは後で詳細を見てもいいかもしれませんね。 最後にon_tab_switchですが、コールバックメソッドのようなものでタブをスイッチ したときにファイアーされるメソッドみたいです。これはUsageの方で書いて欲しいな。 まず引数としてはself以外に、instance_tabsやinstance_tab、instance_tab_ labelとtab_textの4つがあります。これらはコメント文にある通りですが、おいおい 見ていければと思います。ここではinstance_tab、すなわちTabオブジェクトを持って きています。さらにその中のアイコンプロパティを取り出し、ただただそれをprintして いるだけになります。これも後で見てみましょう。 結果 1通りコードを見ることが出来たので、実行結果を見てみることにします。 うん、問題ありません。サンプルと相違なく動いています。 ちなみにですが、on_tab_switchメソッドの中でプリントしていた文言としては ちゃんと出力されていれば以下のようになります。 Welcome to account-arrow-left-outline' tab' Welcome to account-arrow-left' tab' Welcome to account-alert-outline' tab' Welcome to account-alert' tab' アポストロフィーの打ち方が気になりますが、まぁそれはそれということで。 結果の続き 先程on_startメソッドの説明の中で、以下のように記載をしていました。 また、TabレイアウトのMDIconButtonもこのアイコンとリンクが出来ている みたいです。これは後で詳細を見てもいいかもしれませんね。 すっ飛ばそうと思いましたが、どうしても気になることと、以前の何らかの篇で異なる ことを言っていたような気がしたので、この辺りを詳しく見てみます。まず何をするか と言うと、コードを直打ちしている方は以下の文を追加してみてください。 ※ snackbar自体は先週触れているので気になる方は以下リンクもしくは マニュアルの該当ページをご覧ください (略) KV = ''' + #:import Snackbar kivymd.uix.snackbar.Snackbar (略) <Tab> MDIconButton: id: icon icon: root.icon user_font_size: "48sp" pos_hint: {"center_x": .5, "center_y": .5} + on_release: Snackbar(text=str(root)).open() (略) 単にMDIconButtonのon_releaseプロパティのコールバックメソッドで、Snackbar オブジェクトを作りアプリ側で表示しているだけになります。何を表示しているかと 言うとrootがなんぞやということになります。これが準備できれば、アプリを起動 してコンテンツ側のアイコンを押してみると以下のようにsnackbarが表示出来ます。 なんと、on_tab_switchメソッドの中で説明があった第3引数のinstance_tabの 出力がされているではありませんか。なんらかの篇でroot.hogeのrootはルート ウィジェットになります、みたいなことを言っている可能性があります。というか 記憶にあるな、、総ざらいしてページを訂正するのは後にして、ここにてそのこと を訂正します。間違ったことを発信してしまったことはお詫び申し上げます。 まとめ はい、ということで中途半端な終わり方ですが、一旦ここにて締めくくろうかと 思います。いかがだったでしょうかというのは来週におあずけですね。 まぁ、でもこれだけでも使い方とかは分かるので、おっ、じゃあこれでなんか作っ てみるかという方もいらっしゃるのではないでしょうか。リストなんかと作り方は 似ていて決して難しいものではないかと思われます。 来週にやろうかと思いましたが、切りがいいので、ここで今後の展望などを書いて おきます。なんとコンポーネンツの触れ込みが全て終わりましたー!と言いたいと ころなのですが、まだ少々残っているものがあるんです。。それを以下に記載します。 AnchorLayout CircularLayout FitImage FileManager (*Picker) (DataTables) とまぁ、追加分も含めてこれだけありました。結構多いですね。1番目と2番目に ついては以前のようにまとめて触れ込めればと思います。FitImageはいつぞや に追加されたものですね。何回か登場はしているけど。 あとは、現バージョンでは動かせない、実際にアプリで落とし込んでみたいなど 色々あるので後半3つはおいおいゆっくり見ていきたいなぁと思っています。これ らが終わり次第、Behaviors章に入っていきます。 あとは十分なくらい知識を入れれたので、アプリもそろそろ作成しなきゃですね。 何にしよう、、まぁでも最初は簡単なアプリからスタートになりそうです。TODO アプリとかかな。ありきたりだなぁ。。 ということで今週はここまでとしようと思います。来週はこのTabs篇の続きですね。 ではまた来週〜。 それでは、ごきげんよう。 参照 Components » Tabs https://kivymd.readthedocs.io/en/latest/components/tabs/
- 投稿日:2021-10-31T14:36:15+09:00
JupyterLab Desktop Appのインストールで困ったこと
1 はじめに JupyterLab Desktop App(紹介ページ)をインストール後、アプリを実行すると以下のエラーメッセージが出た。この解決策(自分の場合)を備忘録として残す。 [エラーメッセージ] 2 現象の説明 JupyterLab Desktop App を以下のサイトを参考にインストールした。 このときのインストール環境は以下のとおり 項目 内容 備考 OS Windows10 Winユーザ 管理者権限あり IPアドレス 固定IP 意味のない情報かもしれませんが念のため DNSサーバ設定 認証なし 意味のない情報かもしれませんが念のため インストール後、「Jupyer Server Not Found」とエラーメッセージがでてアプリが起動されない。 3 対策 上記の[エラーメッセージ]が出た後に確認したことを記す。一番最後の方法が(自分の環境では)効果がありました。 3.1 実施したが不発した対策 ”CHOOSE PATH”から、JupyterLabAppServerの中にあるPythonJupyterLabPython.exeを選択 ⇒ 効果なし "INSTALL JUPYTER"を押すと、インストール説明画面に飛ばされるだけで、意味が分からなかった。 ⇒ 効果なし (別サイトでの解決策)で、アプリを"管理者権限で実行"するとよいと記載されていた。解決した人もいればしない人もいるらしいが、実行してみた。 ⇒ 効果なし 3.2 効果があった対策 JupyterLab Appをアンインストールした後、以下の手順で再インストールを試みた。 再インストールで"All users"を選択した。(これまではJust Me) 保存先は以下のとおりです。 ※先程、"Just Me"を選択すると、個人フォルダが保存先に割り当てられる。この辺がアクセス制限の問題を引き起こしているのか? 以下2か所(Recommendedの個所)にチェックをつけてインストール開始 インストール後、JypyterLabが立ち上がった。 涙 4 まとめ Jupyterlab Appをインストールすると"Jupyter Server Not Found"とエラーが出てしまった。理由は分からないが、All user でインストールすることで、起動するようになった。 アカウントのフォルダアクセス権限とかが問題になっているんじゃないかと推測するも良くわからない。とりあえず使えればいいやの精神で完。
- 投稿日:2021-10-31T13:58:05+09:00
【Python】pandas_datareaderで株価取得
概要 Pythonを使って株価データを取得します。 取得元はStooqからとし、pandas_datareader.stooqを使うと日足データが得られます。 株価データの保存形式はCSVです。 ソースコード 重要なのは、GetStockPriceの最後3行。 コマンドライン引数でデータ開始時刻と終了時刻は入力必須(YYYYMMDD形式)です。 オプションで銘柄と保存ファイルパスを指定してください。 downloadStooq.py import argparse import pandas_datareader.stooq as web from datetime import datetime def main(): args = Parse() GetStockPrice(args) def Parse(): """ Get CommandLine Arguments Note: Input Date: YYYYMMDD """ parser = argparse.ArgumentParser() parser.add_argument("start", type=str) parser.add_argument("end", type=str) parser.add_argument("-b", "--brand", type=str, default="TSLA") parser.add_argument("-s", "--savePath", type=str, default="./stock.csv") args = parser.parse_args() return args def GetStockPrice(args): """ Get Daily Stock Price Between $start and $end Args: args (argparse): CommandLine Arguments """ # str -> datetime start = datetime.strptime(args.start, "%Y%m%d") end = datetime.strptime(args.end, "%Y%m%d") # データ読み込み&保存 data = web.StooqDailyReader(args.brand, start=start, end=end) reader = data.read() reader.to_csv(args.savePath) return if __name__ == "__main__": main() 使い方(例) bash python downloadStooq.py 20210101 20210930 -b AAPL -s test.csv 詳細 ブログでもう少し丁寧に書いています。 詳しく知りたい方は参照してみてください。 GitHubにも置いています。
- 投稿日:2021-10-31T13:40:35+09:00
Python list [datetime,str型]日付の生成と変換
datetime型の日付が要素のリストを生成 リスト内包表記で、3日分のdatetime型の日付を要素とするリストを作ります。 datetime() from datetime import datetime, timedelta date_list = [datetime(2021, 10, 1) + timedelta(days=i) for i in range(3)] date_list #[datetime.datetime(2021, 10, 1, 0, 0), # datetime.datetime(2021, 10, 2, 0, 0), # datetime.datetime(2021, 10, 3, 0, 0)] str型の日付が要素のリストへ変換 フォーマットが%Y-%m-%dの、str型の日付を要素とするリストに変換。 strftime() date_str_list = [x.strftime("%Y-%m-%d") for x in date_list] date_str_list #['2021-10-01', '2021-10-02', '2021-10-03'] x.strftimeとfor xの、xは任意の文字。 datetime型の日付が要素のリストへ再度変換 strptimeで再度、datetime型の日付を要素とするリストに変換。 strptime() date_list = [datetime.strptime(x, '%Y-%m-%d') for x in date_str_list] date_list #[datetime.datetime(2021, 10, 1, 0, 0), # datetime.datetime(2021, 10, 2, 0, 0), # datetime.datetime(2021, 10, 3, 0, 0)] 型の確認 それぞれの要素の型を確認します。 type(date_list[0]) #datetime.datetime type(date_str_list[0]) #str
- 投稿日:2021-10-31T13:10:05+09:00
発想力を測定するWebアプリをWord2Vecを使って作ってみた
はじめに 発想力測定器と題しまして、以下のWebアプリを作成しました。できるだけ意味の異なる単語を10個入力することで、あなたの発想力が数値化されます。 https://imagination-checker.izumi-satoshi.com/ github フロントエンドエンド : https://github.com/IzumiSatoshi/imagination_checker_front バックエンド : https://github.com/IzumiSatoshi/imagination_checker_back なお、この発想力測定器は、Naming unrelated words predicts creativity という研究をもとにして作成したものになりますので、学術的な内容に興味がある方は、本家の記事を読むことをお勧めします。 動機 「できるだけ意味の異なる単語を10個挙げると、発想力が測定できるらしい」という話を聞き、調べてみると、Naming unrelated words predicts creativity という研究にたどり着いた。 実際にWeb上で発想力を測定できるサイト (英語)は既に公開されているのだが、日本語版はないようだ。英語版でも測定できることはできるが、やはり自分の母語でテストを受けたいという気持ちは強い。あれ、キリン、キリンって英語でなんだっけと、思考が乱れるのでは、明らかに正確な測定ができない。 ないのなら作ってしまおうと思ったのが、このWebアプリを作るに至った経緯である。 測定器開発偏 どうやって測定するか 発想力を測定するには、いくつかのできるだけ意味の異なる単語を挙げてもらい、それぞれが実際にどのくらい異なっているかを測定すればよいらしい。 もう少し深く説明するために、本家Webサイトからの引用を挟む。 The Divergent Association Task is a quick measure of verbal creativity and divergent thinking, the ability to generate diverse solutions to open-ended problems. The task involves thinking of 10 words that are as different from each other as possible. For example, the words cat and dog are similar, but the words cat and book are not. People who are more creative tend to generate words that have greater distances between them. These distances are inferred by examining how often the words are used together in similar contexts. Still, this task measures only a sliver of the complex process of creativity. 出典: About the task 以下は私の無断Google翻訳+α 発散関連タスク(The Divergent Association Task)は言葉の創造力と発散的思考(自由形式の問題に対する多様な解決策を生み出す能力)の迅速な尺度です。タスクは、互いに可能な限り異なる10単語を考えることを含みます。例えば、猫と犬という言葉は似ていますが、猫と本という言葉は似ていません。より高い創造力を持つ人は、より異なる単語を生成する傾向があります。ここで言う「異なる」とは、単語が同様の文脈で一緒に使用される頻度を調べることによって推測されます。 それでも、このタスクは、創造性の複雑なプロセスのほんの一部しか測定しません。 つまり、より創造力の高い人は、より異なる単語を生成する傾向があるので、生成された単語がどの程度異なっているかを測定すれば、その人の創造力を測定することができるというわけだ。 余談だが、いまさらながら、発想力ではなく創造力を測定するタスクだったということに気づいた。githubのリポジトリ名やドメイン名をcreativityではなくimaginationにしてしまったことを後悔している。だが、引用文にある「自由形式の問題に対する多様な解決策を生み出す能力」としては発想力のほうが近い気もするし、いまさら全てを書き換えるのは面倒なので、以降は発想力で統一しようと思う。(創造力と想像力と発想力の違いを考えたことはありますか?) 基本的な考え方は説明したが、プログラムに落とし込むためには、さらに具体的に考える必要がある。 「単語間の意味がどれくらい異なっているか」を測定するのが難しいわけだが、私は既に、Word2Vecという、この目的にピッタリなモデルを知っていた。つまり、Word2Vecを用いてそれぞれの単語をベクトル化し、そのベクトル同士の類似度からそれらしい測定値を作ることができそうだと考えた。Pythonのライブラリを使えば簡単に実装できそうだ。 Word2Vecって何? ということで、Word2Vecについて軽くおさらいしておこう。(私が) 絵で理解するWord2vecの仕組み word2vec(Skip-Gram Model)の仕組みを恐らく日本一簡潔にまとめてみたつもり などの記事を斜め読みし、ざっくりと何をしているのか理解しようとした。 何となくわかったことは、単語の定義を調べるといった人間的(?)な作業をしているわけではなく、「単語Aの周辺に単語Bがあれば、単語Aと単語Bは似てるよね!」などの、一見大丈夫か?というような方法で単語をベクトル化しているということだ。膨大な文章を学習すればある程度法則が見えてくるものなのだろうか。 調べている途中、そもそも私は幼少期にどうやって単語を学んだのかと考え、不思議な気持ちになった。 Word2Vecを使ってみる ライブラリインストールして、サンプルプログラムを動かすところまで進めていこう。 以下の記事が参考になった。 https://zenn.dev/sorami/articles/fb2eb78e250568b767fd Word2Vecの実装では、Gensimというライブラリが有名なようだが、今回はその改良版であるらしいMagnitudeを使っていく。 ライブラリのほかに、日本語コーパスも必要なので、chiVeという日本語単語ベクトルを以下のページからダウンロードした。 https://github.com/WorksApplications/chiVe from pymagnitude import Magnitude vectors = Magnitude("./chive-1.2-mc5.magnitude") print('りんご と みかん :', vectors.similarity('りんご', 'みかん')) print('りんご と 機械学習 :', vectors.similarity('りんご', '機械学習')) """ (実行結果) りんご と みかん : 0.22651043798939477 りんご と 機械学習 : -0.02963153130331572 """ いい感じだ。 発想力を数値化してみる 以下のような流れで発想力を数値化する。 10個の単語を入力 単語同士の全組み合わせを列挙 それぞれの組み合わせについて、単語ベクトル間のコサイン距離を求める コサイン距離をスコアに換算する 全組み合わせのスコアの平均を求める コサイン距離とは、以下の数式で求まる値だ。 $$f(\vec{q},\vec{d}) = 1 - \frac{\vec{q}\cdot\vec{d}}{|\vec{q}||\vec{d}|}$$ 0(同じ) から 2(全く違う)までの値をとる。今回は、100点満点で測定したいので、この値に50をかける。 コサイン距離は距離じゃないんだから、勘違いしないでよねっ! という著者のテンションが高そうな記事を読み、コサイン距離は距離じゃないということを学んだが、それっぽい値が出るので良しとした。(本当に大丈夫?) 以下のプログラムが発想力測定器の中身である。名前だけ聞くと複雑な処理をしているように聞こえるが、50行程度の簡単なプログラムだ。諸ライブラリへの感謝を忘れないようにしたい。 import itertools from pymagnitude import Magnitude from scipy import spatial vectors = Magnitude('./chive-1.2-mc90.magnitude') def calc_score(word_list): """ 単語リストからスコアを算出 マークダウン形式の表で出力(Qiitaに張り付けるため) """ score_sum = 0 # 単語の組み合わせ全通りをリストにする word_pair_list = list(itertools.combinations(word_list, 2)) # 列名を表示 print('|単語1|単語2|スコア|') print('|---|---|---|') for pair in word_pair_list: distance = spatial.distance.cosine( vectors.query(pair[0]), vectors.query(pair[1]) ) # コサイン距離は0 ~ 2のはず。それを100点満点に変換した値をスコアとする。 score = (distance / 2) * 100 # 小数第一位にまとめる score = round(score, 1) print(f'|{pair[0]}|{pair[1]}|{score}|') # socre_sumに加算 score_sum += score # 合計スコアをペア数で割って平均スコアを求める score_mean = score_sum / len(word_pair_list) # 少数第一位にまとめる score_mean = round(score_mean, 1) print('total = ', score_mean) word_list = ['葉巻', '女子高校生', 'トリアージ', '密林', '横隔膜', 'ニューラルネットワーク', 'オルゴール', '枯山水庭園', 'シャリ', '社会保険'] calc_score(word_list) 実行結果を以下に示す。サンプルとして入力した単語リストは、私の発想力を最大限生かしたものである。 実行結果 被験者 : 私 入力単語 : 葉巻, 女子高校生, トリアージ, 密林, 横隔膜,ニューラルネットワーク, オルゴール, 枯山水庭園, シャリ, 社会保険 総合スコア : 45.5 内訳 単語1 単語2 スコア 葉巻 女子高校生 48.0 葉巻 トリアージ 49.6 葉巻 密林 45.1 葉巻 横隔膜 43.5 葉巻 ニューラルネットワーク 48.1 葉巻 オルゴール 42.5 葉巻 枯山水庭園 44.4 葉巻 シャリ 41.6 葉巻 社会保険 49.4 女子高校生 トリアージ 41.3 女子高校生 密林 44.5 女子高校生 横隔膜 46.5 女子高校生 ニューラルネットワーク 46.6 女子高校生 オルゴール 47.4 女子高校生 枯山水庭園 42.8 女子高校生 シャリ 48.9 女子高校生 社会保険 44.6 トリアージ 密林 45.3 トリアージ 横隔膜 43.1 トリアージ ニューラルネットワーク 39.0 トリアージ オルゴール 49.5 トリアージ 枯山水庭園 48.6 トリアージ シャリ 48.5 トリアージ 社会保険 38.9 密林 横隔膜 46.0 密林 ニューラルネットワーク 47.3 密林 オルゴール 45.2 密林 枯山水庭園 41.0 密林 シャリ 45.9 密林 社会保険 50.4 横隔膜 ニューラルネットワーク 41.6 横隔膜 オルゴール 41.3 横隔膜 枯山水庭園 47.8 横隔膜 シャリ 44.3 横隔膜 社会保険 47.4 ニューラルネットワーク オルゴール 47.0 ニューラルネットワーク 枯山水庭園 48.1 ニューラルネットワーク シャリ 46.2 ニューラルネットワーク 社会保険 42.7 オルゴール 枯山水庭園 43.3 オルゴール シャリ 48.0 オルゴール 社会保険 45.7 枯山水庭園 シャリ 44.9 枯山水庭園 社会保険 47.4 シャリ 社会保険 46.9 トリアージと社会保険を近い意味と判断しているのには感心した。 ただ、これだけでは何とも言えないので、「文房具しか思いつかなかった人」を想定してもう一度実行してみる。 実行結果 被験者 : 文房具しか思いつかなかった人 入力単語 : シャープペンシル, ホワイトボード, 消しゴム, 万年筆,定規, 三角定規, コンパス, 分度器, ボールペン, 修正液 総合スコア : 30.0 内訳 単語1 単語2 スコア シャープペンシル ホワイトボード 30.9 シャープペンシル 消しゴム 23.4 シャープペンシル 万年筆 14.2 シャープペンシル 定規 25.9 シャープペンシル 三角定規 39.9 シャープペンシル コンパス 32.0 シャープペンシル 分度器 29.0 シャープペンシル ボールペン 9.0 シャープペンシル 修正液 23.9 ホワイトボード 消しゴム 30.3 ホワイトボード 万年筆 33.6 ホワイトボード 定規 29.4 ホワイトボード 三角定規 39.5 ホワイトボード コンパス 32.6 ホワイトボード 分度器 31.0 ホワイトボード ボールペン 26.3 ホワイトボード 修正液 32.1 消しゴム 万年筆 30.1 消しゴム 定規 30.0 消しゴム 三角定規 37.1 消しゴム コンパス 40.0 消しゴム 分度器 31.9 消しゴム ボールペン 20.2 消しゴム 修正液 29.0 万年筆 定規 33.1 万年筆 三角定規 46.4 万年筆 コンパス 36.4 万年筆 分度器 34.1 万年筆 ボールペン 12.6 万年筆 修正液 28.2 定規 三角定規 27.9 定規 コンパス 25.9 定規 分度器 18.8 定規 ボールペン 26.9 定規 修正液 29.1 三角定規 コンパス 34.2 三角定規 分度器 28.5 三角定規 ボールペン 40.5 三角定規 修正液 39.9 コンパス 分度器 25.7 コンパス ボールペン 31.8 コンパス 修正液 38.4 分度器 ボールペン 33.0 分度器 修正液 34.0 ボールペン 修正液 23.7 予想通り、低いスコアが測定された。一応、測定器としての役割は果たしていると思われる。 高いスコアを出すには いろいろな単語を入力して測定を行い、何となくではあるが以下のような傾向を見出すことができた。 以下に挙げる傾向はすべて、Word2Vecの「周辺にある単語は近い意味だよね」という性質に起因していると思われる。 1. より具体的な単語は、より高いスコアを出しやすい この傾向は強く感じられた。例えば、「ごはん→米→白米」のように、より具体的にな単語に置き換えていくことで、簡単にスコアを上げることができる。なので、高スコアを目指すためには、関連性の低い単語を引っ張ってくる能力に加え、思いついた単語をより具体的なものに置き換えていくといった方向の発想力も重要になってくるのだろう。 2. より使いにくそうな単語は、より高いスコアを出しやすい。 1の傾向と重なる部分もあるが、使いにくい単語は高スコアを出しやすい。例えば「白米→酢飯→シャリ」と置き換えていくことで、使用場面が寿司屋に限定され、より使いにくい単語となる。そのため、文章中で他の単語の周辺に位置する機会も少ないのだろう。 3. 抽象度の高い単語はスコアが低くなりやすい 「時間」や「上」などの概念は、スコアが低くなりやすかった。これは、様々な単語と組み合わせて使用することができるためだろう。例えば、「時間」と「みかん」という一見関連性がないように見える単語でも、「時間がない。みかんを食べよう。」というような文があると、Word2Vecの性質上近い単語だと判断されてしまうのかもしれない。 気になる点 以下に挙げるような気になる点もいくつかあるが、今は動くものを完成させることを優先しよう。 コーパスに含まれていない単語はどのように処理されるのだろうか 50点を超えるのがかなり難しいように感じたが、それはなぜか 二つ以上の名詞がつながった複合名詞の測定はどうなっているのか また、本来であれば、この測定器の信ぴょう性を検証するため、その他の発想力テストやIQテストなどとの相関関係を調べるのが良いと思われるが、今回はこれ以上は踏み込まないことにする。(それを行っているのが本家の研究) 何はともあれ、測定器が完成してひと段落ついた。 Webアプリ開発偏 全体の構成としては、Flaskで測定器のAPIを開発し、Reactで作るフロントエンドエンドからAPIを呼び出すという形をとった。Flaskは軽量なPythonのWebアプリケーションフレームワークで、ReactはSPA(Single Page Application)が簡単に作れるJavaScriptライブラリだ。 この程度の規模のWebアプリであれば、フロントエンドエンドもFlaskに任せるのが良いような気もするが、今回は学習中のReactを使ってみる。githubのリポジトリもバックエンドとフロントエンドエンドで分け、両者の疎結合を意識した開発を行っていく。 Flaskで測定器APIを実装 単語リストを投げたら、スコアとその内訳を返すAPIを実装する。Flaskのチュートリアル等を読みながら、前述した測定器のプログラムを順当にWebAPI化した。 import itertools from flask import Flask, request from flask.json import jsonify from flask_cors import CORS from pymagnitude import Magnitude from scipy import spatial from waitress import serve vectors = Magnitude('./chive-1.2-mc90.magnitude') app = Flask(__name__) app.config['JSON_AS_ASCII'] = False CORS(app) @app.route('/', methods=['POST']) def api(): json_dict = request.json word_list = json_dict['word_list'] response = calc_score(word_list) return response def calc_score(word_list): """ 単語リストからスコアを算出し、スコアとその内訳をjsonで返す """ score_sum = 0 breakdown_dict = dict() word_pair_list = list(itertools.combinations(word_list, 2)) for idx, pair in enumerate(word_pair_list): distance = spatial.distance.cosine( vectors.query(pair[0]), vectors.query(pair[1]) ) # コサイン距離は0 ~ 2のはず。それを100点満点に変換した値をスコアとする。 score = (distance / 2) * 100 # socre_sumに加算 score_sum += score # 内訳dictに入れる breakdown_dict[idx] = { 'word1': pair[0], 'word2': pair[1], 'score': score, } # 合計スコアをペア数で割って平均スコアを求める score_mean = score_sum / len(word_pair_list) json_obj = jsonify({ 'score': score_mean, 'breakdown': breakdown_dict }) return json_obj if __name__ == '__main__': print('start server') serve(app, host='0.0.0.0', port=5000) ここで一つ問題発生 githubにプッシュしようとしたら、magnitudeファイル(単語ベクトルデータ)が100MBを超えているとのことで拒否された。 ということで、git lfsなるものを導入したが、よくわからないエラーに悩まされる。 数時間格闘したのち、結局、magnitudeファイルはgitignoreに入れて、自力で管理することにした。 バックエンドをデプロイ Docker化 まず、FlaskアプリケーションをDocker化し、docker compose upだけで立ち上がるようにしておく。Dockerイメージのサイズが5GBになっているが、Word2Vec関連のファイルサイズが大きいのだろう。いや、何か設定がおかしいのかもしれない。 サービス選定 次に、デプロイ先のサービスを選定する。 この測定器APIを動かすためには、700MB以上の単語ベクトルデータをメモリ上に展開する必要があり、ある程度のマシンスペックが必要だと考えていた。 デプロイ先としてまず思いついたのがHerokuの無料枠だったが、利用可能メモリが512MBだということで断念した。そこで、GCE(Google Compute Engine)にデプロイすることにした。永久無料枠のVM(Virtual Machine)インスタンスの利用可能メモリは1GBだ。前々から一度使ってみたいと思っていたので、いい機会になる。 GCPにデプロイ 永久無料枠を利用する手順については、こちらの記事が分かりやすかった。OSは使い慣れているUbuntuを選ぶ。 https://qiita.com/Brutus/items/22dfd31a681b67837a74 VMインスタンス上のFlaskアプリケーションに外部からアクセスするには、Nginxを導入する必要があるらしい。以下の記事が非常に参考になった。本来はNginxも含めてDocker化するものなのだろうが、まぁいいだろう。 https://triple-four.hatenablog.com/entry/20210810/1628584545 不安点 パフォーマンステスト等は全く行っていないので、1回の測定あたりどのくらいのリソースを食うか分かっていない。アクセスが集中するとサーバーがダウンすることは容易に想定できるが、そういった心配は、今回のような個人開発サービスにおいて、杞憂になることが多いと知っているので、ひとまずはスルーすることにする。 APIの動作確認が成功して、ひと段落付いた。初めてのGCPへのデプロイだったので、いろいろはまりどころがあるかと思っていたが、案外簡単に動くものができてうれしかった。 Reactでフロントエンドを実装 フロントエンドエンドの実装は、常にDone is better than perfect!!と叫びながらのものであった。避けなければならない最悪の事態は、途中でモチベーションを失ってしまい、未完成のまま放置されることだ。私にはよくある。なので、まずは、どれだけ見た目が貧相でもいいので、動くものを公開することにした。 測定結果の表示方法 今回作るアプリには大きく分けて2つのパートがある。1つ目が単語を入力する部分で、2つ目が測定結果を表示する部分だ。その他にも補足情報を掲示するための「これは何?」というページも作成したが、メインではない。 どのように単語を入力する部分と結果を表示する部分を接続するか考えた結果、以下のような手順で処理を行うことにした。単語入力側と結果表示側を別々のページとして実装する。 単語を入力される(単語入力側) 入力された単語のリストをURLのクエリ文字列に変換(単語入力側) 結果表示ページに、クエリ文字列を含んだURLからリダイレクト(単語入力側) クエリ文字列を解析し、単語のリスト抽出 (結果表示側) 測定器APIにリクエストを送る(結果表示側) 結果を表示する(結果表示側) この手順で処理を行うことで、結果表示ページとそのURLとが1対1で対応するようになり、結果表示ページを共有することが可能になる。例えば私の測定結果をここに張り付けることができる。 将来的にSNSでの結果共有機能を実装することを考えると、悪くないアイデアだと思う。 測定結果に一言添える 測定結果として、数値だけが出されても、多くの人は自分に発想力があるのかないのか分からないだろう。そこで、「あなたの発想力は人並です」というようなメッセージを添えるようにする。処理自体は単純で、スコアをif文で区切り、あらかじめ設定しておいたメッセージを表示するだけだ。このメッセージに根拠はないので、無視してもらっても構わない。 測定結果の内訳を表示する 単語の組み合わせごとにスコアを算出し、最後に平均をとるという測定を行っていみるため、単語の組み合わせごとのスコアも表示することにした。これがあることで、前回の測定でスコアの低かった単語を入れ替え、再度測定するというような楽しみ方も可能となる。単語ペアをスコアで昇順ソートし、スコアを下げている原因となっている単語を見つけやすくした。 レスポンシブルデザイン 今の時代において、スマホからも使いやすいアプリであることは必須だろう。ヘッダーのデザインを切り替えたり、画面の幅を調整したりといった基本的な箇所のみの実装にとどまったが、スマホユーザにも配慮した発想力測定器になった。 この当たりで、フロントエンドの開発にはいったん終止符を打つことにする。自分の作っているものが、だんだんと形になっていくのを見るのはやはり楽しい。モノづくりの醍醐味が感じられる。 フロントエンドをデプロイ フロントエンドエンドのデプロイ先は、静的ファイルを設置できればどこでもよい。これは、SPAであることのメリットの1つだと思う。今回はFirebaseにデプロイすることにした。 バックエンドのSSL化 Firebase CLIの Firebase deploy だけですんなりデプロイできると思っていたが、測定器APIとの通信がうまくいっていない模様。 調べてみると、Firebaseは外部との通信含めてhttpsしか許可していないらしい。 ということで、バックエンド側のAPIをhttpsに対応させる作業を開始した。だが、Let’s Encrypt(無料SSL証明書)の手続きをするためには独自ドメインをとる必要があるらしい。どうせフロントエンドは独自ドメインで運用するつもりだったので、お名前ドットコムで取得。誤ってWhoisメール転送オプションをつけてしまい300円損した。 GCEサーバー上でのSSL化の作業は以下の記事を参考に進めた。自動更新の設定もすることができ、満足した。 https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04-ja 独自ドメインで公開 バックエンドに続き、フロントエンドエンドのほうも独自ドメイン化する。特に難はなく、サクッと完了した。 完成! ついに、動くものを公開するという目標を完遂することができた。 1日当たり数時間程度の作業で、約一週間ほどの時間がかかった。これは、今後小規模なWebアプリを作成するときの、自分の中での目安の1つになるだろう。 完成後は、家族の発想力を測定して楽しんだ。信ぴょう性に欠けるためか、家族が測定器に強い興味を持っている様子は感じられなかったが、まぁいいだろう。 私としては、自分の母語で発想力を測定できるようになり、大満足である。 今後やりたいこと Twitterで測定結果をつぶやけるようにする 測定結果の表を総当たりの対戦表みたいにして、見やすくする 測定結果を収集し、平均値とか偏差値とかを算出したりする おわりに 間違いなどがありましたら、ご指摘いただけると幸いです。 最後までお読みくださり、ありがとうございました。
- 投稿日:2021-10-31T12:34:44+09:00
【Python】ノイズを含む信号を2値化する
目的 オシロスコープで観測した波形等のノイズを含む信号を2値化する。 課題 ノイズの影響により、変化のタイミングや変化の回数が正しく求まらない。 解決策 移動平均でデータを平滑化してから2値化する。 コード import numpy as np import matplotlib.pyplot as plt # 時間行列 t = np.linspace(0,10,100) # 真値 y_true = np.sin(t) + 1/3*np.sin(3*t) + 1/5*np.sin(5*t) # 観測値(真値+ノイズ) y_obs = y_true + np.random.randn(100)*0.3 # 平均化に使用する行列 num = 5 # 移動平均に用いる個数 k = np.ones(num)/num # 平均化に使用する行列の重み。移動平均なので均等 # 観測値を移動平均 y_fil = np.convolve(y_obs, k, mode='same') # 移動平均を2値化(0より大きければ1,0以下であれば0) y_bin = np.zeros(len(y_fil)) y_bin[y_fil>0] = 1 # グラフ化 fig, ax = plt.subplots() ax.plot(t, y_true,'r') # 真値 ax.plot(t, y_obs,'k-') # 観測値 ax.plot(t, y_fil,'b--') # 移動平均 ax.plot(t, y_bin,'g-.') # 二値化 ax.legend(['真値','観測値','移動平均','二値化'], prop={"family":"MS Gothic"}) # グラフを保存 fig.savefig("img.png") 結果
- 投稿日:2021-10-31T12:11:03+09:00
pythonのlambda関数についてまとめてみた
今回のお題 今回は、pythonのlambda関数について取り上げます。 例によって自分用のメモです。 目次 lambda関数概要 基本的な使い方 引数の渡し方のパターン lambda関数概要 まずは、lambda関数とはそもそも何ぞやという話から始めます。 一旦は能書きから入りますが、イメージが湧かなければ一度飛ばした後に次項の具体例をみた上で戻ってきていただいても構いません。 ざっくりと言うと、lambda関数とは名前をつけずに定義できる関数のことを言います。 JavaScriptの無名関数に近いですね。 欠点としては、名前がつかないので複数回使いまわせない、それからpythonの場合は1行でしか書けずif文なども使えない、などがあります。 しかし一度別の箇所で定義したものを呼び出して、と言う手間を省けるので一度しか使わない関数を扱う場合にはコードがスッキリしますし、とりわけ関数の戻り値を別の関数の引数として使いたい場合には大変重宝します。 では、基本的な使い方を見ていきましょう。 基本的な使い方 # 公式 lambda x, y: (x / y) # lambda 変数: 戻り値 の形で関数を定義 lambda関数は他の関数の引数として用いることで真価を発揮します。 以下の例を見てください。 sorted関数のkeyとしてlambda関数を用いています。 sorted関数は第一引数のリストを昇順に並べ替えます。 もしkey=の形で関数を指定した場合は、リストの各要素にkey関数を適用させた戻り値で順番が決まります。 今回はlambda関数を用いて各要素を3で割った余りを返すようにしたので、引数として与えたリストが「3で割った余が小さい順」に並べ替えられます。 lambda関数の実験 list = [10, 5, 18] print(sorted(list)) # [5, 10, 18] print(sorted(list, key=lambda x: x % 3)) # [18, 10, 5] なお、sorted関数のkeyはあくまで戻り値を使って並び替えの基準を提供するだけです。 元のリストを書き換えたり、新しいリストを返したりするわけではないのでそこは注意してください。 引数の渡し方のパターン あまりないのですが、lambda関数に引数を渡すケースがあるのでそれに関してもまとめておきます。 print((lambda x: 2 * x)(10)) # 出力:20 関数の引数として、lambda関数の戻り値を使いたいと言う場合には (lambda 変数: 戻り値)(引数) と言う書き方をします。 また、他の関数の戻り値がlambda関数になるような場合にも似たような書き方をします。 def hoge(a): if a == 10: return lambda x: 2 * x print(hoge(10)(3)) # 出力:6 hogeの後の一つ目の括弧はhoge関数に引数を渡すためもの、二つ目は戻り値のラムダ関数に引数を渡すためのものと考えるとわかりやすいですね。 終わりに 以上でlambda関数の解説を終わります。 書き方が独特なので早く慣れていきたいですね。
- 投稿日:2021-10-31T11:23:56+09:00
27年間事務屋だった私、pythonと機械学習を学んだら半年間でいい感じで実証実験ができたよ
私の属性 50代前半の男性です。 大学での専攻は刑法(総論)でした。 1993年春に新卒で地方公務員(事務)になりました。 転職歴はありません。 初めてのコンピューターはPC-6001で、中学2年のときに親に買ってもらいました。 Pythonははじめて、機械学習もはじめて Pythonはほぼ、はじめての状態でした。無料で読める入門書を読み始めることがありましたが、理解できないお作法を見ると苦痛が溜まって、入門書を投げ出していました。プログラミングの経験でいうと、ふた昔前にCGIのPerlスクリプトを作ったことがありました。 機械学習は「はじめての機械学習-小高知宏」を読んだだけです。 あとは、キカガクのオンライン講座「Python&機械学習入門」を不真面目に受けました。 まるっきりの雑魚キャラです。ガンダムで言えば、迂闊に前に出るから撃墜されるザク。 警告 ここからポエムから始まります。 ポエムを飛ばしたい方はこちらをクリック! 昇進したら私の未来が見えた 昇進して数カ月後に、組織内で私が歩むであろう今後のルートが見えてきました。 今後は、非常にゆっくりしたペースで昇進することになります。 独学で得たITスキルは他人のために使われるか、全く使わなくなります。 そこでは、私は僅かにしか成長しません。 そんな未来が見えました。 組織の外へ 組織外で働いて組織外の文化を取り入れることは、自分自身の成長につながります。 でも、組織の外に働き口はあるのでしょうか。 出向とか派遣とかをしてくれるよう、組織に希望を出しても、受け入れてくれるとは限りません。 私のITスキルを表に出してみよう、そうしたらポートフォリオとしてだれかに評価してもらえて、組織の外で雇ってもらえるようなことに繋がっていくかも。 そう思ったのが2021年の春でした。 そのころ、法案誤りがニュースになっていました。 AIなら法案誤りを見つけ出せるのでは? 法案誤りのニュースを見ていて、「AIなら法案誤りを見つけ出せるのでは?」と特に根拠もなく思いました。 というわけでネット上で調査をはじめました。 ネットでAIによる文章校正を検索すると、助詞について機械学習で予測しているブログ記事(RNNで「てにをは」を校正する『にほんごのれんしゅう』)が検索結果に出てきました。深層学習フレームワークはKerasで、ソースコードがgithubで公開されていました。 これは名詞でも使えそうだと思いました。 # 後でわかったことですが、ニュースになっていた法案誤りは、言葉の使い方での誤りは少なく、インデントなどの文書形式面での誤りのほうが多いので、文書形式面での誤りを解決するほうがよりよかったのでした。 Kerasを理解しよう とりあえずKerasを理解したかったです。Google先生に聞いてもよくわからなかったので、 直感Deep Learning - (アフィリエイトリンクじゃないです。) Antonio Gulli、Sujit Pal 著、大串 正矢 、久保 隆宏、中山 光樹 訳、原著"Deep Learning with Keras" というKerasの本を買って読むことからはじめました。なぜこの本を選んだかというと、コピーガード機能がついていない電子書籍としてオライリーから購入できるからでした。 なお、原著の発行年が2017年であるため、2017年6月に発表されたTransformerや、その後のGPT-3、BERT1、T5についての記載がありません。 本を読んだ結果、単語分散表現がどういうものか、深層学習のモデルが何をしようとしているのかという概念的な部分を理解できた気がしました。その一方で、数学的な表現や数式をほとんど読み解くことができず、理論の応用方法を理解して自分の武器にするということができませんでした。 Pythonをどうやって学ぶ? Pythonの学習は、本ではできませんでした。 私の場合は、本を読まず、受講せず、いきなりPythonのスクリプトで実作業を行いました。初めは、テキストファイルからデータファイルを作りました。そこで、わからなければググってスクリプトを直すことでPythonを学びました。 その後も、ググってスクリプトを直すことでPythonを学び続けました。 この学習方法の欠点は、ググってすぐに分からなければ時間を多く費やしてしまうことと、正しい、または読みやすいコードには必ずしもならないことです。なにか正当な学習方法があれば、学習効率が向上して時間の節約になり、また、正しく読みやすいコードになったかもしれません。 いきなりスクリプトに手を付ける前に、どういう方法でPythonを使ったらいいのかを調べました。その結果、あちこちにファイルを置いて、バージョン管理ができないズボラな私には、開発環境はGoogle Colabが良いと思いました。 #モデルを作成する時になって、Google ColabでGPUが使えることがわかりました。 学習環境 学習(作業)場所を自宅にすると、精神状態の緩和と集中の差が激しい私は、作業が手につかないか寝食を忘れて作業するかの両極端に陥ってしまいがちでした。場所をネットカフェにしたら、誘惑が多くてなにもできませんでした。 場所を模索した結果、パソコンの使用が黙認されている飲食店の○○○(お察しください)にして、学習効率が悪くなったら自宅に帰ることにしたら、うまくONとOFFを切り替えられました。 このやり方の欠点は、飲食店はパソコンで作業する場所ではないために、混雑時間帯には出ていかなければならないことと、飲食しすぎて体重が増えがちなことです。 いい感じで実証実験ができた 今回の取組の目標は、機械学習(AI)による単語予測と、法令文を校正するための予測精度の向上を実務で利用できるレベルにすることでした。また、それができるかは不明であったため、今回の取組は目標の技術的な可能性を実証することになります。実証実験と言えるかと思いますが、検証がないので研究にはなりません。 取り組んで、だいたい目標まで達せられたのは2021年10月でした。最初から最後まで、満足な結果が出る確実な見込みを持てませんでした。ずっと「なんの成果もありませんでした」とならないか、不安でした。実証実験と言えそうなところまで、やり遂げられてよかったです。 取組の内容については、以下のリンク先をご覧ください。 機械学習(AI)による単語予測と、法令文を校正するための予測精度の向上についての取組(概要) 機械学習(AI)による単語予測と、法令文を校正するための予測精度の向上についての取組(技術的説明:モデル作成編) 機械学習(AI)による単語予測と、法令文を校正するための予測精度の向上についての取組(技術的説明:試行編) 今後は 自分が成長できるところで働きたいです。 「BERTのMasked Language Modelを利用して文の校正を行う」では"Wikipediaでpretrainしたモデルで文章校正するのは難しそう"としています。 ↩
- 投稿日:2021-10-31T11:23:19+09:00
GStreamerで連続ファイルにエンコード・記録する
multifilesrcでデバイスからの入力を連続ファイルへ記録できる...かと思えばさにあらず、日本語記事も少ないと思い、書きました。 環境 筆者はJetson複数モデルで開発をしています。GPUのNVIDIA SDK使用環境も共通項があるかもしれません。 Jetson JetPack 投稿の時点でのJetpack最新バージョンは 4.6です。 20WでJetsonを駆動するnvpmodelが追加され、enc/decフレンドりとなっています。 コンテナでやろうとする方向けの注意としてはJetpack 4.5.0と 4.5.1, 4.5.2の違いはBSPやバグフィックスだそうで、L4Tのイメージは r32.5.0となります。Dockerfileをr32.5.1やr32.5.2と書き換えてもいきなりコケるのでご注意。ホストのJetpackバージョンとコンテナイメージバージョンも合わせましょう。 GStreamer Jetson内の gstreamerはversion1.14.5となります。NVidiaの動作検証もこのバージョンで行われています。 >On Xavier, it is with gstreamer 1.14.5 and may not work with later version(s). Each release is tested/verified with gstreamer 1.14.5. If you manually upgrade to 1.18, it may not work properly and the system can be unstable. multifilesinkではなくsplitmuxsink やっと本論です。曰く multifilesinkだとフレームロスが生じた、とのこと。 このトピック自体は出来たか出来なかったか分からないままcloseしています。 投稿者がomxh265encではうまくいかなかったとするのに対して、NVIDIA担当からはsplitmuxsinkで指定するmuxerを選ぶ必要があると回答。 横から失礼してくれた最後の投稿者のqtmux使用のアイディアには無回答です。個人的には qtmux, qtdemuxで行けてほしいところです。。。 keyframeや分割と関連して、他の記事にはエンコードオプションにiframeintervalを設定せよとの記述もありました。お試し下さい。 splitmuxsink 量が多いので別記事にしたくおもいますが、ver1.16以降のサポート事項については残念、Jetson(gstreamer1.14.5)ではサポートされていませんのでご注意ください、 以下はhttps://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-splitmuxsink.html をもとにしています。 早速パイプラインの例 v4l2デバイスからのビデオ入力をmuxしてISO準拠mp4ファイルに記録します。時刻は ns =nanosec単位とのことで、0の数にはどうぞご注意ください。出来上がりストリームが短いと泣きます。 (原文)Records a video stream captured from a v4l2 device and muxes it into ISO mp4 files, splitting as needed to limit size/duration to 10 seconds and 1MB maximum size. gst-launch-1.0 -e v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! timeoverlay ! x264enc key-int-max=10 ! h264parse ! splitmuxsink location=video%02d.mov max-size-time=10000000000 max-size-bytes=1000000 プロパティの例 指定サイズで切るmux-size-bytes, 指定時間経過後に切るmax-size-time などが汎用性高いかと思います。2GB境界とか(いまさらw)、超えないように気を付けたいところです。 location multifilesinkと同じ、locationです。フォーマット形式 %02dなどで連番指定を行います。 The “location” property “location” gchar * Format string pattern for the location of the files to write (e.g. video%05d.mp4). Flags: Read / Write Default value: NULL muxer デフォルトのmuxerはmp4muxerです。コーデックに対応している必要があります。また、パイプラインのparserも適切に選ぶ必要がありますね。 The “muxer” property “muxer” GstElement * The muxer element to use (NULL = default mp4mux). Valid only for async-finalize = FALSE. Flags: Read / Write シグナルの例 例えばsplit-afterというシグナル(v1.16以降)は、現在進行中のGOP終了後新しいファイルにしてくれるとのこと。 The “split-after” signal void user_function (GstSplitMuxSink *splitmux, gpointer user_data) When called by the user, this action signal splits the video file (and begins a new one) immediately. The current GOP will be output to the old file. スクリプト例 ソフトエンコーダで 10秒ごとに分割 gst-launch-1.0 -e v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! timeoverlay ! x264enc key-int-max=10 ! h264parse ! splitmuxsink location=video%02d.mov max-size-time=10000000000 max-size-bytes=1000000 それでは、また! happy encoding life!
- 投稿日:2021-10-31T11:23:19+09:00
GStreamerでビデオを連続ファイルにエンコード・記録する
multifilesrcでデバイスからの入力を連続ファイルへ記録できる...かと思えばさにあらず、日本語記事も少ないと思い、書きました。 環境 筆者はJetsonの複数モデルで開発をしています。GPUでのNVIDIA SDK使用環境も共通項があるかもしれません。 Jetson JetPack 投稿の時点でのJetpack最新バージョンは 4.6です。 これまでの10,15Wに加えて20WでJetsonを駆動するnvpmodelが追加されました。 L4Tコンテナでやろうとする方向けの注意としてはJetpack 4.5.0と 4.5.1, 4.5.2の違いはBSPやバグフィックスだそうで、L4Tのイメージは r32.5.0となります。Dockerfile内を単にr32.5.1やr32.5.2と書き換えてもイメージがないとしていきなりコケるのでご注意。ホストのJetpackバージョンとコンテナイメージバージョンも合わせましょう。 GStreamer Jetson内の gstreamerはversion1.14.5となります。NVidiaの動作検証もこのバージョンで行われています。 >On Xavier, it is with gstreamer 1.14.5 and may not work with later version(s). Each release is tested/verified with gstreamer 1.14.5. If you manually upgrade to 1.18, it may not work properly and the system can be unstable. multifilesinkではなくsplitmuxsink やっと本論です。曰く multifilesinkだとフレームロスが生じた、とのこと。splitmuxsinkの登場です。(トピックがこのあとどうなったかは折り畳みを展開ください) このトピック自体は出来たか出来なかったか分からないままcloseしています。 投稿者がomxh265encではうまくいかなかったとするのに対して、NVIDIA担当からはsplitmuxsinkで指定するmuxerを選ぶ必要があると回答。 横から失礼してくれた最後の投稿者のqtmux使用のアイディアには無回答です。個人的には qtmux, qtdemuxで行けてほしいところです。。。 keyframeや分割と関連して、他の記事にはエンコードオプションにiframeintervalを設定せよとの記述もありました。お試し下さい。 splitmuxsink 量が多いので別記事にしたくおもいますが、ver1.16以降のサポート事項については残念、Jetson(gstreamer1.14.5)ではサポートされていませんのでご注意ください、 以下はhttps://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-splitmuxsink.html をもとにしています。 早速パイプラインの例 v4l2デバイスからのビデオ入力をmuxしてISO準拠mp4ファイルに記録します。時刻は ns=nanosec単位とのことで、0の数にはどうぞご注意ください。 (原文)Records a video stream captured from a v4l2 device and muxes it into ISO mp4 files, splitting as needed to limit size/duration to 10 seconds and 1MB maximum size. gst-launch-1.0 -e v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! timeoverlay ! x264enc key-int-max=10 ! h264parse ! splitmuxsink location=video%02d.mov max-size-time=10000000000 max-size-bytes=1000000 プロパティの例 エレメントのオプション値をプロパティと呼んでいますが、先にパイプライン例に含めたmux-size-bytes, max-size-time が汎用性の高いプロパティでしょう。2GB境界とか(いまさらw)、超えないように気を付けたいところです。 location multifilesinkと同じ、locationでファイル名を指定します。フォーマット形式 %02dなどで連番指定を行います。 The “location” property “location” gchar * Format string pattern for the location of the files to write (e.g. video%05d.mp4). Flags: Read / Write Default value: NULL muxer デフォルトのmuxerはmp4muxerです。自明に指示しなければNULLとなりデフォルトのmp4muxに設定されるそうです。 ポイントは、muxerが現在利用中のコーデックに対応している必要があります。また、パイプラインのparserも適切に選ぶ必要がありますね。async-finalize=falseでしか有効にならない、というのはTrueにしても何も起きないか、はじかれるということでしょうか。 The “muxer” property “muxer” GstElement * The muxer element to use (NULL = default mp4mux). Valid only for async-finalize = FALSE. Flags: Read / Write muxer-factory async-finalize = TRUEで有効になる、muxer指定子。 ...ということはつまりasync-finalizeで muxerプロパティと指定方法が切り替わるのですね The “muxer-factory” property “muxer-factory” gchar * The muxer element factory to use (default = mp4mux). Valid only for async-finalize = TRUE. Flags: Read / Write Default value: "mp4mux" muxer-properties muxer factoryで指定したmuxのオプション指定を行う。propertiesとあるとおり、複数指定が可能です。 The “muxer-properties” property “muxer-properties” GstStructure * The muxer element properties to use. Example: {properties,boolean-prop=true,string-prop="hi"}. Valid only for async-finalize = TRUE. Flags: Read / Write シグナルの例 例えばsplit-afterというシグナル(v1.16以降)は、現在進行中のGOP終了後新しいファイルにしてくれるとのこと。v1.14.5のJetsonでは使えません。 split-after The “split-after” signal void user_function (GstSplitMuxSink *splitmux, gpointer user_data) When called by the user, this action signal splits the video file (and begins a new one) immediately. The current GOP will be output to the old file. format-location-full リファレンスにpythonコールバックとして記載がありましたが、リファレンスの説明文は書きかけでいまいち判然としません。locationでのファイル名パターン指定では足りないときに使える、のでしょうか。ここまで手を突っ込むなら、パイプラインを起こす前に設定したいところですが... format-location-full def format_location_full_callback (splitmux, fragment_id, first_sample, udata): #python callback for the 'format-location-full' signal Parameters: splitmux – the fragment_id – the sequence number of the file to be created first_sample – A Gst.Sample containing the first buffer from the reference stream in the new file udata – No description available Returns (str) – the location to be used for the next output file. This must be a newly-allocated string which will be freed with GLib.free by the splitmuxsink element when it no longer needs it, so use GLib.strdup or g_strdup_printf (not introspectable) or similar functions to allocate it. Flags: Run Last Since : 1.12 スクリプト例 ソフトエンコーダで 10秒ごとに分割 gst-launch-1.0 -e v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! timeoverlay ! x264enc key-int-max=10 ! h264parse ! splitmuxsink location=video%02d.mov max-size-time=10000000000 max-size-bytes=1000000 それでは、また! happy encoding life!
- 投稿日:2021-10-31T10:17:47+09:00
Pytorch L1 loss カスタマイズ
環境 Ubuntu 18.04.4 LTS Python 3.6.8 Pytorch 1.8.1+cu102 やりたいこと L1 loss から 0 を除いた平均値を算出して逆伝播させたい。 L1 loss Pytorch document MAE (mean absolute error) 誤差の絶対値の平均ですね。 L1 loss を自作。 import torch import torch.nn as nn class CustomLoss(nn.Module): def __init__(self): super().__init__() def forward(self, outputs, targets): return torch.abs(outputs - targets).mean() 本当に再現できているか本家 torch.nn.L1Loss() と比較 class CustomLoss(nn.Module): def __init__(self): super().__init__() def forward(self, outputs, targets): loss_1 = torch.abs(outputs - targets).mean() loss_2 = torch.nn.L1Loss()(outputs, targets) print(loss_1.item() == loss_2.item()) return loss_1 True 0 を除いて平均を出すように変更 class CustomLoss(nn.Module): def __init__(self): super().__init__() def forward(self, outputs, targets): loss_abs = torch.abs(outputs - targets) return loss_abs[loss_abs != 0.0].mean() ひとまず、やりたいことは実現できた。
- 投稿日:2021-10-31T09:14:23+09:00
FastAPIで作るWebアプリ - Form validation
今回はFastAPIのFormのValidationについてです。 【過去記事】 Python Asyncio入門 Aiohttpで作るSimple Web Server - Qiita Starletteで作る Simple Web Server - QIita FastAPIで作るWebアプリ - 基本 FastAPIで作るWebアプリ - validation FastAPIで作るWebアプリ - Body validation FastAPIで作るWebアプリ - Form validation FastAPI 公式サイト Bodyの時とほぼ同じプログラムです。 form1.py from fastapi import Form, FastAPI app = FastAPI() @app.post("/items/") async def create_item(name: str=Form(...), price: float=Form(...)): return {"name": name, "price": price} Form()関数はBody()関数とほぼ同じですが、以下の違いを吸収します。 Body Request => Content-Type: application/json Form Data => Content-Type: application/x-www-form-urlencoded 正常なデータをRequestします。 curl -X 'POST' 'http://localhost:8000/items/' -H 'accept: application/json' -H 'Content-Type: application/x-www-form-urlencoded' -d 'name=Hanako&price=5.3' サーバ側は200 okの出力をし、クライアント側は以下のデータを受け取ります。 {"name":"Hanako","price":5.3} もちろんエラーデータのRequestに対しては、Bodyの時と同じようにエラーとなります。 今回は以上です。
- 投稿日:2021-10-31T08:27:55+09:00
pythonのfunctoolsについてまとめてみた
今回のお題 今回は、pythonのfunctoolsというモジュールについてまとめます。 元々はfunctools.wrapについて知りたかっただけなのですが、その過程で他の関数についてもいくつか勉強せざるを得なかったので記念に残しておきます。 目次 functoolsモジュールとは functools.partial functools.update_wrapper functools.wrap functoolsモジュールとは functoolsモジュールとは、「ある関数を操作したり、またそれによって新たな関数を取得したりできる関数をまとめたモジュール」のことです。 つまり、このモジュールをimportすることで関数に対してさまざまな操作を加えることができるようになるのですね。 言葉だけで説明してもわかりづらいと思うので、早速具体例を紹介していきます。 なお、今後の本記事のソースコードにおいては全てfunctoolsモジュールのimportは省略されています。 その点にはご注意ください。 functools.partial functools.partialは、ある関数の引数やキーワード引数の一部を固定して新しい関数を作り出すことができます。 functools.partialの一例 def info(name, age): print(f"{name}さんは{age}歳です。") func_1 = functools.partial(info, "John") func_1(30) # 出力:Johnさんは30歳です。 上記の例では、partialの第一引数であるinfo関数に対してpartialの第二引数である"John"が渡されています。 これによりinfo関数の第一引数がJohnで固定された新しい関数が作られ、それがfunc_1に代入されています。 なお、上記の例でinfoの第一引数以外(例えばage)に引数を渡したい場合には、キーワード引数を用いることになります。 def info(name, age): print(f"{name}さんは{age}歳です。") func_1 = functools.partial(info, age=30) func_1("John") # 出力 Johnさんは30歳です。 functools.update_wrapper functools.update_wrapperは、「ある関数の属性を別の関数の属性で上書きする」ためのものです。 属性とは関数名や関数のモジュール名などのことで、クラスやモジュールなどにも属性が設定されています。 属性値の例 print(print.__name__) # 出力:print # printという関数の関数名はprint print(print.__module__) # 出力:builtins # printはビルトイン関数 class Hoge: pass print(Hoge.__name__) # 出力:Hoge # Hogeクラスのクラス名はHoge では、update_wrapperで属性値を上書きするとどうなるでしょうか。 def hoge(): pass def fuga(): pass # そのまま出力 print(hoge.__name__) # 出力:hoge # 上書きして出力 print(update_wrapper(hoge, fuga).__name__) # 出力:fuga ちなみに、上書きされる要素は以下の通りであり、逆にこの部分を引数で指定することでアップデート内容を操作できます。 WRAPPER_ASSIGNMENTS(第3引数) = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES(第4引数) = ('__dict__',) 引数操作の例 def hoge(): pass def fuga(): pass # そのまま出力 print(hoge.__name__) # 出力:hoge # 引数を限定した上書き print(update_wrapper(hoge, fuga, "__doc__").__name__) # 出力:hoge また、update_wrapperで上書きした内容は基本的にその場限りではなく持続します。 def hoge(): pass def fuga(): pass # そのまま出力 print(hoge.__name__) # ここでは出力:hoge # 上書き print(update_wrapper(hoge, fuga).__name__) # ここで出力:fugaに上書き print(hoge.__name__) # ここでも出力:fugaのまま なお、update_wrapperを用いた上書きは、主にで主にデコレータによる属性値の上書きを防ぐ目的で使用されるようです。 デコレータ、及びデコレータによる属性値の上書きについては今回は省略するので、各自でお調べいただくようにお願いいたします。 functools.wraps この関数については、上記のupdate_wrapperを踏まえた上で公式ドキュメント(の日本語訳)を引用します。 これはラッパー関数を定義するときに update_wrapper() を関数デコレータとして呼び出す便宜関数です。 つまりつまりあるデコレータを定義する際にこのfunctools.wrapsをデコレータとして用いておくことで、デコレータによる属性値の上書きを防ぐことができるのですね。 import functools def deco(f): @functools.wraps(f, "__name__") def wrapper(*args, **kwargs): """test""" return f(*args, **kwargs) return wrapper @deco def hoge(): """sample""" pass print(hoge.__name__) # 出力:hoge # __name__はwrapsで指定しているのでそのまま print(hoge.__doc__) # 出力:test # __doc__はwrapsで指定していないので上書きされる 今回は例として属性値を指定する方法を取りましたが、指定しなかった場合の初期値はupdate_wrapperと同じです。 また、公式にはデコレータ定義時に使用、とありましたが、属性値を上書きするデコレータとしても一応使えます。 import functools @functools.wraps(sum) def hoge(): pass print(hoge.__name__) # 出力:sum 終わりに 以上でfunctoolsについての解説を終わります。 今回はfunctools.wrapsを理解する上で必要になった箇所をまとめただけですが、functoolsには他にもたくさんの関数がありますので、機会があればそちらについてもまとめたいと思います。