20211025のPythonに関する記事は29件です。

二重降下の例

1. 二重降下の例 論文 Two models of double descent for weak features (https://arxiv.org/abs/1903.07571) の例 https://github.com/tohmae/double-descent-sample/blob/main/sample.ipynb $ \beta = (\beta_{1},\beta_{2},....,\beta_{D}) \in \mathbb{R}^D : 固定$ $ x = (x_{1},x_{2},....,x_{D}) : 正規分布$ $ \sigma\epsilon : ノイズ $ $ y = x^* \beta + \sigma\epsilon = \sum_{j=1}^{D} x_{j} \beta_{j} + \sigma\epsilon$ 初期設定 D=200 train_num=80 #学習データの数 test_num=20 #テストデータの数 sigma = 1/5 #ノイズの標準偏差 b = 2 * np.random.rand(D) -1 b = b / np.linalg.norm(b) # β |β|=1 学習データ、テストデータ(x) train_X = np.random.randn(train_num, D) test_X = np.random.randn(test_num,D) 学習データ、テストデータ(y) train_y = np.matmul(train_X, b.T) + np.random.normal(0, sigma, train_num) test_y = np.matmul(test_X, b.T) + np.random.normal(0, sigma, test_num) パラメータ数を制限した時のβの予測値 def calc_reg(param_num): b_pred = np.linalg.lstsq(train_X[:,:param_num], train_y, rcond=None)[0] return b_pred 予測パラメータ時のloss def calc_loss(X, y, param_num, b_pred): y_pred = np.matmul(X[:,:param_num], b_pred.T) loss = np.sum(np.abs((y - y_pred)*2))/ len(y) return loss パラメータ数を変更した時の学習データおよびテストデータのloss param_nums = [] train_losses = [] test_losses = [] for param_num in range(1,D+1): b_pred = calc_reg(param_num) train_loss = calc_loss(train_X, train_y, param_num, b_pred) test_loss = calc_loss(test_X, test_y, param_num, b_pred) param_nums.append(param_num) train_losses.append(train_loss) test_losses.append(test_loss) 可視化 import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(8,8)) ax.plot(param_nums, train_losses, label="train") ax.plot(param_nums, test_losses, label="test") ax.legend(loc=0) plt.ylim(-1,5) fig.tight_layout() plt.show() lossのグラフ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

幾何分布を数学的にもpythonでもアプローチする

統計関係が続いているのですが、統計検定に受かるまで続くんじゃないかと思っています笑 基礎は理解している(と思っている)ものの、「幾何分布」がいまいち自分の中で定着しなかったので書きます。 急に「平均は $\frac{1}{p}$ やねん」とだけ書かれているテキストも多いのが原因かなと思い、数学的な導出とpython での実装をミックスした自分専用の「幾何分布大全」でも作ろうかと。 自分のテーマとして「読むだけでもなんとなく理解できる」ことをいつも目的としているため、文章多めかも。 前提 幾何分布は「ある事象(確率p)が初めて観測されるまでの期待値とかその確率はどのくらい?」ということを知りたいことが背景にあります。 統計検定では「営業している人が初めてちゃんと受け答えしてくれる確率を求めよ」みたいなのがありました。 それ以外にも初めてコインで裏が出る期待値とか、色々実生活の思考実験などしても面白そうですね (ちなみに、幾何分布は離散ですが、連続型を考慮すると指数分布というものになります。ここでは省略) 幾何分布の式 では、実際に具体例をみながらイメージをさらに固めていきます。 例えば、ある一軒家にめちゃくちゃ怖い犬がいて、人が通り過ぎるとpの確率でめちゃくちゃ吠えるとします(だいぶ特異な例を持ち出していることは自覚している)。 ここで、その犬が朝起きてから初めてx人目に向かって初めて吠える確率関数をP(x)とすると、P(x)の式は $$P(x) = (1 - p)^{x-1}p$$ となります。 ここまではOKかなと思います (x回目に初めて事象が発生するため、その前x-1回目までは全て(1-p)) 別にこの辺はわかるんだよ!って方、いよいよかもです 幾何分布の期待値と分散 確率変数を求める部分までは覚えなくても大体わかるよっていう人も、急に 期待値 = $\frac{1}{p}$ 、分散 = $\frac{1-p}{p^2}$ というものは暗記しましょう!というのはだいぶ酷な気がしました。 なので、ちゃんと導出しましょう。 期待値 まず期待値の定義式に則り、今回は離散であることも踏まえると \begin{align} E[x] &= \sum_{x=1}^{∞}x(1-p)^{x-1}p \\ &= p\sum_{x=1}^{∞}x(1-p)^{x-1} \end{align} 実はなぜにテキストが急に期待値とか分散になると下を向いて「そーなりますねん」っていうスタンスになるのかといえば、 マクローリン展開という割とガチガチの数学的手法が必要とされるからだと調べていくうちに知りました。 (統計学は割と文理問わず最近は学ぶ人も多いので、マクローリン展開って言われてもね〜みたいな人への配慮かもしれませんね) マクローリン展開は以下。 $$\frac{1}{1-x} = 1 + x + x^2 + ・・・ = \sum_{k=0}^{∞}x^k$$ (ここではマクローリン展開がなぜ成り立つのか?みたいな導出の過程は省略します) これをそのまま使うわけでもなく、両辺xで微分すると、 $$\frac{1}{(1-x)^2} = \sum_{k=0}^{∞} kx^{k-1}$$ なので、x = 1 - p, k=xとすれば、 \begin{align} E[x] &= p\sum_{x=1}^{∞}x(1-p)^{x-1} \\ &= p * \frac{1}{(1 - (1-p))^2} \\ &= p * \frac{1}{p^2} \\ &= \frac{1}{p} \end{align} 導出完了。 さすがに、この導出方法を毎度覚えておくことは無謀であり、最終的には「覚える」というのが最短であることは否めませんが、それでも一度ちゃんと導出しておけば、理解も深まると個人的に思います。 分散 次に分散。 定義式は以下。 $$ V[x] = E[x^2] - (E[x])^2$$ 先ほどのマクローリン展開を微分したものをさらに微分したものを使います。 微分しちゃいましょう $$\frac{2}{(1-x)^3} = \sum_{k=2}^{∞} k(k-1) x^{k-2}$$ ここに、先ほどと同じくx = 1-p, k=xを入れると $$\sum_{x=2}^{∞} x(x-1) (1-p)^{x-2} = \frac{2}{p^3}$$ このままの導出は難しいので、両辺に(1-p)をかけます。 (こういう数学独特の発想みたいなのは今回は追求しません笑) $$\sum_{x=2}^{∞} x^2 (1-p)^{x-1} = \frac{2(1-p)}{p^3}$$ そこで、以下の天才しか思いつかないであろう期待値を用意します。 $$ E[x(x-1)] = p\sum_{x=1}^{∞} x(x-1)(1-p)^{x-1} = \frac{2(1-p)}{p^2}$$ では、分散を求めます \begin{align} V[x] &= E[x^2] - (E[x])^2 \\ &= E[x(x-1)] + E[x] - (E[x])^2 \\ &=  \frac{2(1-p)}{p^2} + \frac{1}{p} - \frac{1}{p^2}\\ &= \frac{1-p}{p^2} \end{align} 言いたいことはわかります。グッと堪えてください笑 どう考えてもこの導き方は天才以外思いつきませんw ですが、導出を問題にすることは実生活上意味がないので、あくまで「導出してみた」というくらいで十分だと思います pythonで幾何分布を扱う では、最後にpythonで少しみてみます いつもながら、こんな感じ、くらいで。 import numpy as np import matplotlib.pyplot as plt from scipy import stats # 確率(今回サイコロを想定) p=1/6 x = np.arange(1, 20) y = stats.geom.pmf(k=x, p=p) # それぞれの統計量 x_stats = stats.geom.stats(p=p, loc=0, moments='mv') # 公式からの導出 mean = 1/p var = (1-p)/p**2 print(f'公式で計算\n Mean: {mean}, Variance: {var:.0f}') plt.figure(figsize=(8, 5)) plt.plot(x, y, 'bo') plt.vlines(x, 0, y) # vlines(x, ymin, ymax) plt.ylabel('Probability') plt.title(f'Mean: {int(x_stats[0])}, Variance: {int(x_stats[1])}', fontsize=20) plt.show() 実際に公式で計算した場合と、pythonで自動で導出した時と一致しました! そして、幾何分布のグラフイメージもこれでバッチリですな。 今回参考にさせていただいた記事 幾何分布の確率関数からの期待値と分散の導出 Matplotlib で水平線と垂直線をプロットする方法 scipy.stats.geom
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoレッスンメモ

Djangoを学習するにあたり、YouTubeの動画を参照しました。 以下の二つが検索上位にヒットし、内容も分かりやすかったように思います。 レッスンで学んだキーポイントを挙げておきます。 https://www.youtube.com/watch?v=rHux0gMZ3Eg&t=1325s Djangoの基礎を教えてくれます。DjangoのViewはなんでそういう名前にしちゃったかな。役割としてはRequest Handlerなんだけど、って言ってたような気がします。 紹介していたdjango-debug-toolbarが便利そうでした。 https://www.youtube.com/watch?v=e1IyzVyrLSU こちらはアンケートアプリを題材に、最初のインストールから、簡単なアプリができるまで解説してくれるので、Djangoの動き、開発の流れをつかむのに役に立ちました。 といっても、今日から学び始めたので、まだまだこれからです。 この記事も随時アップデートしていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1macにmatplotlibを入れる

はじめに M1macでmatplotlibをimportしたらエラーが出る numpy関連のエラーらしい TensorFlow-macosをインストールしてあるからnumpyでエラーが出るのはおかしい 調べてみたら追加でパッケージをインストールしないといけないらしいのでまとめる matplotlibを使えるようにする numpyをインストール matplotlibをインストール libjpegをインストール 1は既にnumpyを入れてる人は不要 ただし私はTensorFlow-macosをインストールした際に一緒にインストールしたものなので、それ以外の方法でインストールした人は知らない numpyをインストール 私のこの記事を参考にtensorflow-macosをインストール そしたら一緒にnumpyがインストールされる matplotlibをインストール 普通にpipでインストール pip install matplotlib libjpegをインストール 下のコマンドを叩いく brew install libjpeg
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python3】0から作るPython初心者プログラミング【02】-じゃんけんプログラムをより使いやすく-

【ご挨拶】こんにちは! ぬかさんエンジニアリングです。(2回目‼) 前回の投稿にありがたいことにコメントを頂きまして、じゃんけんプログラムをより簡潔に書ける方法を教えていただきました。 今回はそれらの内容も含めてじゃんけんプログラムを関数化、そしてクラス化というように進化させていく内容になっています! 普段クラスから作っているという方も、順を追ってみていくことで関数とクラスの便利さに気づけるような構成になっていますので最後まで見て頂けると嬉しいです! LGTMも是非宜しくお願い致します‼ 本シリーズ初めての方へ 【趣旨】 Python初心者プログラマーが入門書で学んだ知識を実践的なコーディングを通じて身に着けていくためのお題を提供します。 お題を基に各自コーディングに挑戦していただいた後、この記事でコーディングの過程を答え合わせします。 【対象】 Pythonの入門書を読んで理解はしたけど何か目的をもって実践的にコーディングをしたい方。 ProgateでPythonコースをLv5まで勉強したけど応用力が身に着いていないと感じている方。 【初心者とは】 この記事シリーズでの初心者は下記の項目を理解済みであることが目安となっています。 [演算子, 標準ライブラリ, 条件分岐, 繰り返し処理, 例外処理, リスト, タプル, セット, 辞書, オブジェクト指向] 【利用ライブラリ & Python3 --version】 ・Google Colaboratory 以下のリンクからアクセスして使い方を確認した後、左上のファイルタブから「ノートブックを新規作成」を選択して自分のプログラムを作りましょう。ファイルはGoogleDriveに保存されるため、自分のGoogleアカウントと連携させるのを忘れないようにしましょう。 Colaboratory へようこそ ←ここからリンクへ飛ぶ ・Python 3.7.12 $$$$ それではさっそく本題に入っていきましょう 第【02】回 -じゃんけんプログラムをより簡潔に- このシリーズ第二回目の今回は、前回のじゃんけんプログラムを応用してじゃんけんプログラムのクラス化をしていきます! 前回のコードをさらに簡潔化させた上で、前回利用しなかったユーザー定義関数とクラスを用いることでその便利さを皆さんと一緒に体感していきたいと思います。 【お題】じゃんけんプログラムをクラス化しよう! janken_battle = Janken() janken_battle.janken() #出力 ------------------------------------------------------- じゃんけんの選択肢:{0: 'グー', 1: 'チョキ', 2: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:0, 1, 2) > 1 コンピュータの手:パー あなたの手:チョキ <><><><><><><><><> 勝敗結果:勝ち <><><><><><><><><> 1.Jankenクラスを定義し、オブジェクトの仕様(クラスの中身)を書いてください。 2.Jankenクラスからjanken_battleインスタンスを作成してください。 3.jankenインスタンスメソッドを実行して上記の出力を得てください。 〔お助けヒント〕 ヒント1 オブジェクトの仕様(クラスの中身)を書くには、前回投稿分の解答を参考にして下さい。 ヒント2 クラスの中身に入れる処理は全て関数の形に変更してください。 ヒント3 関数に変更する際に、引数や返り値の概念が加わることで一部コードを変更しなくてはいけなくなることに気を付けてください。 ヒント4 クラス化する際に、クラスのルールで一部コードを変更しなくてはいけなくなることに気を付けてください。 【解答】 解答は以下の通りです。 ※この解答はあくまで私の回答です。各々の方法でお題が解けていればそれで全然かまいません。むしろ、もっと簡潔な方法があればご教示頂けると助かります 0P【02】解答.py import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} class Janken: def __init__(self, d): self.my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(self): return random.choice(self.my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(self): my_hand = 999 while my_hand not in self.my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(self, computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" #出力 def janken(self): print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = self.computer_hand_func() my_hand = self.my_hand_func() result = self.judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) janken_battle = Janken(d) janken_battle.janken() #出力 ------------------------------------------------------- じゃんけんの選択肢:{0: 'グー', 1: 'チョキ', 2: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:0, 1, 2) > 1 コンピュータの手:パー あなたの手:チョキ <><><><><><><><><> 勝敗結果:勝ち <><><><><><><><><> 【解説】 ※本解説の中で使われる用語に関する補足事項 メソッド == 関数 初期化メソッド == コンストラクタ インスタンス変数 == メンバ変数 〔クラスの概念〕 プログラミングで使う便利なお道具箱の枠組みは図の通り5つの入れ子構造になっており、大きい順からライブラリ、パッケージ、モジュール、クラス、メソッドとなっています。 どの言葉も何となく聞いたことが有るかとは思いますが、実際はこんな構造になっていたんですね。 モジュールは一つ一つのPythonファイルのことで、その中にクラスやメソッドが入っていると考えるとよりイメージがしやすいのではないでしょうか。 他の人が作ってくれたお道具箱から必要な部分だけ自分のプログラムに取り込むことで効率化できたり、自分が作ったお道具箱をオープンソースで使ってもらえたりと、お道具箱を通してエンジニア同士の相互扶助が行われているって考えると素晴らしいですよね。 では、そんなお道具箱の中でクラスの立場はどうなのかと言うと、なにやら変数とメソッドをまとめている存在みたいですよね。これがどんな意味を表しているのかはオブジェクト指向を学べば分かります。簡単に表すとするならばある「モノ」の「属性」を変数という形で、「モノ」でする「行動」をメソッドという形でまとめている設定資料みたいな存在です。 その設定資料から実物を作ることをインスタンスを作成すると言い、作成したインスタンスでする「行動」をメソッドを使って実行します。 1つのクラスから複数のインスタンスを作ることも可能で、各インスタンごとに「属性」だけをカスタマイズすることによって似たようなインスタンスを簡単に作ることができます。 よく、車についての設定資料から色々な車種を作り出せることに例えられたりします。 今回のじゃんけんプログラムで言うと、じゃんけんの設定資料であるJankenクラスからインスタンスjanken_battleを作成し、じゃんけんゲームをするjankenメソッドを実行するということです。 ちなみに、インスタンスは英語に直訳すると「実例」という意味になります。クラスという設定資料から作った実例がインスタンスだと思えばもう謎の存在ではなくなるはずです。 まとめると、クラスはモジュールの中にあるもので、ある「モノ」の「属性」と「モノ」でする「行動」をまとめている設定資料的存在と言うことになります。 今回は分かりやすさに重点を置いて説明したので厳密性には少々欠けていると思います。今後別の記事でオブジェクト指向についてしっかり書きたいと考えていますのでまたの機会に。 〔作り方〕 オブジェクトの仕様(クラスの中身)にはインスタンス変数やインスタンスメソッドを入れるのが一般的です。 前回はあえてユーザー定義関数を使わずに各機能をコーディングしていきました。それは、今回こうして必要な時にユーザー定義関数を使ってクラスの中身を作って頂き、同時にクラスと関数の便利さを体感して頂きたかったからです。 コード自体はそこまで前回と変わることはありません。しかし、クラスや関数にすることで得られるメリットはたくさんあります。そんなお得な関数を一緒に作っていきましょう。 それではさっそく作り方をご紹介していきます。 ①各機能の処理をユーザー定義関数に変換 import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(): my_hand = 999 while my_hand not in my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" def janken(): #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() my_hand = my_hand_func() result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) #janken関数を呼び出す janken() パッと見てどうでしょう。あまり変化したように見えないんじゃないでしょうか。その通りでほとんどコードは変わっていません。 基本的に、各機能の処理の最終的な出力を変数に代入して定義するのではなく、returnで返り値として返す方法に変更しているだけです。 しかし、これのみの変更でありながら関数にすることで得られるメリットはたくさんあります。 ⑴毎回コードを書かずとも、関数名()と書くだけで同じ処理ができるところ 一度関数を設定してしまえば同じ処理を短い関数名で呼べるので圧倒的な時間の短縮にもなりますし、他人も使いやすくなります。 例えると、ピカソに絵を描いてほしい時に毎回本名で「パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・マリーア・デ・ロス・レメディオス・クリスピン・クリスピアーノ・デ・ラ・サンディシマ・トリニダード・ルイス・イ・ピカソ」って呼ぶのだるすぎるから「ピカソ」だけで呼べるようにしちゃおうぜってことです。覚えやすいし使いやすい! ⑵処理の具体的な処理内容を理解していなくとも、引数のルールと返り値(出力結果)の意味さえ分かっていれば簡単に使えてしまうところ 組み込み関数(Pythonで最初から使える関数)の処理の具体的な処理内容ってPython教材でもあまり載っていませんよね?それよりはどういう引数を入れたらどういう返り値が帰ってきてどういう時に使えるかの方が詳しく載っていますよね。つまり、関数の具体的な処理内容は関数の使い手にっとってそれほど気にするポイントではないということです。気にしなくてもしっかり動いてくれる関数くんすごい! ⑶規模の大きいプログラムにおいて、各所で何の機能が動いているのか分かりやすく可読性向上になるところ プログラムの規模が大きくなるとコードがずらあーーっと数千数万並んでいて何が何だか分からなくなってしまうなんてことがあってもおかしくありません。そんな中からいちいち「何行目から何行目は○○の処理」「何行目から何行目は△△の処理」と見ていてはキリがありません。だからこそ、コードを切り分けて各処理ごとに関数でまとめることで分かりやすくするのです。特に大規模なプログラムはチームで作るので、他人が見てもわかりやすくするために関数にまとめるのは大切です。 関数のメリットは他にも細かくあるとは思いますがざっくりとこんなものです。これらはクラスにも適応できる考え方です。クラスは「インスタンス」を作成して属性となる変数やメソッドをまとめて簡潔に呼び出せるようになっているので⑴が適応されますし、規模が大きいときにまとまりがあって可読性が上がるので⑶も適応されます。⑵は、関数よりはクラスの方が中身を理解していないと使えません。とにかくまとめて読みやすさをアップしましょう。 関数のメリットを熱くご紹介した上で、今回のじゃんけんプログラムを見てみましょう。 ちゃんと関数の恩恵を受けていますよ!何故ならjanken()とコードを書いて実行するだけでいつでもどこでもじゃんけんが出来るようになったじゃないですか!実感が湧かない人は以下の「関数化による変更点の詳細情報」を見ながら実際に手を動かして感動してみてください! 関数化による変更点の詳細情報 ◆importの場所について変更 #変更前 #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム import random #<<<<<<<<<< def computer_hand_func(): return random.choice(my_hand_key) #変更後 import random #<<<<<<<<<< #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) 基本的にクラスやメソッドの中にモジュールやライブラリはimportしません。 特別な理由がない限りはそうしてください。 ◆コンピュータの手アルゴリズムを一部変更 #変更前 #コンピュータの手アルゴリズム def computer_hand_func(): return random.randint(0, 2) #<<<<<<<<<< #変更後 #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) #<<<<<<<<<< 辞書キーの値の変更、追加に対して自動的に対応できるようにrandint()ではなくchoice()に変更します。choice()は引数としてシーケンスを受け取り、そのシーケンスの中からランダムに要素を返します。 シーケンスが変われば自動的にコンピュータの手も変更されるためプログラムの頑健性が上がります。 補足:シーケンスとは リストやタプルなどのオブジェクトは、コレクションの一種で、他のオブジェクトを登録し、集約できるオブジェクトです。 シーケンスとは、コレクションのうちで、集約する要素が一定の順序で並んでいて、その順序(インデックス)を使ってその要素を指定できる種類のオブジェクトのことを指します。 ざっくりまとめるとインデックスで要素を指定できるリストやタプルのこと python Japan シーケンス←参考資料 ◆入力アルゴリズムを一部変更 #変更前 #入力(自分の手)アルゴリズム my_hand = 999 #<<<<<<<<<< while my_hand not in my_hand_key: #<<<<<<<<<< my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") #変更後 #入力(自分の手)アルゴリズム def my_hand_func(): my_hand = 999 while my_hand not in my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: #<<<<<<<<<< return my_hand #<<<<<<<<<< 前回投稿分の③入力(自分の手)アルゴリズムの項目にある「While文の代表的な使い方」から、elseを使う方法に変更してwhile文を抜け出したら返り値my_handを返すように一部変更する。 ◆出力部分をjanken関数として定義する。 #変更前 #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() #<<<<<<<<<< my_hand = my_hand_func() #<<<<<<<<<< result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) #変更後 def janken(): #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() #<<<<<<<<<< my_hand = my_hand_func() #<<<<<<<<<< result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) jankenメソッドを定義すれば、janken()を入力するだけで「じゃんけんプログラム」が実行されます。 関数名と関数を代入する変数名が同じだと、グローバル変数を関数内で使ってしまうことになりErrorになるので、関数名を一部変更する。(変数名を変更してもよい。異なっていれは大丈夫) 以上が関数化に際する詳細な変更点になります。 ②じゃんけんプログラムの全ての処理をインスタンスメソッド化して記入 import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} class Janken: def __init__(self, d): self.my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(self): return random.choice(self.my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(self): my_hand = 999 while my_hand not in self.my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(self, computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" #出力 def janken(self): print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = self.computer_hand_func() my_hand = self.my_hand_func() result = self.judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) janken_battle = Janken(d) janken_battle.janken() ⑴じゃんけんの手の辞書をクラスの外で定義 じゃんけんの手の辞書d = { 0:"グー", 1:"チョキ", 2:"パー"}はじゃんけんクラスにおける属性であると考えて、インスタンス変数self.dを辞書で初期化したい。 そのためにクラスの外で辞書を定義して、インスタンスjanken_battleを作成した際に引数dが辞書を受け取り、初期化メソッドによって自動的にインスタンス変数self.dを辞書で初期化する方法を取ります。 こうすることによって、辞書の内容を変更して「グーチョキパー」以外の三すくみゲーム用のインスタンスを作るなど、インスタンスごとのカスタマイズが可能になります。 例)d = { 0:"水タイプ",1:"炎タイプ",2:"草タイプ"} ⑵各メソッドの第1引数にselfを設定 jankenメソッドを実行する際にインスタンスjanken_battle自身を参照する必要があるのでselfを設定します。また、jankenメソッドを実行する際に使うcomputer_hand_funcメソッド、my_hand_funcメソッド、judgeメソッドを実行する際にもインスタンスjanken_battle自身を参照する必要があり、各メソッドの第1引数にもselfを設定します。 以上でクラス化は完了です。 各自実行して便利さを実感してみてください。 クラスをモジュールとしてPythonファイルに保存して、別ファイルのプログラムにimportすると更に便利さを実感できるかと思いますが、それはまた次回以降に。 【終わりに】 今回は、前回作ったじゃんけんプログラムを関数に変換した後クラス化するというものでした。いかがっだったでしょうか。 オブジェクト指向に慣れている方は最初からクラスを作ればいいじゃんと言う発想に至ったかもしれませんね。 ただ、オブジェクト指向は勉強したけどいまいちメソッドやクラスについて何が便利か分からなかったという方には、順を追って理解してもらういい機会になったんじゃないでしょうか。 次回は、今回作ったJankenクラスを使って「あっち向いてほいプログラム」を皆さんと一緒に作っていこうと考えていますのでよろしくお願い致します。 良記事だと思ってくれた方は是非LGTMを宜しくお願いします!それではまた次回!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python3】0から作るPython初心者プログラミング【02】-じゃんけんプログラムをクラス化する-

【ご挨拶】こんにちは! ぬかさんエンジニアリングです。(2回目‼) 前回の投稿にありがたいことにコメントを頂きまして、じゃんけんプログラムをより簡潔に書ける方法を教えていただきました。 今回はそれらの内容も含めてじゃんけんプログラムを関数化、そしてクラス化というように進化させていく内容になっています! 普段クラスから作っているという方も、順を追ってみていくことで関数とクラスの便利さに気づけるような構成になっていますので最後まで見て頂けると嬉しいです! LGTMも是非宜しくお願い致します‼ 本シリーズ初めての方へ 【趣旨】 Python初心者プログラマーが入門書で学んだ知識を実践的なコーディングを通じて身に着けていくためのお題を提供します。 お題を基に各自コーディングに挑戦していただいた後、この記事でコーディングの過程を答え合わせします。 【対象】 Pythonの入門書を読んで理解はしたけど何か目的をもって実践的にコーディングをしたい方。 ProgateでPythonコースをLv5まで勉強したけど応用力が身に着いていないと感じている方。 【初心者とは】 この記事シリーズでの初心者は下記の項目を理解済みであることが目安となっています。 [演算子, 標準ライブラリ, 条件分岐, 繰り返し処理, 例外処理, リスト, タプル, セット, 辞書, オブジェクト指向] 【利用ライブラリ & Python3 --version】 ・Google Colaboratory 以下のリンクからアクセスして使い方を確認した後、左上のファイルタブから「ノートブックを新規作成」を選択して自分のプログラムを作りましょう。ファイルはGoogleDriveに保存されるため、自分のGoogleアカウントと連携させるのを忘れないようにしましょう。 Colaboratory へようこそ ←ここからリンクへ飛ぶ ・Python 3.7.12 $$$$ それではさっそく本題に入っていきましょう 第【02】回 -じゃんけんプログラムをクラス化する- このシリーズ第二回目の今回は、前回のじゃんけんプログラムを応用してじゃんけんプログラムのクラス化をしていきます! 前回のコードをさらに簡潔化させた上で、前回利用しなかったユーザー定義関数とクラスを用いることでその便利さを皆さんと一緒に体感していきたいと思います。 【お題】じゃんけんプログラムを関数を使ってクラス化してみよう! janken_battle = Janken() janken_battle.janken() #出力 ------------------------------------------------------- じゃんけんの選択肢:{0: 'グー', 1: 'チョキ', 2: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:0, 1, 2) > 1 コンピュータの手:パー あなたの手:チョキ <><><><><><><><><> 勝敗結果:勝ち <><><><><><><><><> 1.Jankenクラスを定義し、オブジェクトの仕様(クラスの中身)を書いてください。 2.Jankenクラスからjanken_battleインスタンスを作成してください。 3.jankenインスタンスメソッドを実行して上記の出力を得てください。 〔お助けヒント〕 ヒント1 オブジェクトの仕様(クラスの中身)を書くには、前回投稿分の解答を参考にして下さい。 ヒント2 クラスの中身に入れる処理は全て関数の形に変更してください。 ヒント3 関数に変更する際に、引数や返り値の概念が加わることで一部コードを変更しなくてはいけなくなることに気を付けてください。 ヒント4 クラス化する際に、クラスのルールで一部コードを変更しなくてはいけなくなることに気を付けてください。 【解答】 解答は以下の通りです。 ※この解答はあくまで私の回答です。各々の方法でお題が解けていればそれで全然かまいません。むしろ、もっと簡潔な方法があればご教示頂けると助かります 0P【02】解答.py import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} class Janken: def __init__(self, d): self.my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(self): return random.choice(self.my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(self): my_hand = 999 while my_hand not in self.my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(self, computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" #出力 def janken(self): print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = self.computer_hand_func() my_hand = self.my_hand_func() result = self.judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) janken_battle = Janken(d) janken_battle.janken() #出力 ------------------------------------------------------- じゃんけんの選択肢:{0: 'グー', 1: 'チョキ', 2: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:0, 1, 2) > 1 コンピュータの手:パー あなたの手:チョキ <><><><><><><><><> 勝敗結果:勝ち <><><><><><><><><> 【解説】 ※本解説の中で使われる用語に関する補足事項 メソッド == 関数 初期化メソッド == コンストラクタ インスタンス変数 == メンバ変数 〔クラスの概念〕 プログラミングで使う便利なお道具箱の枠組みは図の通り5つの入れ子構造になっており、大きい順からライブラリ、パッケージ、モジュール、クラス、メソッドとなっています。 どの言葉も何となく聞いたことが有るかとは思いますが、実際はこんな構造になっていたんですね。 モジュールは一つ一つのPythonファイルのことで、その中にクラスやメソッドが入っていると考えるとよりイメージがしやすいのではないでしょうか。 他の人が作ってくれたお道具箱から必要な部分だけ自分のプログラムに取り込むことで効率化できたり、自分が作ったお道具箱をオープンソースで使ってもらえたりと、お道具箱を通してエンジニア同士の相互扶助が行われているって考えると素晴らしいですよね。 では、そんなお道具箱の中でクラスの立場はどうなのかと言うと、なにやら変数とメソッドをまとめている存在みたいですよね。これがどんな意味を表しているのかはオブジェクト指向を学べば分かります。簡単に表すとするならばある「モノ」の「属性」を変数という形で、「モノ」でする「行動」をメソッドという形でまとめている設定資料みたいな存在です。 その設定資料から実物を作ることをインスタンスを作成すると言い、作成したインスタンスでする「行動」をメソッドを使って実行します。 1つのクラスから複数のインスタンスを作ることも可能で、各インスタンごとに「属性」だけをカスタマイズすることによって似たようなインスタンスを簡単に作ることができます。 よく、車についての設定資料から色々な車種を作り出せることに例えられたりします。 今回のじゃんけんプログラムで言うと、じゃんけんの設定資料であるJankenクラスからインスタンスjanken_battleを作成し、じゃんけんゲームをするjankenメソッドを実行するということです。 ちなみに、インスタンスは英語に直訳すると「実例」という意味になります。クラスという設定資料から作った実例がインスタンスだと思えばもう謎の存在ではなくなるはずです。 まとめると、クラスはモジュールの中にあるもので、ある「モノ」の「属性」と「モノ」でする「行動」をまとめている設定資料的存在と言うことになります。 今回は分かりやすさに重点を置いて説明したので厳密性には少々欠けていると思います。今後別の記事でオブジェクト指向についてしっかり書きたいと考えていますのでまたの機会に。 〔作り方〕 オブジェクトの仕様(クラスの中身)にはインスタンス変数やインスタンスメソッドを入れるのが一般的です。 前回はあえてユーザー定義関数を使わずに各機能をコーディングしていきました。それは、今回こうして必要な時にユーザー定義関数を使ってクラスの中身を作って頂き、同時にクラスと関数の便利さを体感して頂きたかったからです。 コード自体はそこまで前回と変わることはありません。しかし、クラスや関数にすることで得られるメリットはたくさんあります。そんなお得な関数を一緒に作っていきましょう。 それではさっそく作り方をご紹介していきます。 ①各機能の処理をユーザー定義関数に変換 import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(): my_hand = 999 while my_hand not in my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" def janken(): #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() my_hand = my_hand_func() result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) #janken関数を呼び出す janken() パッと見てどうでしょう。あまり変化したように見えないんじゃないでしょうか。その通りでほとんどコードは変わっていません。 基本的に、各機能の処理の最終的な出力を変数に代入して定義するのではなく、returnで返り値として返す方法に変更しているだけです。 しかし、これのみの変更でありながら関数にすることで得られるメリットはたくさんあります。 ⑴毎回コードを書かずとも、関数名()と書くだけで同じ処理ができるところ 一度関数を設定してしまえば同じ処理を短い関数名で呼べるので圧倒的な時間の短縮にもなりますし、他人も使いやすくなります。 例えると、ピカソに絵を描いてほしい時に毎回本名で「パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・マリーア・デ・ロス・レメディオス・クリスピン・クリスピアーノ・デ・ラ・サンディシマ・トリニダード・ルイス・イ・ピカソ」って呼ぶのだるすぎるから「ピカソ」だけで呼べるようにしちゃおうぜってことです。覚えやすいし使いやすい! ⑵処理の具体的な処理内容を理解していなくとも、引数のルールと返り値(出力結果)の意味さえ分かっていれば簡単に使えてしまうところ 組み込み関数(Pythonで最初から使える関数)の処理の具体的な処理内容ってPython教材でもあまり載っていませんよね?それよりはどういう引数を入れたらどういう返り値が帰ってきてどういう時に使えるかの方が詳しく載っていますよね。つまり、関数の具体的な処理内容は関数の使い手にっとってそれほど気にするポイントではないということです。気にしなくてもしっかり動いてくれる関数くんすごい! ⑶規模の大きいプログラムにおいて、各所で何の機能が動いているのか分かりやすく可読性向上になるところ プログラムの規模が大きくなるとコードがずらあーーっと数千数万並んでいて何が何だか分からなくなってしまうなんてことがあってもおかしくありません。そんな中からいちいち「何行目から何行目は○○の処理」「何行目から何行目は△△の処理」と見ていてはキリがありません。だからこそ、コードを切り分けて各処理ごとに関数でまとめることで分かりやすくするのです。特に大規模なプログラムはチームで作るので、他人が見てもわかりやすくするために関数にまとめるのは大切です。 関数のメリットは他にも細かくあるとは思いますがざっくりとこんなものです。これらはクラスにも適応できる考え方です。クラスは「インスタンス」を作成して属性となる変数やメソッドをまとめて簡潔に呼び出せるようになっているので⑴が適応されますし、規模が大きいときにまとまりがあって可読性が上がるので⑶も適応されます。⑵は、関数よりはクラスの方が中身を理解していないと使えません。とにかくまとめて読みやすさをアップしましょう。 関数のメリットを熱くご紹介した上で、今回のじゃんけんプログラムを見てみましょう。 ちゃんと関数の恩恵を受けていますよ!何故ならjanken()とコードを書いて実行するだけでいつでもどこでもじゃんけんが出来るようになったじゃないですか!実感が湧かない人は以下の「関数化による変更点の詳細情報」を見ながら実際に手を動かして感動してみてください! 関数化による変更点の詳細情報 ◆importの場所について変更 #変更前 #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム import random #<<<<<<<<<< def computer_hand_func(): return random.choice(my_hand_key) #変更後 import random #<<<<<<<<<< #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) 基本的にクラスやメソッドの中にモジュールやライブラリはimportしません。 特別な理由がない限りはそうしてください。 ◆コンピュータの手アルゴリズムを一部変更 #変更前 #コンピュータの手アルゴリズム def computer_hand_func(): return random.randint(0, 2) #<<<<<<<<<< #変更後 #コンピュータの手アルゴリズム def computer_hand_func(): return random.choice(my_hand_key) #<<<<<<<<<< 辞書キーの値の変更、追加に対して自動的に対応できるようにrandint()ではなくchoice()に変更します。choice()は引数としてシーケンスを受け取り、そのシーケンスの中からランダムに要素を返します。 シーケンスが変われば自動的にコンピュータの手も変更されるためプログラムの頑健性が上がります。 補足:シーケンスとは リストやタプルなどのオブジェクトは、コレクションの一種で、他のオブジェクトを登録し、集約できるオブジェクトです。 シーケンスとは、コレクションのうちで、集約する要素が一定の順序で並んでいて、その順序(インデックス)を使ってその要素を指定できる種類のオブジェクトのことを指します。 ざっくりまとめるとインデックスで要素を指定できるリストやタプルのこと python Japan シーケンス←参考資料 ◆入力アルゴリズムを一部変更 #変更前 #入力(自分の手)アルゴリズム my_hand = 999 #<<<<<<<<<< while my_hand not in my_hand_key: #<<<<<<<<<< my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") #変更後 #入力(自分の手)アルゴリズム def my_hand_func(): my_hand = 999 while my_hand not in my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: #<<<<<<<<<< return my_hand #<<<<<<<<<< 前回投稿分の③入力(自分の手)アルゴリズムの項目にある「While文の代表的な使い方」から、elseを使う方法に変更してwhile文を抜け出したら返り値my_handを返すように一部変更する。 ◆出力部分をjanken関数として定義する。 #変更前 #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() #<<<<<<<<<< my_hand = my_hand_func() #<<<<<<<<<< result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) #変更後 def janken(): #出力 print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = computer_hand_func() #<<<<<<<<<< my_hand = my_hand_func() #<<<<<<<<<< result = judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) jankenメソッドを定義すれば、janken()を入力するだけで「じゃんけんプログラム」が実行されます。 関数名と関数を代入する変数名が同じだと、グローバル変数を関数内で使ってしまうことになりErrorになるので、関数名を一部変更する。(変数名を変更してもよい。異なっていれは大丈夫) 以上が関数化に際する詳細な変更点になります。 ②じゃんけんプログラムの全ての処理をインスタンスメソッド化して記入 import random #じゃんけんの手の辞書を定義 d = {0:"グー",1:"チョキ",2:"パー"} class Janken: def __init__(self, d): self.my_hand_key = tuple(d.keys()) #コンピュータの手アルゴリズム def computer_hand_func(self): return random.choice(self.my_hand_key) #入力(自分の手)アルゴリズム def my_hand_func(self): my_hand = 999 while my_hand not in self.my_hand_key: my_hand = input("あなたの出す手を入力してください(整数:0, 1, 2) > ") try: my_hand = int(my_hand) except: print("整数0, 1, 2を入力をして下さい") else: return my_hand #勝敗判定アルゴリズム def judge(self, computer_hand, my_hand): result = False if my_hand == computer_hand: return "引き分け" else: if my_hand == 0 and computer_hand == 1: result = True elif my_hand == 1 and computer_hand == 2: result = True elif my_hand == 2 and computer_hand == 0: result = True if isinstance(result, bool): return "勝ち" if result else "負け" #出力 def janken(self): print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55) computer_hand = self.computer_hand_func() my_hand = self.my_hand_func() result = self.judge(computer_hand, my_hand) print(f"コンピュータの手:{d[computer_hand]}") print(f"あなたの手:{d[my_hand]}") print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6) janken_battle = Janken(d) janken_battle.janken() ⑴じゃんけんの手の辞書をクラスの外で定義 じゃんけんの手の辞書d = { 0:"グー", 1:"チョキ", 2:"パー"}はじゃんけんクラスにおける属性であると考えて、インスタンス変数self.dを辞書で初期化したい。 そのためにクラスの外で辞書を定義して、インスタンスjanken_battleを作成した際に引数dが辞書を受け取り、初期化メソッドによって自動的にインスタンス変数self.dを辞書で初期化する方法を取ります。 こうすることによって、辞書の内容を変更して「グーチョキパー」以外の三すくみゲーム用のインスタンスを作るなど、インスタンスごとのカスタマイズが可能になります。 例)d = { 0:"水タイプ",1:"炎タイプ",2:"草タイプ"} ⑵各メソッドの第1引数にselfを設定 jankenメソッドを実行する際にインスタンスjanken_battle自身を参照する必要があるのでselfを設定します。また、jankenメソッドを実行する際に使うcomputer_hand_funcメソッド、my_hand_funcメソッド、judgeメソッドを実行する際にもインスタンスjanken_battle自身を参照する必要があり、各メソッドの第1引数にもselfを設定します。 以上でクラス化は完了です。 各自実行して便利さを実感してみてください。 クラスをモジュールとしてPythonファイルに保存して、別ファイルのプログラムにimportすると更に便利さを実感できるかと思いますが、それはまた次回以降に。 【終わりに】 今回は、前回作ったじゃんけんプログラムを関数に変換した後クラス化するというものでした。いかがっだったでしょうか。 オブジェクト指向に慣れている方は最初からクラスを作ればいいじゃんと言う発想に至ったかもしれませんね。 ただ、オブジェクト指向は勉強したけどいまいちメソッドやクラスについて何が便利か分からなかったという方には、順を追って理解してもらういい機会になったんじゃないでしょうか。 もしもコーディングの部分で間違いなどありましたら編集リクエストからご指摘いただけると幸いです。 また、その他質問などございましたらコメントをお願い致します。 今回作ったJankenクラスを使って「あっち向いてほいプログラム」を作りたいと思います。下記リンクからどうぞ! →※ただいま作成中です!お待ちください! この記事が良かったと感じたらLGTMを宜しくお願いします!それではまた次回!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoogleAnalytics のRetention viewをPythonで模倣する

なにがしたいか GoogleAnalyticsからの出力を得てレポート化する案件があり、無理やりスクリプト化しました。 Imageとして出力して終わりでもよかったのですが、グラフ中にキャプションを入れたり、微妙にいじる必要があったので、一度数値に落として再描画するという中途半端なソリューションになってしまいました。 以下のビューをレポートに入れたい、ただそれだけです。 バージョンはGA3用になります。 そんなやり方でよいのか? だったら、最初からログ分析しろ、ってところですが、このソリューションの良いところは、 Google Analyticsのretention計算の結果と完全に一致する 上記のスナップショットを一時的に保存できる 見た目をいじれる 場合によっては後処理を加えられる(使いにくいが) などです。 コード データの読み出し GAのキーを使って読み出します。ga_utilというライブラリが、つつがなくdata frame化してくれるので苦労は皆無です。 import pandas as pd import numpy as np import ga_util as ga from pylab import rcParams KEY_FILE_LOCATION = 'client_secrets.json' VIEW_IDApp = '00000000' analytics = ga.gar_initialize_analyticsreporting() week = 12 response = ga.gar_get_retention_report(analytics,VIEW_IDAndroidApp,week) dfAndroid = ga.gar_decodeResponse(response) response = ga.gar_get_retention_report(analytics,VIEW_IDiOSApp,week) dfiOS = ga.gar_decodeResponse(response) df = (dfAndroid[0] + dfiOS[0]) / (dfAndroid[1] + dfiOS[1]) df.columns = ["{}th week".format(int(x)) for x in df.columns] df これでこうなります: ここから先はただのお絵かきです。 それっぽく表示 import matplotlib.pyplot as plt import seaborn as sns rcParams['figure.figsize'] = 16,12 # get retention for both OS, Android, iOS retention = (dfAndroid[0].sum() + dfiOS[0].sum()) / (dfAndroid[1].sum() + dfiOS[1].sum()) retentionAndroid = (dfAndroid[0].sum()) / (dfAndroid[1].sum() ) retentioniOS = ( dfiOS[0].sum()) / (dfiOS[1].sum()) # plot time series plt.subplot(2, 1, 1) plt.plot(retention,marker='o', color='r',label="combined") plt.plot(retentionAndroid,marker='^', color='g',label="Android") plt.plot(retentioniOS,marker='x', color='b',label="iOS") # plot x pos = np.arange(len(retention)) plt.xticks(pos, df.columns) ax = plt.gca() # plot oresen: y plt.ylim([0,1]) plt.yticks([0,0.25,0.5,0.75,1]) vals = ax.get_yticks() # plot texts plt.title("retention rate (%) - Android/iOS combined") plt.legend(loc = 'upper right') plt.grid(True, which='major', axis='y') ax.set_yticklabels(['{:,.2%}'.format(x) for x in vals]) txt = "{0:2.1%} of total ********************".format(retention[0]-retention[1]) plt.annotate(txt, xy=(1,retention[1]), xytext=(0.5, 0.2),arrowprops=dict(arrowstyle="->"),fontsize=16,color="red") plt.subplot(2, 1, 2) # heatmap sns.heatmap(df, annot=True, fmt='2.1%', cmap='coolwarm', annot_kws=None ,linewidths=.5,vmin = 0.3,vmax=0.7) ax = plt.gca() ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False) # save image plt.savefig("fig_retention_rate.png", bbox_inches='tight', dpi=200) これで少し色ランプは違いますが、似たようなビューができます。 まとめ こんな事をして同じ見た目にするのにどんな意味があるのか、今一歩利点がわからないかもしれませんが、ここから発展させたレポートが必要な場合に、特に自動化されたキャプションなどを、毎度作るのが面倒な局面では、結構有益ではないでしょうか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AtCoder解説】PythonでABC224のA,B,C,D問題を制する!

ABC224のA,B,C,D問題を、Python3でなるべく丁寧に解説していきます。 ただ解けるだけの方法ではなく、次の3つのポイントを満たす解法を解説することを目指しています。 シンプル:余計なことを考えずに済む 実装が楽:ミスやバグが減ってうれしい 時間がかからない:パフォが上がって、後の問題に残せる時間が増える ご質問・ご指摘はコメントかツイッター、マシュマロまでどうぞ! Twitter: u2dayo マシュマロ: [https://marshmallow-qa.com/u2dayo] ほしいものリスト: https://www.amazon.jp/hz/wishlist/ls/2T9IQ8IK9ID19?ref_=wl_share Discordサーバー(質問や記事の感想・リクエストなどどうぞ!) : https://discord.gg/jZ8pkPRRMT よかったらLGTMや拡散していただけると喜びます! 目次 ABC224 まとめ A問題『Tires』 B問題『Mongeness』 C問題『Triangle?』 D問題『8 Puzzle on Graph』 アプリ AtCoderFacts を開発しています コンテストの統計データを見られるアプリ『AtCoderFacts』を作りました。 現在のところ、次の3つのデータを見ることができます。 レート別問題正解率 パフォーマンス目安 早解きで上昇するパフォーマンス 今後も機能を追加していく予定です。使ってくれると喜びます。 ABC224 まとめ 全提出人数: 6549人 パフォーマンス パフォ AC 点数 時間 順位(Rated内) 200 AB------ 300 35分 4863(4656)位 400 ABC----- 600 117分 3979(3776)位 600 ABC----- 600 58分 3274(3072)位 800 ABC----- 600 35分 2568(2367)位 1000 ABC----- 600 21分 1931(1730)位 1200 ABC----- 600 11分 1405(1206)位 1400 ABCD---- 1000 55分 994(801)位 1600 ABC--F-- 1100 62分 686(501)位 1800 ABCDE--- 1500 69分 451(288)位 2000 ABCDEF-- 2000 101分 295(151)位 2200 ABCDEF-- 2000 82分 196(72)位 2400 ABCDEF-- 2000 64分 135(33)位 色別の正解率 色 人数 A B C D E F G H 灰 2862 98.1 % 69.3 % 35.1 % 1.3 % 0.3 % 0.5 % 0.0 % 0.0 % 茶 1195 99.5 % 97.5 % 80.6 % 4.9 % 1.1 % 1.1 % 0.2 % 0.0 % 緑 907 99.8 % 99.0 % 94.4 % 23.7 % 5.7 % 4.4 % 0.2 % 0.0 % 水 578 99.5 % 99.3 % 96.9 % 53.3 % 31.3 % 21.1 % 0.9 % 0.0 % 青 333 100.0 % 100.0 % 99.4 % 76.3 % 65.5 % 58.3 % 4.2 % 0.3 % 黄 160 93.1 % 92.5 % 93.1 % 84.4 % 76.9 % 77.5 % 32.5 % 3.1 % 橙 27 100.0 % 100.0 % 100.0 % 100.0 % 96.3 % 100.0 % 81.5 % 22.2 % 赤 20 95.0 % 95.0 % 95.0 % 90.0 % 95.0 % 95.0 % 95.0 % 70.0 % ※表示レート、灰に初参加者は含めず A問題『Tires』 問題ページ:A - Tires 灰コーダー正解率:98.1 % 茶コーダー正解率:99.5 % 緑コーダー正解率:99.8 % 入力 $S$ : 文字列(末尾はerかistのどちらかで、必ず $2$ 文字以上) コード S = input() print("Yes" if S[-2:] == "er" else "No") もっと手を抜くなら、erの最後の $1$ 文字はr、ist はtで違いますから、最後の $1$ 文字がrかどうか判定するだけでもいいです。 S = input() print("Yes" if S[-1] == "r" else "No") B問題『Mongeness』 問題ページ:B - Mongeness 灰コーダー正解率:69.3 % 茶コーダー正解率:97.5 % 緑コーダー正解率:99.0 % 入力 $H,W$ : マス目の縦幅、横幅(それぞれ最大 $50$) $A_{i,j}$ :マス $(i,j)$、すなわち上から $i$ 行目、右から $j$ 行目 には整数 $A_{i,j}$ が書かれている 考察 $4$ 重ループですべての $i_1\lt{i_2}$ および $j_1\lt{j_2}$ を満たす $(i_1,i_2,j_1,j_2)$ の組を全探索して、すべての組が$A_{i_1,j_1}+A_{i_2,J_2} \le A_{i2,j1}+A_{i1,j2}$ を満たすか確認すればいいです。 実装 判定部分を関数に分けて、$A_{i_1,j_1}+A_{i_2,J_2} \le A_{i2,j1}+A_{i1,j2}$ を満たさない組が $1$ つでもあれば、すぐに Falseを返すのがいいです。 $A_{i_1,j_1}+A_{i_2,J_2} \le A_{i2,j1}+A_{i1,j2}$ を満たさないとはつまり、$A_{i_1,j_1}+A_{i_2,J_2} \gt A_{i2,j1}+A_{i1,j2}$ ということです。 ループ変数名を工夫すると間違えづらいのでおすすめです。二次元のグリッドを扱う問題では、row(行)column(列)からとって、row、colというループ変数名にするのがいいです。 コード def judge(): for row2 in range(H): for row1 in range(row2): for col2 in range(W): for col1 in range(col2): if A[row1][col1] + A[row2][col2] > A[row2][col1] + A[row1][col2]: return False return True H, W = map(int, input().split()) A = [list(map(int, input().split())) for _ in range(H)] print("Yes" if judge() else "No") C問題『Triangle?』 問題ページ:C - Triangle? 灰コーダー正解率:35.1 % 茶コーダー正解率:80.6 % 緑コーダー正解率:94.4 % 入力 $N$ : 点の数 $X_i, Y_i$ : 点 $i$ の座標(整数) 考察 『$3$ 点を選んで、選ばれた $3$ 点を線分で結んだ図形が正の面積を持つ三角形になる』条件は、『$3$ 点が一直線上に並んでいないこと』です。$3$ 点が一直線上に並んでいる場合、面積のある三角形にはならず、線分になってしまうからです。 選んだ $3$ 点 $A,B,C$ の座標をそれぞれ $A(x_1,y_1)$、$B(x_2,y_2)$、$C(x_3,y_3)$とすると、『$3$ 点が一直線上に並んでいる』条件は、線分 $AB$ と $AC$ の傾きが等しいことです。数式で表すと \frac{y_2-y_1}{x_2-x_1} = \frac{y_3-y_1}{x_3-x_1}\\ 分母を払って\\ (y_2-y_1)\times{(x_3-x_1)}=(y_3-y_1)\times{(x_2-x_1)} です。 すべての点の組を $3$ 重ループで全探索して、この判定式を満たさない($3$ 点が一直線上に並んでおらず、三角形になっている)組み合わせの数を数えればいいです。 コード def solve(): ans = 0 for p1 in range(N): x1, y1 = points[p1] for p2 in range(p1): x2, y2 = points[p2] for p3 in range(p2): x3, y3 = points[p3] if (y2 - y1) * (x3 - x1) != (y3 - y1) * (x2 - x1): ans += 1 return ans N = int(input()) points = [] for _ in range(N): x, y = map(int, input().split()) points.append((x, y)) print(solve()) D問題『8 Puzzle on Graph』 問題ページ:D - 8 Puzzle on Graph 灰コーダー正解率:1.3 % 茶コーダー正解率:4.9 % 緑コーダー正解率:23.7 % 入力 *$M$ : 辺の数 $u_i,v_i$ : 辺 $i$ は頂点 $u_i$ と $v_i$ をつないでいる $p_j$ : 初期状態で、コマ $j$ は頂点 $p_j$ に置いてある * 考察 この問題は幅優先探索(BFS)を使って、移動回数が少ない順にマスの状態を全探索すれば解くことができます。マスの状態は、$9$ 頂点に 空きマス含め $9$ 種類のコマが置いてあるため、全部で $9!=362880$ 通りしかありません。 BFSの計算量は $O(VE)$ ですから、最大で $V=362880,E=36$ の制約では実行時間制限($4$ sec)に間に合い、ACを取ることが可能です。 実装 基本的に、盤面の状態はstr型(数字の"1"~"9"、"9"は空きマスを表す)のリストstateでもっておきます。state[i]は、頂点番号iにコマstate[i]("1"~"9")が存在することを表します。state=["1", "2", "3", "4", "5", "6", "7", "8", "9"]ならば、パズルが完成したことになります。 すでに訪れた盤面の状態を管理するためにset型を使いたいのですが、set型にはリストのようなミュータブル(変更可能)オブジェクトを追加することができません。そこで、必要な場合にリストを''.join(state)で文字列に変換したものを得て、set型への追加や、存在判定をします。 BFSでは、空いている頂点をstate.index("9")で取得し、その頂点から辺でつながっている頂点に置いてあるコマを、空きマスに移動させます。 コード from collections import deque def main(): def make_start(): """ コマjはP[j]に置かれています。"9"が空きマスということにします str型のリストにしたほうが、後々都合が良いです(''.join(state)で文字列に変換する必要があるため) """ start = ["9"] * 9 for piece, v in enumerate(P, 1): start[v] = str(piece) return start def bfs(start): def to_str(state): # setに追加不能なstate: List[str]を、setに追加可能なstrに変換します return ''.join(state) # いちいちこれを打つのが面倒なため、関数にしておきます goal = [str(i + 1) for i in range(9)] # ["1", "2", "3", "4", "5", "6", "7", "8", "9"] です seen = set() # 既に探索した状態を管理します。リストはsetに追加できないので、リストを文字列に変換したものを追加します que = deque() que.append((start, 0)) # 現在のマス目の状態、移動回数 while que: state, d = que.popleft() if state == goal: return d u_empty = state.index("9") # 空マスの頂点番号を取得し、そこに辺で繋がっている頂点に置いてあるコマを移動させます for v in G[u_empty]: n_state = state[:] # 現在の状態をコピーします n_state[u_empty], n_state[v] = n_state[v], n_state[u_empty] # # コマを移動します ns_str = to_str(n_state) # setで存在判定をするために、strに変換します if ns_str not in seen: seen.add(ns_str) que.append((n_state, d + 1)) return -1 """ コマは1~8の8種類、頂点は-1して、0~8の9頂点とします コマが置かれていない「空の頂点」には、コマ9が置かれていることにします マスの状態は、『頂点i』に『コマj』が置いてある、というリストで管理します """ # ここは 入力を受け取って辺から無向グラフを構築しているだけです M = int(input()) G = [[] for _ in range(9)] # 無向グラフです for _ in range(M): u, v = (x - 1 for x in map(int, input().split())) G[u].append(v) G[v].append(u) P = [x - 1 for x in map(int, input().split())] # Pの中身は頂点番号ですから、それぞれ-1して0はじまりにします start = make_start() # 最初の盤面の状態を、文字列のリストにして得ます print(bfs(start)) if __name__ == '__main__': main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webスクレイピングツール5選 | Webクローラーを簡単評価(4)

Webスクレイピングツールは、Webサイトで必要な情報を取得するように開発されています。前回はscrapestormなどのスクレイピングツールを紹介しました。今回は他の五つのWebスクレイピングツールを紹介します。 1) Bright Data (formerly Luminati Networks) Bright Dataの次世代データコレクターは、コレクションのサイズに関係なく、1つのダッシュボードで自動化およびカスタマイズされたデータフローを提供します。eComのトレンドやソーシャルネットワークデータから競争力のあるインテリジェンスや市場調査まで、データセットは、お客様のビジネスニーズに合わせて調整されます。 特徴: ・データ収集プロセスを完全に制御できる ・数分で信頼できるデータフローを取得する ・データ収集はシンプルで動的であり、ターゲットサイト側の変更に対応できる ・コーディングの経験や複雑なデータ収集インフラストラクチャは必要なし ・24時間年中無休のカスタマーサポート リンク:https://brightdata.com/products/data-collector?lang=ja 2) Import.io Import.ioは、Webページ内の半構造化情報を構造化データに変換するプラットフォームであり、Appや他のプラットフォームとの統合など、及びビジネス業務決定の促すに使用できます。 JSON RESTベースおよびストリーミングAPIによるリアルタイムのデータ取得、および多くの通用するプログラミング言語とデータ分析ツールとの統合を提供します。 特徴: ・クリックだけでトレーニングができる ・Webインタラクティブとワークフローを自動化する ・データをスゲジュールしやすいです リンク: http://www.import.io/ 3) Webhose.io Webhose.io APIは、メッセージボード、ブログ、レビュー、ニュースなどの数十万のグローバルなオンラインソースから、統合が容易な高品質のデータとメタデータを提供します。 Webhose.io APIは、クエリベースのAPIまたはfirehoseを介して利用でき、高カバレッジデータで低遅延を提供し、記録時に新しいソースを追加する効率的な動的機能を備えています。 特徴: ・JSONおよびXML形式の構造化されたデータセットを取得できる ・追加料金を支払うことなく、データフィードの膨大なリポジトリにアクセスできる ・詳細な分析を実行できる リンク: https://webhose.io/products/archived-web-data/ 4) Apify Apifyは、WebサイトのAPIを作成することができるWebスクレイピングと自動化プラットフォームです。それは、データ抽出のために最適化される住居とデータセンタープロキシで、統合された代理サービスを含みます。Apify Storeには、Instagram、Facebook、Twitter、Googleマップなどの人気のあるWebサイト向けのさまざまな既製のスクレイピングツールがあり、カスタムソリューションではあらゆる規模のスクレイピングと抽出が可能です。 特徴: ・構造化形式でデータを抽出する ・GoogleSERPプロキシでGoogle検索エンジンの結果ページからデータを抽出する ・5ドルのプラットフォームと30日間のプロキシを無料トライアル リンク:https://apify.com/ 5) Common Crawl Common Crawlは、データを調査および分析し、そこから意味のある洞察を明らかにしたい人のために開発されましたスクレイピングツールです。Common Crawlを使用すると、料金やその他の複雑さを心配することなく、このツールを使用できます。 これは登録された非営利プラットフォームであり、寄付に依存して運営しています。 特徴: ・非コードベースの使用例をサポート。 ・データ分析を教える教育者にリソースを提供する ・Webページデータとテキスト抽出のオーブンデータセットを提供する リンク:https://commoncrawl.org/ 免責事項: 本文はユーザーが提供して、侵害がありましたら、ご連絡してすぐに削除します。ScrapeStormは、ユーザーが本ソフトウェアを使って行うすべての行為に対して、一切責任を負いません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのタプルについてまとめてみた

今回のお題 今回は、Pythonのタプルという概念について取り上げます。 私が経験している他の言語ではあまり馴染みがなかったので筆を取ったというのが本音で基本的には自分用のメモですが、私と同じように他言語から流れてきたPython初学者の方の参考になれば幸いです。 目次 タプルの概要 色々なタプル タプルでできないこと・できること タプルとリストの使い分け タプルの概要 タプルは何かを一言で説明すると、「後から要素を変更できないリスト」になります。 listやintなどと同じく、tupleというクラスも存在します。 タプルには配列とは異なり要素を()で区切るという特徴がありますが、逆に言えば他の操作はリストと同じように行えます。 # タプルの生成 tuple1 = ('a', 'b', 'c') # 要素の取り出し print(tuple1[0]) # for文 for str in tuple1: print(str) 色々なタプル 色々な、というわかりにくいタイトルですが、要素の数などによって若干表記が変わるということの解説です。 # 通常の表記 tuple1 = ("a", "b", "c") # 空のタプル empty_tuple = () # 空でないタプルに関しては、、カッコは省略可能 tuple2 = "d", "e" # 要素が一つの場合にも、末尾に,が必要 tuple3 = "f", 要素が一つの場合に最後の,を省略すると、()の有無に関わらずタプル以外の型(上記であればstr)として解釈されるので注意してください。 タプルではできないこと・できること 冒頭でも説明した通り、タプルでは要素の変更ができません。 また、要素の追加もできません。 tuple = "a", "b", "C" tuple[0] = "d" # これはできない tuple1.append("d") # これもできない 逆に、タプル同士の結合は可能です。 tuple1 = 1, 2, 3 tuple2 = 4, 5 tuple3 = tuple1 + tuple2 print(tuple3) # 結果:(1, 2, 3, 4, 5) また、ややこしいのですが ある変数xにタプルが代入されている場合に、その変数に別の値を代入することは可能。 あるタプルがリストや辞書を配列として持つ場合に、そのリストや辞書に操作を加えることも可能。 タプルが変数xを要素として持つ場合に、xの値を更新してもタプルの要素には影響しない。 list = [1, 2] dict = {"name" "jiro"} x = 10 tuple = x, list, dict tuple = (3, 4, 5) # これは変数の更新なので可能 tuple[0][0] = "a" # これはlistの操作なのでOK tuple[0].append(3) # これもOK tuple[1]["name"] = "saburo" # これはdictの操作なのでOK tuple[1]["age"] = 30 # これもOK x = 20 # これをしてもtupleの要素には影響はない print(tuple[0]) # 結果:10 この辺りは注意点としてあげておきます。 タプルとリストの使い分け 基本的に上記が守られている限りはタプルとリストのどちらを使っても問題はありません。 ただ、公式ドキュメントによると、「同じ型のものを集める場合にはリスト、違う型のもの同士を集める場合にはタプル」という慣習もあるようです。 例えば、生徒全員の名前を集めるのであればリスト、生徒1人の氏名・クラス・住所を管理するのであればタプルといった感じですね。 終わりに 以上、pythonのタプルについてまとめてみました。 もちろん、タプルにするぐらいであれば辞書のほうが便利なケースも多いので極論を言ってしまえばタプルを全く使わなくてもプログラムを書くことは可能かもしれません。 ただ、djangoではデフォルトの記述の中に一部タプルが用いられていることもあり、理解しておいて損はない概念なのかなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列をBoolに変換

bool()は使いづらい 文字列型を変換したい場合、何か値が入っていればTrueが返ってくる configparserと一緒に使おうとして、確かダメだったなーということで今更ながら関数作った。 自作関数で解決 最後のelseは不要 def get_bool(input: str) -> bool: if input.lower() == 'true': return True if float(input) >= 0: return True else: return False
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bookmeterのAPIをつくってみた。 #1 (データの取得)

はじめに 昔は図書館の本すら、自由に読めなかったらしいが、今は自由に読めるし、ブックオフもあるし、更に bookmter もある。恵まれた時代である。僕も bookmeter を愛用させていただいてる。美しいデザイン、知的なユーザー、痒いところに手の届く機能。人類の幸福に多大な貢献をしている bookmeter だが、だからこそ、なぜこれが出来ないのかと思うことがある。例えば、「読みたい本」というブックマークのような機能があって、素晴らしいのだが、検索ができないのである。VIM はヘルプすら検索できるのに。同様に「読んだ本」や「積読」も検索できない。また、タグがないので、バラバラに本がある印象で、どうにもまとまらない。しかし不満ばかり言っても仕方ない。使う側が工夫すれば良いのである。モンスターカスタマーでは、素晴らしい運営者も疲れて辞めてしまうかもしれない。 したいこと 「読みたい本」などの検索 本のタグ付け ... まずは、上の2つをやりたい。他にも色々できたら。 本のデータの取得 まずは本のデータを取る。本のデータの一覧は同じ形式なので同じように取れる。 bookmeter.py import requests from bs4 import BeautifulSoup as bs import time import urllib.parse as up import json HEADERS={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" } class Selector: BOOK="li.group__book" THUMB=".book__thumbnail" TITLE=".detail__title" DATE=".detail__date" AUTHOR=".detail__authors" PAGE=".detail__page" def toData(book): thumb=book.select_one(Selector.THUMB) if thumb: img=thumb.find("img") imgUrl=img.get("src") if img else "" else: imgUrl="" title_e=book.select_one(Selector.TITLE) title=title_e.text if title_e else "" href=title_e.find("a").get("href") if title_e else "" date=book.select_one(Selector.DATE) date=date.text if date else "" author=book.select_one(Selector.AUTHOR) author=author.text if author else "" page=book.select_one(Selector.PAGE) page=page.text if page else "" return { "imgUrl":imgUrl, "title":title, "href":href, "date":date, "author":author, "page":page } def __getBooks(soup): return map(toData,soup.select(Selector.BOOK)) def _getBooks(url,page=1,session=requests): params={ "page":page } res=session.get(url,headers=HEADERS,params=params) soup=bs(res.text,"html.parser") return __getBooks(soup) def getBooks(url,start=1,n=5): session=requests.Session() for page in range(start,start+n): data=list(_getBooks(url,page,session)) if not data: return for dat in data: dat["href"]=up.urljoin(url,dat["href"]) yield dat time.sleep(1) 同じような関数ばかり並んでるのはご愛敬。肝心なのは getBooks だけだが、一応説明。 toData 引数 意味 book 本のデータのスープ toDataの返り値 キー 値 imgUrl 画像のURL title タイトル href 本のURL date 日付(追加日) author 著者名 page 本のページ数 __getBooks 引数 意味 soup スープ 返り値は、データのリスト。 _getBooks 引数 意味 url URL page サイトのページ数 session セッション 返り値は、データのリスト。sessionは内部のもの。 getBooks 引数 意味 url URL start 最初のページ n データを取るページの数 返り値は、データのリスト。 要するに、toData はスープをデータに変換する、__getBooksはスープから本のデータを返す、_getBooks は __getBooks のラッパーのようなもので、getBooks は _getBooks のラッパーのようなもの。 pageというのはページ数で、例えば、ホームの検索窓で、「数学」と入れて調べると、 https://bookmeter.com/search?keyword=%E6%95%B0%E5%AD%A6 に飛ぶ。これに page=3 を足して https://bookmeter.com/search?keyword=%E6%95%B0%E5%AD%A6&page=3 とすると3ページ目に行く。 試す main.py import bookmeter import time import json url="https://bookmeter.com/users/1035737/books/wish" for data in bookmeter.getBooks(url,start=1,n=10**32): print(json.dumps(data,ensure_ascii=False,indent=4)) 実行結果 $ python main.py { "imgUrl": "https://m.media-amazon.com/images/I/41OCTQEQrbL._SL500_.jpg", "title": "安部公房とわたし", "href": "https://bookmeter.com/books/7020962", "date": "", "author": "山口 果林", "page": "" } { "imgUrl": "https://m.media-amazon.com/images/I/510TTCKFQNL._SL500_.jpg", "title": "イスラーム文化−その根柢にあるもの (岩波文…", "href": "https://bookmeter.com/books/579483", "date": "", "author": "井筒 俊彦", "page": "" } { "imgUrl": "https://m.media-amazon.com/images/I/51vdg2TkNfL._SL500_.jpg", "title": "日本史有名人の身体測定", "href": "https://bookmeter.com/books/10276713", "date": "", "author": "篠田 達明", "page": "" } ~~ 以下略 ~~ 簡単な検索 データを取れたので、簡単な検索をしてみる。 main.py import time import json import docopt import re import docopt __doc__=""" Usage: search <s> <url> """ args=docopt.docopt(__doc__) s=args["<s>"] url=args["<url>"] for data in bookmeter.getBooks(url,start=1,n=10**32): if re.search(s,data["title"]): print(json.dumps(data,ensure_ascii=False,indent=4)) 実行結果 $ python main.py 数学 https://bookmeter.com/users/1035737/books/wish { "imgUrl": "https://m.media-amazon.com/images/I/51xDl2z9K+L._SL500_.jpg", "title": "高校数学でわかる流体力学 (ブルーバックス)", "href": "https://bookmeter.com/books/8095831", "date": "", "author": "竹内 淳", "page": "" } { "imgUrl": "https://m.media-amazon.com/images/I/51G4jU5hGhL._SL500_.jpg", "title": "高校数学でわかるフーリエ変換―フーリエ級数か…", "href": "https://bookmeter.com/books/424303", "date": "", "author": "竹内 淳", "page": "" } { "imgUrl": "https://m.media-amazon.com/images/I/416Yus64pNL._SL500_.jpg", "title": "物語 数学の歴史―正しさへの挑戦 (中公新書)", "href": "https://bookmeter.com/books/491408", "date": "", "author": "加藤 文元", "page": "" } ~~ 以下略 ~~ 終わりに 次回はデータベースを作って、もう少し本格的にできるようにしたい。 コードはこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pipインストール手順

環境 windows10 python3.9 エディタ VSCode 事象 pythonをインストールしたが、pipがインストールできていなかった。 [No module named pip]とあるが、python3.4以上は、pipは自動インストールでは?と非常に悩んだ。 調査するとpowerShellは最新のバージョンにアップデートしてみると良いとあったので、 最新にしてみた。 python3.9 の保存場所は以下。 C:\Users\xxx\AppData\Local\Programs\Python\Python39\python.exe コマンドプロンプト >python --version Python 3.9.2 > py -m pip install C:\Users\xxx\AppData\Local\Programs\Python\Python39\python.exe: No module named pip PS C:\Users\xxx\AppData\Local\Programs\Python\Python39> pip list pip : 用語 'pip' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。 名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください 。 発生場所 行:1 文字:1 + pip list + ~~~ + CategoryInfo : ObjectNotFound: (pip:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException 解決方法 PowerShellを立ち上げる。 python3.9と同じ場所に、以下のファイルを保存する。 ダウンロード先:https://bootstrap.pypa.io/get-pip.py ファイル名:get-pip.py python3.9のフォルダ下で、コマンドpython get-pip.pyを実行する。 powerShell PS C:\Users\xxx\AppData\Local\Programs\Python\Python39> python get-pip.py Collecting pip Downloading pip-21.3.1-py3-none-any.whl (1.7 MB) |████████████████████████████████| 1.7 MB 3.3 MB/s Collecting setuptools Downloading setuptools-58.3.0-py3-none-any.whl (946 kB) |████████████████████████████████| 946 kB 6.4 MB/s Collecting wheel Downloading wheel-0.37.0-py2.py3-none-any.whl (35 kB) Installing collected packages: wheel, setuptools, pip Successfully installed pip-21.3.1 setuptools-58.3.0 wheel-0.37.0 確認 powerShell > pip -V pip 21.3.1 from C:\Users\xxx\AppData\Local\Programs\Python\Python39\lib\site-packages\pip (python 3.9) 念のため、コマンドプロンプトでも確認 コマンドプロンプト >pip -V pip 21.3.1 from C:\Users\xxx\AppData\Local\Programs\Python\Python39\lib\site-packages\pip (python 3.9) >pip list Package Version ---------- ------- pip 21.3.1 setuptools 58.3.0 wheel 0.37.0 参考 こちらの記事を参考にさせていただいた。 Qiita: pipのインストール方法 pipが使えるようになったら、pythonライブラリをインストールできるので 以下のサイトを参考にしつつ、インストールしてください。 考察
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PymatgenでABINIT入力ファイルを作成し、自動実行する

はじめに この記事で書くこと Pymatgenには、Quantum EspressoやABINITをはじめとする、各種計算用の入力ファイルを作成する機能が付いているが、使い方についての情報がほとんどなく、公式ドキュメントも、「もともと使える人にはわかる」レベルで、導入がなかなか難しかった。そこで、この文章を書いておくことにした。入力ファイルを作成する部分だけのつもりでいたが、せっかくなので、cifファイルのコレクションをもとに、自動で計算を実行してもらうことにした。実際、世に出回っているcifファイルの質はまちまちで、「とりあえず雑に最適化を一回噛ませたほうが使いやすい」というシチュエーションはよくある。とりあえず拾い集めてきたcifファイルをまとめてフォルダに保存しておいて、サクッと一回計算を回しておく、くらいの使い方ができればいいだろう。ここではABINITを使うが、Quantum Espressoでも、ほとんど同じ使い方ができる。 使用するバージョン pymatgen 2022.0.8 2020.x.xくらいのバージョンだと関数の呼び出し方が違っていてエラーが起きるかもしれないので注意。 ABINIT 9.4.2 操作はMacで行っている。Windowsだとsubprocess内のコマンドを変えなければいけない、、、と思うが、WindowsでABINITをコンパイルできる人にとっては、全く問題にならないだろう。 事前準備 擬ポテンシャルファイルの準備 公式ページから、擬ポテンシャルファイルをダウンロードしておく。"Download the entire dataset table"と書かれたところから、全部ダウンロードできるので、これを持ってきて、"PPs"というフォルダに入れておく。 https://www.abinit.org/psp-tables フォルダ構成の確認 計算を行なっていくに当たって、必要なものは、 ・実行するコード ・計算のもとにするcifファイルの入ったフォルダ ・擬ポテンシャルファイルの入ったフォルダ で、これらをフォルダの同じ階層に入れておく。要するに、↓のような状態。(9_calc_input.ipynbに実行するコードが書いてある。このファイルの名前はなんでもいい。) 実行するコード 肝になる関数 ここでは、Pymatgenの、BasicAbinitInputという関数を使う。基本的な使い方は、以下の通り。 BasicAbinitInput(structure型のデータ, 擬ポテンシャルファイルへのパスのリスト, 計算用キーワードを格納した辞書) 例えば、適用なcifファイルを読み込んできて、最低限のパラメータ(計算でエラーが出ない、の意味)だけを入力したいなら、以下のようなコードを書けばよい。とりあえず書いておいて、後から部分ごとに解説することにする。なお、今回はformat()を多用するので、簡単な使い方を補遺に記した。 #このコードを実行する前に、適当なフォルダを作成し、そのフォルダまでの相対パスをdir_nameという変数に格納しておく。 #また、そのフォルダの中に、PPsというフォルダを作っておく。 from pymatgen.io.cif import CifParser from pymatgen.io.abinit.inputs import BasicAbinitInput import glob import subprocess #cifファイルを読み込む cif_path = glob.glob('./cifs/*.cif') cif_file = cif_path[0] #構造を読み込む parser = CifParser(cif_file) mat0 = parser.get_structures()[0] #擬ポテンシャルファイルをコピーしてくる for el in mat0.composition.elements: el = str(el) cmd_PPs = 'cp ./PPs/{0}.psp8 ./{1}/PPs'.format(el, dir_name) subprocess.run(cmd_PPs, shell=True) #入力ファイルを作成する PPs = ['./{0}/PPs/{1}.psp8'.format(dir_name, el) for el in mat0.composition.elements] param_dict = {'ecut': 5, 'tolvrs' : 1.000000E-5, 'pseudos': '"{}"'.format(', '.join(PPs))} input_file = BasicAbinitInput(mat0, PPs, abi_kwargs=param_dict).to_string() 構造を読み込むところまでは、すでに過去の記事で述べた通り。先のスクリーンショットにあるように、"cifs"というフォルダにcifが入っているので、その中から読み込んでいる。 擬ポテンシャルファイルをコピーしてくるところについては、以下のポイントを押さえればよい。 ・擬ポテンシャルファイルのファイル名は、"(元素記号).psp8"で統一されている。 ・必要な擬ポテンシャルファイルは、PPsというフォルダ(相対パスで"./PPs")に入っている。 ・必要な擬ポテンシャルファイルは、組成式に含まれる元素(mat0.composition.elementsで取得可能)。 これらを前提として、subprocessでファイルをコピーしてくればいい。ただし、mat0.composition.elementsが返すリストの要素はElements型なので、これをstr(Elements)として、文字列に直す必要がある。 続いて、入力ファイルを直接作成する関数、BasicAbinitInput()の使い方は、以下の通りである。 ・引数は最低3つ必要で、前から順にStructure型、擬ポテンシャルへのパスのリスト、キーワードを格納した辞書 ・Structure型は、cifから読み込んだものをそのまま。 ・擬ポテンシャルファイルへのパスは、「実行するパイソンファイルを基準とした相対パス」である。 ・計算用キーワード(ecut、tolvrsなど)は、引数abi_kwardsに辞書型でkeyにキーワード、valueに値を入れる。 ・BasicAbinitInputの返り値はBasicAbinitInput型なので、これをto_string()として、文字列に直す必要がある。 ここで気をつけたいのは、BasicAbinitInput()は擬ポテンシャルファイルへのパスが必須の引数となっているのに、自動ではpseudosに値を割り当ててくれない点(なんで?)。なので、abi_kwardsの辞書には、擬ポテンシャルファイルのパスも含めなければならない。また、ここでは最終的にABINITの入力ファイルに記載されるパスを渡す必要があるので、「ABINITのインプットファイルを保存するディレクトリを基準とした相対パス」を入力しなければならない。 計算を自動で実行するためのコード ディレクトリの作成 ABINITに限らず、計算用のソフトウェアで計算を行うと、大量の出力ファイルが生成する。これが一緒くたになっていると混乱するので、計算を行う構造ごとに、別々のフォルダを作っておくのがいいだろう。ただし、これを手作業でやるのはめんどくさいので、自動で行えるようにする。ディレクトリ名はなんでもいいが、一意性とわかりやすさを両立するためには、「通し番号_組成式」くらいにしておくのがいいだろう。ここでは、Materials Projectのcifを使っているので、cifファイル名からMaterial IDを持ってきて、これを組成式と合わせてディレクトリ名にしている。 #計算用のディレクトリを作る import os dir_name = '{0}_{1}'.format(cif_file[7:-4], mat0.formula.replace(' ', '')) if not os.path.exists(dir_name): os.makedirs(dir_name) os.makedirs(dir_name+'/PPs') ここは、すでに以前の記事で取り扱ったもので、ディレクトリが存在しなければ、作成するようにしている。 ディレクトリ名の行は、globでとってきたcifファイルへのパスが"./PPs/(materials ID).cif"となっているので、7文字目からスタート、お尻から数えて4文字目より前をとってくれば、Material IDが手に入る。また、structure型のデータ(mat0)から、.formulaで組成式を取り出し、.replace('置換前の文字列', '置換後の文字列')とすることで、空白を削除している。 入力ファイルの保存 ここでは、先ほど作成したディレクトリに入力ファイルを保存している。これは基本的なPythonの使い方なので、特に説明は不要と思う。 input_file_path = './{}/basic_calc.abi'.format(dir_name) with open(input_file_path, mode='w') as f: f.write(input_file) 計算の実行とアウトプットファイルの移動 subprocessコマンドは、実行したコマンドが終了するのを待ってくれるため、普通にABINITの実行コマンドを実行すればよい。微妙に気をつけたいのが、ABINITのチュートリアルでは、実行コマンドを"abinit hoge.abi >& log &"としている場合があるが、こうすると、おそらくバックグランドで計算を実行したまま、コードが進んでしまうので、おそらく大変な目に遭う。最後の&は付けないこと。また、各種アウトプットファイルは「計算を実行した時のカレントディレクトリ」に保存されるため、自動では先に作成したディレクトリに保存してもらえない。手動でmvコマンドを使って移動する。 #計算を実行する cmd_calc = 'abinit ./{}/basic_calc.abi >& log'.format(dir_name) subprocess.run(cmd_calc, shell=True) #ファイルをディレクトリに保存する cmd_mv = 'mv basic_calc* ' + dir_name subprocess.run(cmd_mv, shell=True) 計算前の確認コード 上記が終われば、基本的には計算を自動実行することができる。しかし、存在しない擬ポテンシャルファイルを指定してしまうと、そこでコードの実行がエラーで止まってしまうので、先に以下のコードを実行して、計算のもとになるcifのリストを作成し直しておくと無難。特に、ランタノイド系列の擬ポテンシャルファイルは、ABINITの公式ページからは取得できないので、注意が必要。 cif_path = glob.glob('./cifs/*.cif') PP_files = glob.glob('./PPs/*.psp8') PP_els = [element[6:-5] for element in PP_files] calc_path = [] for i, cif_file in enumerate(cif_path): parser = CifParser(cif_file) mat0 = parser.get_structures()[0] mat0_el = [str(el) for el in mat0.species] if all([el in PP_els for el in mat0_el]): calc_path.append(cif_file) else: print(cif_file[7:-4]) print(mat0.formula) やっていることとしては、 ・PP_elsに、PPsに入っている擬ポテンシャルファイルの拡張子以前の部分(つまり元素記号の部分)を格納する。 (相対パス"./PPs/*.psp8"なので、7文字目から最後の5文字目より前をとってくればよい。) ・mat0に含まれる元素をmat0.speciesで取得してリスト(mat0_el)に格納 ・mat0_elに含まれる文字列が、全てPP_elsに含まれるかを確認。 ・全てが含まれていれば、calc_pathに格納。 ・もしも、含まれない文字列があれば、printで出力しておく。 となっている。 コードの全体像 最後に、まとめて計算を実行するためのコードを記す。 for cif_file in calc_path: #構造を読み込む parser = CifParser(cif_file) mat0 = parser.get_structures()[0] #計算用のディレクトリを作る dir_name = '{0}_{1}'.format(cif_file[7:-4], mat0.formula.replace(' ', '')) if not os.path.exists(dir_name): os.makedirs(dir_name) os.makedirs(dir_name+'/PPs') #擬ポテンシャルファイルをコピーしてくる for el in mat0.composition.elements: el = str(el) cmd_PPs = 'cp ./PPs/{0}.psp8 ./{1}/PPs'.format(el, dir_name) subprocess.run(cmd_PPs, shell=True) #入力ファイルを作成する PPs = ['./{0}/PPs/{1}.psp8'.format(dir_name, el) for el in mat0.composition.elements] param_dict = {'ecut': 5, 'tolvrs' : 1.000000E-5, 'pseudos': '"{}"'.format(', '.join(PPs))} input_file = BasicAbinitInput(mat0, PPs, abi_kwargs=param_dict).to_string() #入力ファイルを保存する input_file_path = './{}/basic_calc.abi'.format(dir_name) with open(input_file_path, mode='w') as f: f.write(input_file) #計算を実行する cmd_calc = 'abinit ./{}/basic_calc.abi >& log'.format(dir_name) subprocess.run(cmd_calc, shell=True) #ファイルをディレクトリに保存する cmd_mv = 'mv basic_calc* ' + dir_name subprocess.run(cmd_mv, shell=True) これで、擬ポテンシャルが保存されている構造については、計算を行なってくれる。今回は以上。 補遺 formatの使い方 文字列の操作を行う時に、なんでもかんでも'+'で結合させてもいいけれど、それでは最終的に出来上がる文字列の雰囲気がわかりにくいし、引用符が入り混じってわかりにくくなる(特に、最終的に得たい文字列にも引用符が含まれている場合)ので、エラーの温床になりやすい。ということで、formatを使うことにした。実際のところ、formatは初心者にはわかりにくい面がある。僕の場合は、''や""で囲まれた文字列は、不可触というイメージがあり、その中をいじれるという感覚がよくわからなかったためだ。なんにせよ、formatは、以下の構文で使うことができる。 '文字列xxx{}文字列yyy'.format(置換後の文字列zzz) #文字列xxx置換後の文字列zzz文字列yyy 複数の部分に代入したい場合は、 '文字列xxx{0}文字列yyy{1}'.format(置換後の文字列zzz, 置換後の文字列aaa) #文字列xxx置換後の文字列zzz文字列yyy置換後の文字列aaa となる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

m3u8形式の動画をダウンロード

はじめに m3u8 を落とすソフトは無数にあるが、今回は一番シンプルにつくりたい。複雑だとブラックボックスのようになってつまらない。 流れ まず m3u8 のテキストから 動画の url を取る。 url が TS 形式なら、データを取る。 url が m3u8 なら再帰的にデータを取る。 取ったデータを足す。 コード dl.py import requests import urllib.parse as up import sys def _dlm3u8(url,session=None,log=sys.stdout): if not session: session=requests.Session() resdata=bytes() res=session.get(url) for line in res.text.split("\n"): if not line: #skip empty continue if line[0]=="#": #skip comment continue vurl=up.urljoin(url,line) if log: print("vurl",vurl,file=log) if ".m3u8" in vurl: #dl m3u8 resdata+=_dlm3u8(vurl,sesison=session) elif ".ts" in vurl: #dl ts res=session.get(vurl) resdata+=res.content return resdata def dlm3u8(url,fname): data=_dlm3u8(url) if not data: return False with open(fname,"wb") as f: f.write(data) _dlm3u8 引数 意味 url url 返り値 意味 data ビデオのデータ dlm3u8 引数 意味 url url fname 書き込むファイル名 返り値 意味 bool ダウンロードの可否 試す pornhubの動画で試してみる。 main.py import dl url="https://ev-h.phncdn.com/hls/videos/201902/19/208607941/,200810_2009_1080P_4000K,200810_2009_720P_4000K,200810_2009_480P_2000K,200810_2009_240P_1000K,_208607941.mp4.urlset/master.m3u8?validfrom=1635132054&validto=1635139254&ipa=158.201.248.177&hdl=-1&hash=ocl5naHjd1I2e7sK9NVDL8dPLmw%3D" fname="a.mp4" dl.dlm3u8(url,fname) 実行結果 $ python main.py ~~ 略 ~~ vurl https://ev-h.phncdn.com/hls/videos/201902/19/208607941/,200810_2009_1080P_4000K,200810_2009_720P_4000K,200810_2009_480P_2000K,200810_2009_240P_1000K,_208607941.mp4.urlset/seg-101-f2-v1-a1.ts?validfrom=1635132054&validto=1635139254&ipa=158.201.248.177&hdl=-1&hash=ocl5naHjd1I2e7sK9NVDL8dPLmw%3D vurl https://ev-h.phncdn.com/hls/videos/201902/19/208607941/,200810_2009_1080P_4000K,200810_2009_720P_4000K,200810_2009_480P_2000K,200810_2009_240P_1000K,_208607941.mp4.urlset/seg-102-f2-v1-a1.ts?validfrom=1635132054&validto=1635139254&ipa=158.201.248.177&hdl=-1&hash=ocl5naHjd1I2e7sK9NVDL8dPLmw%3D vurl https://ev-h.phncdn.com/hls/videos/201902/19/208607941/,200810_2009_1080P_4000K,200810_2009_720P_4000K,200810_2009_480P_2000K,200810_2009_240P_1000K,_208607941.mp4.urlset/seg-103-f2-v1-a1.ts?validfrom=1635132054&validto=1635139254&ipa=158.201.248.177&hdl=-1&hash=ocl5naHjd1I2e7sK9NVDL8dPLmw%3D vurl https://ev-h.phncdn.com/hls/videos/201902/19/208607941/,200810_2009_1080P_4000K,200810_2009_720P_4000K,200810_2009_480P_2000K,200810_2009_240P_1000K,_208607941.mp4.urlset/seg-104-f2-v1-a1.ts?validfrom=1635132054&validto=1635139254&ipa=158.201.248.177&hdl=-1&hash=ocl5naHjd1I2e7sK9NVDL8dPLmw%3D ~~ 略 ~~ 時間はかかるが一応落とせた。 終わりに 改良すれば早くなるのかな。こればっかりは、サーバーの問題な気もするが。。。 dlm3u8 はイテレータにすればオシャレかもしれない。(邪道な気もする。(笑)) dl.py def _dlm3u8(url,session=None): if not session: session=requests.Session() res=session.get(url) for line in res.text.split("\n"): if not line: continue if line[0]=="#": continue vurl=up.urljoin(url,line) print("vurl",vurl) if ".m3u8" in vurl: for data in _dlm3u8(vurl,session=session): yield data elif ".ts" in vurl: yield session.get(vurl).content def dlm3u8(url,fname): with open(fname,"wb") as f: for data in _dlm3u8(url): f.write(data) for data in _dlm3u8(vurl,session=session): yield data は yield from _dlm3u8(vurl,session=session) の方が簡潔だが、肌に馴染まないので、使いたくないような。 コード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【勉強】線形回帰モデルを作成しよう

はじめに 今回記事作成者の機械学習の勉強&メモのために記事を作成しています 間違いなどありましたらコメントにてご指摘いただけると幸いです。 今回使用するデータセット Pythonの準備 Pythonの準備をします。 Google ColaboratoryはGoogleアカウントがあれば無料で使えますのでお勧めです。 ちなみに記事作成者は AnacondaのJupyter Notebookを使用しています。 どちらも無料で使えます。 データ分析の流れ ① データ分析の目的を明確にする ② データの読み込み まず分析するためのデータを読み込む ③ データの理解 どのようなデータがあるのか 項目の理解、欠損値の確認、分布の確認(可視化)を行い理解を深める ④ 特徴量の設計 y = aX + b (a,bは変数) 何を特徴量(X)、目的変数(y)にするか 分析できるように特徴量を作成していく 例えば 満足度をA-Eの5段階で作成しているとする これはカテゴリー変数のため、そのまま分析をかけるとエラーになってしまう。 なのでカテゴリー変数を分析できる形にするために0,1表現にする。 ⑤ モデルの構築 目的に合わせてモデルを作成していく (今回は線形回帰のモデルを勉強をしていく) ⑥ モデルの性能評価 モデルが良いか悪いかを判断していく モデルの性能評価ではMSE, RMSEなどが良く利用される ① データ分析の目的を明確にする。 今回の目的は 「住宅情報から価格を予測するモデルを作る」 とします。 ② データの読み込み ライブラリーを読み込みます #グラフ作成に必要なライブラリー %matplotlib inline import matplotlib import matplotlib.pyplot as plt # numpyのライブラリー import numpy as np # pandasのライブラリー import pandas as pd matplotlib.style.use('ggplot') import ~~ as A とすると「~~」を適用させたいときに 「A」に置き換えることができます。 最大表示数の変更方法 Pythonでデータセットを表示する際に、表示列が省略されることがあります。 そこで下記内容を実行すると、最大表示数を変更することができます。 #現在の最大表示列数の出力 pd.get_option("display.max_columns") #最大表示列数の指定(ここでは50列を指定) pd.set_option('display.max_columns',50) データセットを読み込む #(データのあるフォルダー名/ファイル名)でデータを呼び出す # これはCSVファイルを呼び出している dataset = pd.read_csv('data/kc_house_data.csv') # エクセルファイルを呼び出すときは下記コードで行える dataset = pd.read_excel('data/kc_house_data.xlsx') ③ データの理解 データの行数、列数の確認 #データの行数、列数を確認できる dataset.shape データの中身を確認 #データの中身を確認 #数字を入れるとその数字分の行数が出力される #今回の場合、3行出力される dataset.head(3) スライシング(欲しいデータを抽出) 見たい部分のデータを抽出する方法 #2~10行のデータのみを出力する dataset[2:10] # priceが1000以下の物件を出力 dataset[dataset["price"]<=1000] 要約統計量の確認 データ数、平均、標準偏差、最大、75,50,25%,最小の 要約統計量を確認する方法は、下記コードになる。 #要約統計量を出力する dataset.describe() データを可視化する ヒストグラム作成(棒グラフ) #価格のヒストグラムの作成 #dataset → 読み込んだデータセット #["price"] → "price"の部分については、グラフにしたい項目を入れる #hist() → ヒストグラム作成 dataset["price"].hist() #下記方法でも作成可能 dataset.price.hist() 散布図の作成 #散布図の作成 dataset.plot(kind = "scatter", x = "sqft_living",y = "price") #kind = "scatter" → 散布図を指定 # x = ,y= でX軸とy軸の設定をする  グループごとに集計して棒グラフを作成 カテゴリー変数をキーにし、priceの平均を集計し、それを棒グラフにする #Group Byで集計して棒グラフで作成をする。 price_by_condition = dataset.groupby("condition").aggregate({"price":np.mean}).reset_index() price_by_condition.plot.bar(x = "condition") #reset_index() → インデックスを連番に振りなおし  欠損値の確認 #pd.isnull(dataset["列名"]で欠損値があるか確認できる #下記の場合、priceに欠損値があるか確認している、。 pd.isnull(dataset["price"]) #効率的ではないが、各列ごとにチェックをする方法 col_names = dataset.columns for col_name in col_names: # Tureを1 Falseを0に変換し、欠損値の個数の合計を出す missing_num = sum(pd.isnull(dataset[col_name])) print(col_name, ";# of missing record:", missing_num) カラム間の演算方法 # カラム間で演算し、出力する方法 dataset["sqft_footage_of_each_floor"] = dataset["sqft_living"] / dataset["floors"] #必要なデータのみを表示する dataset.iloc[0:5][["id","sqft_living","floors","sqft_footage_of_each_floor"]] カラムに関数を適用する dateから年、日付を取り出すことができる。 20141013T000000 ↓ 2014/10/13/T000000 関数作成 #年を取り出すための関数作成 def date_str2year(x): #最初の4文字を取り出せば、年 になる return int(x[:4]) #月を取り出すための関数作成 def date_str2month(x): #5-6文字目を取り出せば、月 になる return int(x[4:6]) データセットに追加 #年のデータをデータセットに追加する dataset["date_year"] = dataset["date"].apply(date_str2year) #月のデータをデータセットに追加する dataset["date_month"] = dataset["date"].apply(date_str2month) #必要なデータのみを表示する dataset.iloc[0:5][["id","date","date_year","date_month"]] ダミー変数の作成 カテゴリー変数を0,1に変換する #カテゴリ変数を0,1のダミー変数に変換する dataset = pd.get_dummies(data = dataset, columns = ["view"]) 特徴量の作成 #特徴量の作成 dataset["sqft_licing_div_sqft_living15"] = dataset["sqft_living"]/(dataset["sqft_living15"]+0.001) dataset.head() ⑤ モデルの構築 目的に合わせてモデルを作成していく (今回は線形回帰のモデルを勉強をしていく) ライブラリーなどの呼び出し #Scikit Learnを用いた線形回帰モデルの構築 #ライブラリーなど呼び出し from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error ターゲット変数と特徴量を指定 # ターゲット変数と特徴量を指定する #目的変数の設定 target_col = "price" #特徴量として検討しないものを選択する exclude_cols = ["price","id","date"] #特徴量として検討しないものを取り除き、説明変数として設定する feature_cols = [col for col in dataset.columns if col not in exclude_cols] モデルに当てはめるための準備を進めていく 特徴量をX、ターゲット変数をyに格納する #特徴量をX、ターゲット変数をyに格納する y = dataset[target_col] X = dataset[feature_cols] 学習データ、テストデータを分割する 予測モデル作成し、うまく予測できているか確認できるようにするために、学習データとテストデータを分割して行う よくあるのは 学習データ : 70%, テストデータ30%に分割する方法 #学習データを70%、テストデータを30%に分割をする X_train, X_test, y_train,y_test = \ train_test_split(X,y,test_size = 0.3, random_state = 1234) #shapeで行数、列数を確認する。 print(X.shape) print(y.shape) print(X_test.shape) 線形回帰モデルの構築 #線形回帰モデル作成 lm = LinearRegression() #fit関数を使って学習 lm.fit(X_train, y_train) #predict関数(予測関数)を使って、予測 y_pred = lm.predict(X_test) print("予測結果") print(y_pred) #性能評価算出 lm_mse = mean_squared_error(y_test, y_pred) print("y_test") print(y_test) print("LinerRegression RMSE:", np.sqrt(lm_mse)) #RMSEはいいか悪いかは一概にも言えない # ±出てきた数字 分ずれてしまうが、そのずれ幅は許容できるかは確認する必要がある。 #回帰係数算出 coef_pd = pd.DataFrame(lm.coef_,columns = ["coef"], index = feature_cols) coef_pd
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】地図上に風配図をプロットしてみた

はじめに 地図上に風配図をプロットしてみます.結構無理やり感が強いと思うのでもしよりエレガントな方法があれば教えてください. 方法 地図の描画にはCartopyを使います.Cartopyで一旦描きたいところの海岸線を描画して,その上に風配図をプロットします.Matplotlibにmpl_toolkits.axes_grid1.inset_locator.inset_axesという拡張機能があるのでこれを風配図のプロットに使いました. 風配図について 風配図については過去に記事を書いているのでこちらを参考にしてください. 使用データ 四国4県の2020年8月1ヶ月間における1時間ごと風向(16方位)データを使いました.あらかじめ気象庁よりDLしたデータを編集して使ってます. 気象庁 データはこんな感じ コード全体 import matplotlib.pyplot as plt import matplotlib as mpl from mpl_toolkits.axes_grid1.inset_locator import inset_axes from cartopy.crs import PlateCarree from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter import cartopy.crs as ccrs import numpy as np import pandas as pd # データの読み込み fname = './JMA/WindDir_Shikoku.txt' df = pd.read_csv(fname, delimiter=' ') # データの配列化 data = df.values # 各気象観測所の緯度経度 lon1 = 133. + 32.9/60 lat1 = 33. + 34./60 # Kochi lon2 = 132. + 46.6/60 lat2 = 33. + 50.6/60 # Matsuyama lon3 = 134. + 3.2/60 lat3 = 34. + 19.1/60 # Takamatsu lon4 = 134. + 34.4/60 lat4 = 34. + 4./60 # Tokushima lon_list = [lon1, lon2, lon3, lon4] lat_list = [lat1, lat2, lat3, lat4] # 描画範囲 ll_lon = 132. ll_lat = 32.5 ur_lon = 135 ur_lat = 35. # 基図の設定 fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection=PlateCarree(central_longitude=133.)) ax.set_extent([ll_lon, ur_lon, ll_lat, ur_lat], crs=ccrs.PlateCarree()) ax.xaxis.set_major_formatter(LongitudeFormatter()) ax.yaxis.set_major_formatter(LatitudeFormatter()) # 海岸線をプロット ax.coastlines() # 風配図をプロットする関数を作成 def plot_windrose(plon, plat, wdir): # 入力する緯度経度を bbox_to_anchor の入力値に変換する(ここが無理やりっぽい) x = ( plon-ll_lon ) * ( 1/( ur_lon-ll_lon ) ) y = ( plat-ll_lat ) * ( 1/( ur_lat-ll_lat ) ) # 風配図の作成 Type=['N','NNE','NE','ENE', 'E','ESE','SE','SSE', 'S','SSW','SW','WSW', 'W','WNW','NW','NNW'] wdir_li = [] for j in Type: index=np.where(wdir==j) num=index[0].size per = num / len(wdir) wdir_li.append(per) # inset_axes で基図上にプロットする axin = inset_axes(ax, width=1.5, height=1.5, loc=10, # アンカーを中心に設定 bbox_to_anchor=(x, y), # 風配図の中心座標 axes_class=mpl.projections.get_projection_class('polar'), # 極座標系に設定 bbox_transform=ax.transAxes, # ax の座標に合わせる borderpad=0.0) # 風向頻度のプロット # 過去の記事参照 angles = np.linspace(0.0, 2 * np.pi, len(Type) + 1 , endpoint=True) values = np.concatenate((wdir_li, [wdir_li[0]])) axin.set_theta_direction(-1) axin.set_theta_zero_location("N") axin.plot(angles, values, color='b', markerfacecolor = 'none', linewidth=0.8) axin.set_zorder(ax.get_zorder()+1) axin.fill(angles, values, color='b', alpha=0.25) axin.set_thetagrids(angles[:-1] * 180 / np.pi, Type) axin.set_rlim(0 , 0.30) axin.patch.set_alpha(0.1) # 風向ラベルを消す(文字が被って汚くなるため) axin.tick_params(labelbottom=False) # 外枠を消す axin.spines['polar'].set_visible(False) # 4地点で実行 for p in range(0, len(lon_list)): plon = lon_list[p] plat = lat_list[p] wdir = data[:, p] plot_windrose(plon, plat, wdir) fig.savefig('./output/windrose_map.png') plt.show() 完成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

E:The repository 'http://security.debian.org./debian-security bullseye/updates Release' does not have a Release file.対処

# 1./etc/apt/sources.list編集 # 2.http://security.ubuntu.com/ubuntu/ → http://deb.debian.org/debian-security/に変更 # 3.docker-compose build xxx エラー箇所を指定してbuild実行 #deb http://security.ubuntu.com/ubuntu/ bullseye-security main #deb-src http://security.ubuntu.com/ubuntu/ bullseye-security main deb http://deb.debian.org/debian-security/ bullseye-security main deb-src http://deb.debian.org/debian-security/ bullseye-security main
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flagでの制御

はじめに flag作って処理して! けっこー前に突然言われて新人エンジニアにはなんのこっちゃ分らんかった。とりあえず分かりましたー言うて調べてみるものの分からず聞く羽目に…。 今回はFlag処理とは何ぞやってところから。 実際の実装例を簡単にpythonで説明する。 そもそもフラグって何? フラグは状態判定に使う変数でBooleanで使うことが多いですが、 1.2つの値のどちらかを設定する 2.2つの状態のどちらかを表現する この二つの機能を持っていればFlagとしての役割は果たせます。 flag = True みたいな感じで使われることが多いですが、もの自体は只の変数なので名前は何でもいいです。 hogehoge = True でも piyopiyo = True でも問題ないです。 肝心なのは状態判定の為に必要なものを用意しているってこと。 例 簡単な例いきましょう flag = True num = 0 while flag: print ("num は " + str(num)) num += 1 if num > 2: flag = False print("処理終わり") 今回の例では数値が2以上になったときにFlagの状態を切り替えて、whileループを止めています。 列挙型enum FlagやIntFlagは列挙型Enumの一種で、pythonでのフラグ処理ならこれ使うことも多い 例  from enum import Flag class PlayerStatus(Flag): SLEEP = "sleep" #睡眠 POISON = "poison" #毒 PARALYSIS = "paralysis" #麻痺 これの良いところは一つの変数に複数のフラグを設定できることです。 例えば、睡眠かつ毒状態のステータスは status = PlayerStatus.SLEEP | PlayerStatus.POISON print(status) #結果 #PlayerStatus.POISON|SLEEP 例 status = PlayerStatus.SLEEP | PlayerStatus.POISON if status & PlayerStatus.SLEEP: print("SLEEP!") if status & PlayerStatus.POISON: print("POISON!") if status & PlayerStatus.PARALYSIS: print("PARALYSIS!") #結果 #SLEEP! #POISON! ただこのやり方だと列挙型のenumを使うメリットはそこまでない。 じゃあどうする? from enum import Flag ,auto class PlayerStatus(Flag): SLEEP = auto() POISON = auto() PARALYSIS = auto() for s in PlayerStatus: print(s.name,s.value) #結果 #SLEEP 1 #POISON 2 #PARALYSIS 4 書き方こうすると処理が高速化出来る。 本来はビット単位で管理するのでメモリが節約でき、プログラムの高速化も狙える。 autoという関数を使うと自動で二のべき乗を割り振ってくれます。 フラグがたくさんあって整理が難しい場合や、少しでも処理を高速化したい場合には便利なので列挙型enumとauto関数 使うと良いよ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBユーザーのパスワードに@があって怒られた

事象の説明 sqlalchemy を使って DB に接続する際に次のような方法で engine を生成します。 username = 'pyuser' password = 'Pyp@ssw0rd' host = 'localhost' port = 3306 database = 'pytest' charset_type = 'utf8' connection_url=f"mysql://{username}:{password}@{host}:{port}/{database}?charset={charset_type}" engine = create_engine(connection_url) ここではデータベースとして MySQL を使っています。この engine を用いて DB に接続してクエリを実行すると以下のように怒られます。 session = sessionmaker(bind=engine)() session.execute("SELECT 1;") sqlalchemy.exc.OperationalError: (MySQLdb._exceptions.OperationalError) (2005, "Unknown MySQL server host 'ssw0rd@localhost' (11)") DBに接続するためのURLをよく見ると、パスワードとホスト名の区切り文字が @ となっています。上の例ではパスワードに @ が含まれており、最初に現れたパスワード内の @ が区切り文字と判断されてしまったことがエラーの原因でした。 対応方法 sqlalchemy には URL class が用意されているので、こちらを使うとよいです。 from sqlalchemy.engine.url import URL connection_url = URL.create( drivername="mysql", username=username, password=password, host=host, port=port, database=database, query = {"charset": charset_type}, ) print(connection_url) 結果 mysql://pyuser:Pyp%40ssw0rd@localhost:3306/pytest?charset=utf8 上記結果からもわかるように、パスワードに含まれている @ が単に URL encode されているだけですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django Rest Framework ListSerializerの書き方

フロントエンドの開発時に、より本格的なスタブを作るために、DRFをよく使っていました。 とても手軽に構築できて、いつも助かっています。 ただ、よりコアの使い方が必要なとき、PythonやDjangoに不慣れの人にはなかなか骨が折れます。 下記のような連想配列を一回のPOSTで複数のデータ更新と新規追加を同時に実現したいニーズがあって 公式サイトではこのように解説されています。 つまり、「ListSerializer」を使えばよいとのことです。 公式サイトに紹介された構文でserializers.pyに書いてみると、それっぽい動きは確認できますが、 POST送信後、データの更新も確認できました。 しかし、正しいレスポンスが返ってこなく、さらに新規追加した分のIDがレスポンスの中に含まれず、判別できない残念な一面があります。 どうやら、views.pyもある程度カスタマイズする必要があるようです。 いろいろ調べているうちにようやく正しい挙動にたどり着きましたので、ご紹介いたします。 同じ使い方するときにお役に立てると幸いです。 Serializers serializers.py class TaskListSerializer(serializers.ListSerializer): def create(self, validated_data): new_tasks = [Task(**p) for p in validated_data if not p.get('id')] updating_data = {p.get('id'): p for p in validated_data if p.get('id')} # 既存データの定義 old_tasks = Task.objects.filter(id__in=updating_data.keys()) with transaction.atomic(): # 新しいデータの処理 all_tasks = Task.objects.bulk_create(new_tasks) # 既存データの更新 for task in old_tasks: data = updating_data.get(task.id, {}) # 一旦削除の実行 data.pop('id') updated_task = Task(id=task.id, **data) updated_task.save() all_tasks.append(updated_task) return all_tasks class TaskSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: list_serializer_class = TaskListSerializer model = Task fields = '__all__' Views views.py class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.none() serializer_class = TaskSerializer def get_queryset(self): queryset = Task.objects.all() return queryset.filter(user=self.request.user.id) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list)) serializer.is_valid(raise_exception=True) self.perform_create(serializer) results = Task.objects.all() output_serializer = TaskSerializer(results, many=True) data = output_serializer.data[:] return Response(data) 一応 Models も models.py class Task(models.Model): PERSON_NONE = "none" PERSON_DAD = "dad" PERSON_MOM = "mom" PERSON_SET = ( (PERSON_NONE, "None"), (PERSON_DAD, "Dad"), (PERSON_MOM, "Mom"), ) user = models.ForeignKey(get_user_model(), related_name='tasks', on_delete=models.CASCADE) master = models.ForeignKey(Master, related_name='tasks', on_delete=models.CASCADE) date = models.DateField() person = models.CharField(choices=PERSON_SET, default=PERSON_NONE, max_length=8) def __repr__(self): return "{}".format(self.pk) __str__ = __repr__
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】REST frameworkでプロジェクトにデータPOSTしてくるクライアントのIPアドレスをDBに格納する

はじめに DjangoのREST frameworkを使うようなアプリケーションを持つプロジェクトではよく「uplink」でデータ収集をすることがあります.私はその逆方向の通信を行う「downlink」をする機会があったため,「そもそもuplink時に誰がデータをPOSTしてきてるの?」っていうことを動的に知る必要がありました. そこで,データPOSTしてくるゲートウェイ等のクライアントのIPアドレスをDBに格納しておいてdownlinkのときに,そのIPアドレスを使おうってことになりました. 結論 Djangoプロジェクトへのアクセスリクエストに含まれるHTTPヘッダの'REMOTE_ADDR'からクライアントのIPアドレスを取得してあげれば良いみたいです. 参考 DjangoでクライアントのIPアドレスを取得する 実装手順 前提 Python,Django及びREST frameworkに関して経験のあることを前提として記事を書いております.POSTAPIの作成に関しては以下の記事がわかりやすいと思います. Django Rest Framework で RESTful な APIを作成する 環境 CentOS Linux release 7.9.2009 (Core) Python 3.6.15 Django 3.2.0 djangorestframework 3.12.3 1.REST frameworkをインストールする $ pip install djangorestframework 2.設定ファイルにREST frameworkを追加 ※設定ディレクトリ名はconfigであると仮定してます ./config/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', # データPOSTAPI     … ] 3.uplink用のアプリケーションを作成する $ python manage.py startapp uplink 4.設定ファイルにuplinkアプリケーションを追加 ※設定ディレクトリ名はconfigであると仮定してます ./config/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # データPOSTAPI + 'uplink.apps.UplinkConfig', # アップリンク(データ収集)機能         … ] 5.uplinkアプリケーションのURLを定義する ./config/urls.py urlpatterns = [ … + path('uplink/', include('uplink.urls')), … ] 6.IPアドレスフィールドをカラムに持つモデルを作成する ./uplink/models.py from django.db import models from django.utils import timezone class PostData(models.Model): """ POSTされるデータのモデルクラス create_at: データPOST時刻 client_ipaddr: データをPOSTしたクライアントのIPアドレス """ class Meta: db_table="post_data" verbose_name='PostData' verbose_name_plural='PostData' create_at = models.DateTimeField( verbose_name='create_at', blank=False, null=False, editable=False, #編集不可 default=timezone.now, ) client_ipaddr = models.GenericIPAddressField( verbose_name='Client IP address', blank=False, null=False, default='0.0.0.0', editable=False, #編集不可 ) 7.adminにモデルを追加する ./uplink/admin.py from django.contrib import admin from .models import PostData @admin.register(PostData) class PostDataAdmin(admin.ModelAdmin): list_display = ( 'create_at', 'client_ipaddr', ) 8.URLを定義する ./uplink/urls.py from django.urls import path from . import views app_name = 'uplink' urlpatterns = [ path('data_post/', views.CreateData.as_view(), name='data_post'), ] 9.シリアライザを定義する ※このシリアライザを定義する際に,client_ipaddr = serializers.IPAddressField()を定義しないとうまくいきませんでした. ./uplink/serializer.py """ データをPOSTするときのシリアライザ """ from rest_framework import serializers from .models import PostData class PostDataSerializer(serializers.ModelSerializer): # IPアドレス型のデータとして用意しておく client_ipaddr = serializers.IPAddressField() class Meta: model = PostData fields = ( '__all__' ) read_only_fields = ('create_at', 'client_ipaddr', ) 10.Viewを定義する ./uplink/views.py from rest_framework.views import APIView from rest_framework import status from rest_framework.response import Response from rest_framework.parsers import MultiPartParser, FormParser from django.utils import timezone from .serializer import PostDataSerializer class CreateData(APIView): """ ゲートウェイからPOSTされたデータをデータベースに格納する """ parser_classes = (MultiPartParser, FormParser, ) def post(self, request, format=None): # POSTデータのペイロードをシリアライズするために整形 data = {} data['create_at'] = timezone.localtime(timezone.now()) client_ipaddr = request.META.get('REMOTE_ADDR') data['client_ipaddr'] = client_ipaddr # シリアライザにかける serializer = PostDataSerializer(data=data) # シラアライズの成否で処理を変更 if serializer.is_valid(): serializer.save() print('●Valied data post.') print(serializer.validated_data) return Response(serializer.validated_data, status=status.HTTP_201_CREATED) # エラーを吐いたら詳細を見せてもらう print('●Data post is invalied.') print(serializer.errors) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 11.データベースマイグレーション $ python manage.py makemigrations $ python manage.py migrate まとめ 色々と書きましたが,REST frameworkを問題なく使える方にとっては「結論」の内容とシリアライザを定義するときの注意点だけ覚えていってもらえればいいと思います.Djagnoを使ったdownlinkも限定的なものになってしまいますが,随時書ければなと思います. ※疑問点や修正点などはご連絡頂けると嬉しいです 参考 アップリンク(uplink)とは - IT用語辞典 e-Words DjangoでクライアントのIPアドレスを取得する IPAddressField in serializers – Django REST Framework Django Rest Framework で RESTful な APIを作成する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BookoffオンラインのAPIをつくってみた。 #3 (改良)

はじめに 前回はこちら 本当はデータベースを作りたかったが、一度に最大5000件しか取れないという壁にぶつかり、どうも深刻な感じなのでとりあえず諦めた。 前回と前々回で、いくつか関数を作ったが、もう少し改良。 データの取得 一回目の searchN を改良。 util.py from . import api import re import time def search(query,title="",author="",priceFrom=0,priceUntil=10**32,onlyStocked=True,n=5,type_=api.SearchType.BOOK,timeout=30): i=0 startTime=time.time() for data in api.searchN(query,n=10**32,type_=type_): if i >= n: break if time.time() - startTime > timeout: break if (not title or re.search(title,data["title"])) and \ (not author or re.search(author,data["author"])) and \ priceFrom <= data["price"] < priceUntil and \ (not onlyStocked or data["stocked"]): yield data i+=1 引数 意味 query 検索する文字 title タイトル (部分一致) author 著者名 (部分一致) priceFrom {priceFrom} 円以上 priceUntil {priceUntil} 円未満 onlyStocked 在庫の在るもののみを返す n データの個数 (最大) type データのタイプ timeout タイムアウト query と title があるのは野暮なようだが、例えば著者名で検索したいとき、 query = title なら無理やし、 query=title+" "+author ではauthorに正規表現を使いたいときにヒットしない。まあ改良してもこんなもの。(笑) 一応タイムアウトを付けた。 試す main.py from bookoff import api import getpass import requests from bookoff import util import sys import json author=sys.argv[1] for data in util.search(author,author=author,priceUntil=220): print(json.dumps(data,indent=4,ensure_ascii=False)) 実行結果 $ python main.py 安部公房 { "title": "砂の女(新潮文庫)", "url": "https://www.bookoffonline.co.jp/old/0015580570", "author": "安部公房", "price": 200, "stocked": true } { "title": "壁(新潮文庫)", "url": "https://www.bookoffonline.co.jp/old/0015580566", "author": "安部公房", "price": 200, "stocked": true } { "title": "笑う月(新潮文庫)", "url": "https://www.bookoffonline.co.jp/old/0015580572", "author": "安部公房", "price": 200, "stocked": true } { "title": "箱男(新潮文庫)", "url": "https://www.bookoffonline.co.jp/old/0015580571", "author": "安部公房", "price": 200, "stocked": true } { "title": "こころの声を聴く 河合隼雄対話集(新潮文庫)", "url": "https://www.bookoffonline.co.jp/old/0012419276", "author": "河合隼雄,安部公房,山田太一,谷川俊太郎,白洲正子", "price": 200, "stocked": true } なかなか有能である。前回のものと組み合わせて、好きな著者の安い本を選んでカートに入れてみる。 好きな著者の安い本をカートに入れる main.py import docopt import getpass PRICE_UNTIL=220 __doc__=""" Usage: addcheaps <author> [(-n <number>)] [(-m <mail>)] """ def main(argv=sys.argv[1:]): args=docopt.docopt(__doc__) author=args["<author>"] number=int(args["<number>"]) if args["-n"] else 5 mail=args["<mail>"] or input("mail : ") pw=getpass.getpass() author=sys.argv[1] client=api.Client(mail,pw) if not client.logined: print("Failed to login") return for data in util.search(author,author=author,priceUntil=PRICE_UNTIL): print(data["title"],data["price"]) client.addCart(util.getidInUrl(data["url"])) if __name__ == "__main__": main() 実行結果 $ python main.py 梅棹忠夫 -m tetsuya1729@icloud.com Password: 知的生産の技術(岩波新書) 200 日本文明77の鍵(文春新書) 200 二十一世紀の人類像 民族問題をかんがえる(講談社学術文庫) 200 タイトルから ID を取るのは遅いので、util.Client ではなく api.Client を使った。(改悪か?) ここまでくれば、中々実用的である。 終わりに 本を買うために、プログラミングをするなんて知的すぎんか 。。。只の陰キャラの末路と言う気もせんではないが。やはりスポーツをやらねば。(笑) コード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】設定ディレクトリをわかりやすく・みつけやすく

はじめに Djangoの開発・実装の際に,どうしてもアプリケーションが多くなってしまうことがあります.こんな状態のプロジェクト構成だと設定ファイルを探すときも,ちょっとだけめんどくさいですよね.ディレクトリ名さえ覚えていれば,アルファベット順なわけですし,探せばそりゃ見つかるんです.それでも「探す作業」自体が嫌いな私はそれすらめんどくさいんですよね… そこで,この設定ディレクトリが「絶対一番上にある」っていう状態にしておくことで,探す手間をなくします.かなり簡単な話なので,みなさんも是非使ってみてください. 結論 設定ディレクトリの名前を「_config」にすることで,このディレクトリが一番上に表示されます. プロジェクト構成例 . ├── _config # プロジェクトの一番上に設定ディレクトリ ├── app1 ├── app2 └── app3 実装手順 この記事でお伝えしたことを実際に自分のDjangoプロジェクトに取り入れようと考えてくれる方に向けて,実装手順も載せておきます. 前提 Python3,Djangoはインストール済みであると想定します.バージョンは何でもいいと思いますが,私は以下の環境で実装しました. Python: 3.8.5 Django: 3.2.8 初めからDjangoのプロジェクトを開発する場合 $ mkdir <projectName> $ cd <projectName> $ django-admin.py startproject _config . 開発中のDjangoプロジェクトに反映する場合 すでに開発しているDjangoプロジェクトがある場合は,元の設定ディレクトリ名(「settingDirName」だとする)を「_config」にしてやる必要があります. プロジェクト構成 . └── <settingDirName> → _config ./manage.py - os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<settingDirName>.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', '_config.settings') ./_config/settings.py - ROOT_URLCONF = '<settingDirName>.urls' + ROOT_URLCONF = '_config.urls' - WSGI_APPLICATION = '<settingDirName>.wsgi.application' + WSGI_APPLICATION = '_config.wsgi.application' ここからの設定はWSGI・ASGIを活用するなら設定の変更が必要ですが,そうじゃない場合は変更しなくても大丈夫なはずです. ./config/asgi.py - os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<settingDirName>.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', '_config.settings') ./config/wsgi.py - os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<settingDirName>.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', '_config.settings') まとめ 今回の記事の発想は研究室の後輩から教わったものなのですが,こういう少しの手間でできる感じがとても良いなと思いました.冒頭でも言ったように,Djangoプロジェクトの開発をしているとゴチャゴチャしてしまうことがあるので,他の方の記事にもあるようなベストプラクティスを活用したいですね. ※疑問点や修正点などはご連絡頂けると嬉しいです 参考 Django ベスト・プラクティス Qiitaの投稿で設定値の変更などの差分を分かりやすく書く方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scikit-learnのサンプルデータセットと主要OSSを活用したデータ分析のチュートリアル

著者: 株式会社 日立ソリューションズ 柳村 明宏 監修: 株式会社 日立製作所 はじめに 近年、機械学習をはじめとするAI技術を活用したデータ分析が注目を集めています。 本投稿では、機械学習に馴染みのない方やデータ分析に馴染みのない方が、データ分析に触れていただくことを目的に、手軽に利用可能なOSSを利用したデータ分析の一連の手順とサンプルコードを紹介いたします。 データ分析の流れ データ分析環境と、データ分析の流れを以下に示します。 吹き出しはそれぞれのフェーズで利用するOSSです。 「データの理解」では、データの全体像を把握するためにJupyter Notebook上でデータセットを可視化します。 「モデルの作成と評価」では、データセットを利用して機械学習モデルの学習・評価を行います。今回は、再利用を考慮してPythonコードで記載していますが、Jupyter Notebookでも作成可能です。 「モデルの利用」では、作成した機械学習モデルをWebサーバにデプロイして、HTTP API経由で利用します。 利用するデータセット データセットには、scikit-learnに同梱されている「Boston housing prices dataset」という、米国ボストン市の住宅価格に関するデータセットを利用します。これは、住宅周辺の環境情報(説明変数)から、住宅価格(目的変数)を予測する、回帰分析向けのサンプルデータです。次の表の通り、ボストンの住宅価格のデータセットは、13項目の説明変数と1項目の目的変数をもつ、506個のデータで構成されています。 なお、本データセットはscikit-learnのバージョン1.0で非推奨となりました。バージョン1.2で削除される予定です。バージョン1.2以降で、今回のサンプルを動作させる場合は、データセットの読込み部分を変更させる必要があると想定されます。詳細は下記のサイトを参考にしてください。 Boston housing prices datasetに関して # カラム 説明 変数分類 1 CRIM 町別の「犯罪率」 説明変数 2 ZN 25,000平方フィートを超える区画に分類される住宅地の割合=「広い家の割合」 説明変数 3 INDUS 町別の「非小売業の割合」 説明変数 4 CHAS チャールズ川のダミー変数(区画が川に接している場合は1、そうでない場合は0)=「川の隣か」 説明変数 5 NOX 「NOx濃度(0.1ppm単位)」=一酸化窒素濃度(parts per 10 million単位)。この項目を目的変数とする場合もある 説明変数 6 RM 1戸当たりの「平均部屋数」 説明変数 7 AGE 1940年より前に建てられた持ち家の割合=「古い家の割合」 説明変数 8 DIS 5つあるボストン雇用センターまでの加重距離=「主要施設への距離」 説明変数 9 RAD 「主要高速道路へのアクセス性」の指数 説明変数 10 TAX 10,000ドル当たりの「固定資産税率」 説明変数 11 PTRATIO 町別の「生徒と先生の比率」 説明変数 12 B 「1000(Bk - 0.63)」の二乗値。Bk=「町ごとの黒人の割合」を指す 説明変数 13 LSTAT 「低所得者人口の割合」 説明変数 14 MEDV 「住宅価格」(1,000ドル単位)の中央値。通常はこの数値が目的変数として使われる 目的変数 データ分析のチュートリアル 利用するデータセットで紹介したデータセットを利用して、データ分析を行っていきます。実現する内容を次の表に示します。 # 項目 内容 利用するライブラリ 1 データの理解 データの全体像を把握するために、Jupyter Notebook上でデータセットを可視化します。 scikit-learn numpy pandas seaborn 2 モデルの作成と評価 データセットを利用して、機械学習モデルの学習・評価を行います。 scikit-learn optuna pandas 3 モデルの利用 #2 で作成した機械学習モデルをWebサーバにデプロイして、HTTP API経由で利用します。 flask データの理解 今回は、データの全体像を把握するために、Jupyter Notebook上でデータセットを可視化します。データセットを読込み、データセットの内容の参照と、説明変数と目的変数のデータの相関を可視化します。データの理解に使用したノートブック の内容を以下に示します。 ライブラリをimportし、データセットを読み込みます。 Visualization.ipynb import pandas as pd import numpy as np from pandas import DataFrame from sklearn.datasets import load_boston # ボストン住宅価格データセットの読み込み boston = load_boston() # 説明変数 X_array = boston.data # 目的変数 y_array = boston.target データセットを表形式で参照します。今回は、先頭と末尾の行を表示します。 Visualization.ipynb df = DataFrame(X_array, columns = boston.feature_names).assign(MEDV=np.array(y_array)) # ヘッダ出力 df.head 出力結果は次の通りです。 利用するデータセットで紹介したカラムで、506個のデータが存在することがわかりました。 次に、説明変数と目的変数の相関関係を可視化してみます。 可視化用のライブラリとしてseabornをimportします。 Visualization.ipynb import seaborn as sns CRIM(犯罪率)と、MEDV(住宅価格)の相関を見ます。 Visualization.ipynb sns.regplot(x=df["CRIM"], y=df["MEDV"], data = df) 出力結果は次の通りです。 犯罪率が低い地域の住宅は、住宅価格が高いことがわかります。 最後に、RM(1戸当たりの「平均部屋数」)と、MEDV(住宅価格)の相関を見ます。 Visualization.ipynb sns.regplot(x=df["RM"], y=df["MEDV"], data = df) 部屋数が多い住宅は、住宅価格が高いことがわかります。 モデルの作成と評価 本項では、ボストンの住宅価格データセットを用いて、住宅周辺の環境情報から住宅価格を予測するモデルを作成します。また、作成したモデルの予測精度を評価します。 初めに学習・検証・評価データに分割します。次に学習・検証データを利用して、Optunaによるハイパーパラメータチューニングを行いつつ、モデルを学習します。最後に評価データを利用して、モデルの予測精度(評価指標)を確認します。 検証用と評価用のデータを分ける理由は、もし評価データを学習時のハイパーパラメータチューニングに利用してしまうと、評価データに最適化されたモデルが選択されてしまうためです。評価ではモデルの本番導入後(=サービング時)に全く知らない新しいデータが来た際の性能を知りたいため、一度も学習に使ったことのないデータで評価する必要があります。 本項の実施内容を以下の表に示します。 # 内容 ポイント 1 データセットを読込み学習、評価、検証用にデータを分割します。 分割数は任意ですが、今回は以下のように分割しました。 学習用:303個(60%) 検証用:102個(20%) 評価用:101個(20%) 2 Optunaでモデルのハイパーパラメータチューニングを行い、モデルを学習します。 利用したモデル: Ridge回帰 探索するモデルのハイパーパラメータ:正則化係数(今回の探索範囲:0.01~100.00) 最適化する評価指標: 平均絶対誤差(MAE) Ridge回帰に関しては次のサイトを参考にしてください。 Ridge回帰とは scikit-learnのRidge回帰の関数仕様 平均絶対誤差 (MAE, Mean Absolute Error) は、実際の値と予測値の絶対値を平均したものです。MAE が小さいほど誤差が少なく、予測モデルが正確に予測できていることを示しています。回帰分析に対する評価指標に関しては、次のサイトを参考にしてください。 MAEとは scikit-learnのMAEの関数仕様 モデルの作成と評価に使用したPythonコードの内容を以下に示します。 train.py from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.linear_model import Ridge import optuna import pickle import pandas as pd import sklearn.metrics boston = load_boston() # xが説明変数 # yが目的変数(MEDV) x = boston.data y = boston.target # 訓練データ:6、検証データ:2、評価データ:2に分割 train_x, valid_test_x, train_y, valid_test_y = train_test_split(x, y, train_size = 0.6, random_state = 1) test_x, valid_x, test_y, valid_y = train_test_split(valid_test_x, valid_test_y, test_size = 0.5, random_state = 1) # Optunaでtrial回数分のモデルをregister_modelで保持する # 試行が完了したら、get_modelで最良の試行回数をキーにモデルを取得する class Callback: def __init__(self): self.models = {} def __call__(self, study, trial): pass def register_model(self, trial_number, model): self.models[str(trial_number)] = model def get_model(self, trial_number): return self.models[str(trial_number)] def objective(trial): # ハイパーパラメータとして指定範囲の乱数を生成 param_alpha = trial.suggest_float("alpha", 0.01, 100.00) # リッジ回帰モデルを作成 model = Ridge(alpha = param_alpha) # モデルの訓練を実行 model.fit(train_x, train_y) # 訓練済みモデルを登録 callback.register_model(trial.number, model) # 訓練済みモデルを使用して、検証データによる予測を実行 y_pred = model.predict(valid_x) # 訓練データの正解値と予測結果から、MAE(Mean Absolute Error)を算出 return sklearn.metrics.mean_absolute_error(valid_y, y_pred) callback = Callback() # セッションの作成 study = optuna.create_study() # 試行 study.optimize(objective,n_trials=100, callbacks=[callback]) # 最良のモデルの取得 best_model = callback.get_model(study.best_trial.number) # 評価データによる予測を実行 pred = best_model.predict(test_x) # 評価データの正解値と予測結果から、MAEを算出 print("MAE : " + str(sklearn.metrics.mean_absolute_error(test_y, pred))) # 学習済みモデルをファイルに出力 filename = "model.pickle" pickle.dump(best_model, open(filename, 'wb')) 作成したPythonコードを実行してモデルの作成と評価を行います。 C:\work>python train.py # --- 実行結果例 ここから ------------------------------------------------- # [I 2021-09-13 16:47:35,285] A new study created in memory with name: no-name-063072d1-55ec-433c-b92e-de199a5af387 # [I 2021-09-13 16:47:35,298] Trial 0 finished with value: 3.4490167509024525 and parameters: {'alpha': 30.18482525879907}. Best is trial 0 with value: 3.4490167509024525. # [I 2021-09-13 16:47:35,300] Trial 1 finished with value: 3.5562661263791084 and parameters: {'alpha': 70.35330894918852}. Best is trial 0 with value: 3.4490167509024525. # : # [I 2021-09-13 16:47:35,690] Trial 68 finished with value: 3.301292598752645 and parameters: {'alpha': 0.1044170092688274}. Best is trial 68 with value: 3.301292598752645. # [I 2021-09-13 16:47:35,694] Trial 69 finished with value: 3.356478806393828 and parameters: {'alpha': 6.2616723436725525}. Best is trial 68 with value: 3.301292598752645. # : # [I 2021-09-13 16:47:35,867] Trial 99 finished with value: 3.3576048354298553 and parameters: {'alpha': 6.4807971462797465}. Best is trial 68 with value: 3.301292598752645. # MAE : 3.6333021709099773 # --- 実行結果例 ここまで-------------------------------------------------- trial 68が最良のモデルとなったため、trial 68の学習済モデルを出力します。学習済みモデルは、上述の実装にある通り、~/model.pickleで出力されます。 モデルの利用 Webサーバの起動 モデルの作成と評価で作成した学習済みモデルをWebサーバにロードして、HTTP API経由で利用してみます。 Flaskで実装したHTTP APIをもつWebサーバ のPythonコードの内容を以下に示します。 web_server.py import pickle import pandas as pd from flask import Flask, request, jsonify # 学習済みパイプラインをロードする filename = "model.pickle" model = pickle.load(open(filename, 'rb')) # Flaskサーバを作成 app = Flask(__name__) # エンドポイントを定義 http:/host:port/predict, POSTメソッドのみ受け付ける @app.route('/predict', methods=['POST']) def get_predict(): # Jsonリクエストから値取得 X_dict = request.json # DataFrame化 X_df = pd.DataFrame(X_dict) # 予測実行 prediction = model.predict(X_df) # 予測結果をJson化して返信 result = {"MEDV": float(prediction[0])} return jsonify(result) # Flaskサーバをポート番号5060で起動 if __name__ == "__main__": app.run(port=5060) Webサーバを起動します。 C:\work>python web_server.py # --- 実行結果例 ここから ------------------------------------------------- # * Serving Flask app "web_server" (lazy loading) # * Environment: production # WARNING: This is a development server. Do not use it in a production deployment. # Use a production WSGI server instead. # * Debug mode: off # * Running on http://127.0.0.1:5060/ (Press CTRL+C to quit) # --- 実行結果例 ここまで-------------------------------------------------- 推論実行 住宅価格を予測したい住宅の周辺情報のデータをJSONファイルで作成し、HTTPリクエストで送信します。入力データのJSONファイルの例を示します。今回各項目の値は、データセットの先頭のデータを利用します。 request.json [ { "CRIM":"0.00632", "ZN":"18.0", "INDUS":"2.31", "CHAS":"0.0", "NOX":"0.538", "RM":"6.575", "AGE":"65.2", "DIS":"4.0900", "RAD":"1.0", "TAX":"296.0", "PTRATIO":"15.3", "B":"396.90", "LSTAT":"4.98" } ] curl コマンドを利用して、HTTPリクエストを送信します。前述で作成したJSONファイルを入力データとします。 # HTTPリクエストの送信 C:\work>curl -v http://127.0.0.1:5060/predict -H "Content-Type: application/json" -d @request.json # --- 実行結果例 ここから ------------------------------------------------- # * Trying 127.0.0.1:5060... # * Connected to 127.0.0.1 (127.0.0.1) port 5060 (#0) # > POST /predict HTTP/1.1 # > Host: 127.0.0.1:5060 # > User-Agent: curl/7.71.1 # > Accept: */* # > Content-Type: application/json # > Content-Length: 242 # > # * upload completely sent off: 242 out of 242 bytes # * Mark bundle as not supporting multiuse # * HTTP 1.0, assume close after body # < HTTP/1.0 200 OK # < Content-Type: application/json # < Content-Length: 28 # < Server: Werkzeug/1.0.1 Python/3.8.8 # < Date: Mon, 13 Sep 2021 09:39:02 GMT # < # {"MEDV":29.493047836307362} # * Closing connection 0 # --- 実行結果例 ここまで ------------------------------------------------- HTTPレスポンスとして、MEDVの値が{"MEDV": 29.493047836307362}のような形式で返却されます。 MEDVは千ドル単位の住宅価格なので、この場合は住宅価格が29,493ドルと予測されたことを示しています。 Webサーバのログには次のようなログが出力されます。 # ---出力例 ここから ------------------------------------------------------ # 127.0.0.1 - - [13/Sep/2021 18:39:02] "[37mPOST /predict HTTP/1.1[0m" 200 - # --- 出力例 ここまで ----------------------------------------------------- おわりに 本投稿では、Python製の機械学習フレームワークであるscikit-learnとハイパーパラメータの最適化を自動化するOptunaとWebサーバであるFlaskを活用して、データの理解・モデルの開発・モデルの利用(サービング)の一連の手順を紹介しました。機械学習に馴染みのない方や、簡単にデータ分析を行ってみたいという方は、是非試してみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

djangoのpathlibについてざっくりとまとめる

今回のお題 今回のお題はpythonのpathlibというモジュールの使い方です。 用途はパスの結合ですね。 前回、パスを結合できるos.path.joinについて扱ったところ、現在はpathlibを用いる方法が便利だというコメントをいただきました。 なので今回はpathlibを用いたパスの結合方法についてまとめていきます。 目次 pathlibの使い方 取得したパスの型・注意点 pathlibの使い方 pathlibの使い方は非常に簡単です。 pathlibからPathをimportする。 Path()もしくは"/"演算子を用いてパスを結合する。 これだけです。 パスの結合 # import from pathlib import Path # Path()で結合 path = Path("dir1", "dir2") # "/"演算子で結合 path = "dir1" / "dir2" 特に、"/"で結合できるのは便利ですね。  取得したパスの型・注意点 一応、取得したパスの型についても触れておきます。 pathlibを用いて取得したパスはPosixPath型と呼ばれる型になっています。 この型はiterableではないので、環境変数の値として設定するときなどタプル型またはリスト型を要求される場合には型変換が必要です。 まとめ 以上がpathlibを用いたパスの連結方法になります。 djangoの特定のファイルにはデフォルトでインポートされていることもあり、なるべくならこちらを使いたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データベースを作る。 (sqlite3)

はじめに sql文が苦手なので、せめてデータベースの作成と読み込みの関数だけ作る。 コード main.py import sqlite3 def makeDB(db,table_name,columns,cols): #columns={<typename>:<type>,...} size=len(columns) cur=db.cursor() columns=",".join(map(lambda key:key+" "+columns[key],columns)) cur.execute("create table if not exists {0} ({1})".format(table_name,columns)) hatenas=",".join(["?"]*size) for col in cols: try: cur.execute("insert into {0} values ({1})".format(table_name,hatenas),col) db.commit() except Exception as e: pass def readDB(db,table_name): cur=db.cursor() return db.execute("select * from {0}".format(table_name)) 使う a.csv Natsumi;40;152;83;60;80 Sayaka;37;158;80;60;87 Maki;36;158;83;60;88 Mari;38;144;75;60;80 Rika;36;157;78;60;84 main.py def readcsv(fname,encoding="utf8"): with open(fname,"r",encoding=encoding) as f: for line in f: yield line.rstrip().split(";") db=sqlite3.connect("a.db") columns={"name":"text unique","age":"int","tall":"int","T":"int","W":"int","H":"int"} fname="a.csv" makeDB(db,"MM",columns,readcsv(fname)) for col in readDB(db,"MM"): print(col) db.close() 実行結果 $ python main.py ('Natsumi', 40, 152, 83, 60, 80) ('Sayaka', 37, 158, 80, 60, 87) ('Maki', 36, 158, 83, 60, 88) ('Mari', 38, 144, 75, 60, 80) ('Rika', 36, 157, 78, 60, 84) おわりに 些細なものだが、sql文を見なくて済むのは嬉しい。しかしいずれは見る羽目になるか。(笑)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 3.10 lxml (wheel) を Windows 10 にインストールする

下記のサイトから lxml の wheel をダウンロードする。 lxml‑4.6.3‑cp310‑cp310‑win_amd64.whl コマンドプロンプトにてダウンロードしたディレクトリで下記を実行する。 pip install lxml-4.6.3-cp310-cp310-win_amd64.whl
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BookoffオンラインのAPIをつくってみた。 #2 (ブックマーク、カートに追加 )

今回の目的 前回の記事はこちら 前回はデータを取得する関数をつくった。勿論データを取るのも良いが、今回はもう少し自作プログラムっぽいこと(?)をしたい。つまり、ブックマークやカートに本を追加したい... ちょっとややこしそうだが、前回同様、bookoffオンラインのサイトの構造はかなり単純なので、スイスイと進んでいく。 ログインする まずはログインする。しなくてもやり方はあるか?... そんな高度そうなことはどうせわからないから、男は黙って selenium の webriver を ... しかしそれは最終手段と言う感じがして使いたくない。ここは流石にbookoffオンラインなので、もっと原始的にイケそうである。 api.py HOST="www.bookoffonline.co.jp" URL="https://"+HOST SEARCH_URL=URL+"/disp/CSfSearch.jsp" LOGIN_URL=URL+"/common/CSfLogin.jsp" HEADERS={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/5377.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"} def isloginurl(text): return "MemberForm" in text def login(session,mail,pw): params={ "name":"MemberForm", "ID":mail, "PWD":pw } res=session.get(LOGIN_URL,headers=HEADERS,params=params) return not isloginurl(res.text) loginの引数 引数 意味 session セッション mail メールアドレス pw パスワード 今回の session は重要である。もし session=requests にすると、ログイン情報はメモリの彼方に消えてしまう。 isloginurl はテキストによってそれが LOGIN_URL のものか調べる。 metaタグに、urlが見つからなかったので、こんな有様に ... (笑) 要するに、ログインして、ページが変わったら成功、変わらなかったら失敗である。 試してみる パスワードは getpass で入力してみる。 main.py import api import getpass import requests session=requests.Session() mail="tetsuya1729@icloud.com" pw=getpass.getpass() logined=api.login(session,mail,pw) print(logined) 正しいのを入力すれば、 $ python main.py Password: True 間違えれば $ python main.py Password: False よしよし。 ブックマーク、カートに追加する ログインできたら、もう7合目である。(早い(笑)) 次は、本をブックマークに追加し、カートにも追加する。 api.py ADD_BM_URL=URL+"/member/BPmAddBookMark.jsp" ADD_CART_URL=URL+"/disp/CSfAddSession_001.jsp" def addCart(session,bookid): params={ "iscd":bookid, "st":1 } return session.get(ADD_CART_URL,params=params) def addBM(session,bookid): params={ "iscd":bookid, "st":1 } return session.get(ADD_BM_URL,params=params) 引数 意味 bookid 本のID session セッション bookid とは本のIDで、url で言えば、 https://www.bookoffonline.co.jp/old/<bookid> のように末尾にあたる。砂の女なら、 https://www.bookoffonline.co.jp/old/0015580570 bookidを渡すのは実用的に見えないので、後でラップする。 実演は難しいので割愛。 まとめる api.py class Client: def __init__(self,mail="",pw=""): self.session=requests.Session() self.__logined=False if mail and pw: self.login(mail,pw) def __getattr__(self,attr): return getattr(self.session,attr) @property def logined(self): return self.__logined def login(self,mail,pw): self.__logined=login(self.session,mail,pw) return self.logined def addCart(self,bookid): if not self.logined: return False addCart(self.session,bookid) def addBM(self,bookid): if not self.logined: return False addBM(self.session,bookid) 実用的に 次に、上のコードをもう少し実用的にしたい。 これは、すこし「原始的」ではない感じなので、別のファイルに書いてみる。 一致した本を返す 前回の search 関数は、クエリからデータのリストを返したが、今回はぴったり一致するデータを返す。しかし厳密に完全一致なら、クエリを入力する方が大変なので、そこはうまくやる。 util.py from . import api import re ALL_LEFT_KAKKO='\xef\xbc\x88' #"(" of all ALL_SPACE='\xe3\x80\x80' #" " of all U_SPACE="\u3000" NBSP="\xa0" #&nbsp GET_TITLE_F_2="{0}"+"[\({0} {1}{2}{3}].*".format(ALL_LEFT_KAKKO,NBSP,ALL_SPACE,U_SPACE) def get(title,author,type_=api.SearchType.BOOK,n=3): title=re.escape(title) def matchtitle(data_title): if re.fullmatch(title,data_title): return True return re.fullmatch(GET_TITLE_F_2.format(title),data_title) for data in api.searchN(title,start=1,n=n,type_=type_): if matchtitle(data["title"]) and re.fullmatch(author,data["author"]): return data return None 引数 意味 title タイトル author 著者名 type データの型 n ページ数 まず引数は、基本的に searchN と同じである。start がないのは、一致するものは、最初の方に来るやろう決め打ちしてるからで、本当はページ数 n も不要な気がするが、一応。しかし偏見か?(笑) まずデータを取って、タイトルと著者名が一致するか調べる。 matchtitle と言いながら fullmatch を使ってるが、細かいことは気にしない。 matchtitle は少し複雑になってしまった。見ての通り fullmatch が2つあって、一つ目は普通の完全一致で、2つ目がやや複雑なのは、以下ののような対応を一致してると見なしたかったせい。 title data_title 砂の女 砂の女(新潮文庫) 熱砂の女たち 熱砂の女たち アラブの心 ダレン・シャン 奇怪なサーカス ダレン・シャン 奇怪なサーカス(1) 使ってみる パッケージとしてつかう。 main.py import bookoff.util as bfu import sys title=sys.argv[1] author=".+" data=bfu.get(title,author) print(data) $ python main.py 砂の None $ python main.py 砂の女 {'title': '砂の女(新潮文庫)', 'url': 'https://www.bookoffonline.co.jp/old/0015580570', 'author': '安部公房', 'price': 200, 'stocked': True} 実用的にまとめる 先ほど、ブックマークやカートに追加するのをまとめたものを、もう少し実用的にする。 util.py def getidInUrl(url): id_=re.search("/(?P<id>[0-9]+)$",url) return id_.group("id") if id_ else None def getid(title,author=".*"): data=get(title,author) if not data: return None return getidInUrl(data["url"]) class Client(api.Client): def addCart(self,title,author=".*"): bookid=getid(title,author) return super().addCart(bookid) def addBM(self,title,author=".*"): bookid=getid(title,author) return super().addBM(bookid) getidInUrl 引数 意味 url url getid 引数 意味 title タイトル author 著者名 ミソは getid で、本と著者名から、その本の ID を取る。単純だが、なんか気持ち良い。(笑) それを使って、Client をラップする。 実演? 実演は難しいのでコードだけ a.txt 砂の女 ロシア的人間 狩猟と遊牧の世界 伊豆の踊子 坂の上の雲 main.py import bookoff.util as bfu import sys import getpass fname=sys.argv[1] mail="tetsuya1729@icloud.com" pw=getpass.getpass() client=bfu.Client(mail,pw) with open(fname,"r") as f: for title in f: title=title.rstrip() print(title) client.addBM(title) 時間はかかるが、一応使える。 終わりに もう API は良いかな。やるなら次はシェルを作りたい。それより bookmeter と連携させたいか。しかしサイトの構造が単純で驚いた。YoutubeやPornhubもこれくらい単純なら嬉しいが、やはり動画は守られてしまうか。。。 しかし、そういう大手のものは、もっと頭の良い方が解析してソフトを作ってくれるので、ボーっと待っておこう。youtube-dl なんて使われてるが、正規なら動画のダウンロードで月1500円取られるわけやから、グレー寄りのブラックと言う感じがする。 しかしそれでも商品やプレミア配信は流石に取れないか。頑張ったら取れるんかな。。。(笑) 全然関係ない話でした、すみません。^-^ コードはこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む