20210506のPythonに関する記事は30件です。

Github Actionsで呼び出すPythonコード上で環境変数(Github Secrets)を使う方法

やりたいこと Github ActionsのワークフローでPythonコードを呼び出す Public Repositoryなので、アクセストークン等をSecretsに隠したい 結論 ※Secretsへの登録は多くの記事で紹介されていると思うので検索してみてください。 ワークフロー側 - name: Run XXXXX.py # Pythonファイルの実行 env: API_KEY: ${{ secrets.API_KEY }} # secretsに登録した環境変数 run: python python/XXX.py # pythonフォルダに格納したXXX.pyを実行 pythonコード側 import os # secretsに登録した環境変数の呼び出し API_KEY = os.environ.get("API_KEY ") 調べてもなかなか見つからなかったので、、、 誰かの助けになりましたら幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出しpythonエンジニアのまとめ デザインパターンまとめ3(builder)

趣旨 駆け出しエンジニアの備忘録の垂れ流し。  ※ 出典漏れがあればご指摘ください 出典: https://yamakatsusan.web.fc2.com/ https://dackdive.hateblo.jp/ https://www.oreilly.co.jp/books/9784873117393/ builderパターン概要 複合オブジェクトについて,その作成過程を表現形式に依存しないものにすることにより,同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする。 Builderでは複合オブジェクトを段階的に作成していく過程に焦点をあてる。Abstract Factoryでは部品の集合を強調している(それが単純であっても複雑であっても) 複雑な部品生成処理を行う場合、作成を指示するdirectorを設けることで、clientと作成者(builder)との間に必要な、複雑な処理を吸収する。directorのメインルーチンで生成手順を記述するため、builderに段階的に部品を作らせることも、複数の部品を作らせることも可能。 段階的な手順での部品作成が必要であり、かつ、部品の作成手順が頻繁に変更される可能性が存在する場合に利用する クラス図とシーケンス図 wikipediaより引用 実装方針 AbstractBuilderクラスではtemplate methodのようにオブジェクトインスタンス作成手順(必須メソッド)を規定する。 Builderサブクラスでは必須メソッドをオーバーライドする。 Directorクラスでは生成処理(construct)としてbuilderの必須メソッドを呼び出しながら、オブジェクト生成の手順を記述する。 Client側ではDirectorクラスのみ呼び出し、directorのconstructのみを呼び出すことでオブジェクトを生成する処理を記述する。 実装例1 # builder側 class AbstractBuilder: @abstractmethod def build_part1(self): pass @abstractmethod def build_part2(self): pass @abstractmethod def build_part3(self): pass class ConcreteBuilder(AbstractBuilder): def build_part1(self): # 実処理を記載 self.do_something() # 処理を実装 def build_part2(self): # 実処理を記載 self.do_something2() # 処理を実装 def build_part3(self): return self.get_product() # 処理を実装 # director側 class MyDirector: # 注目すべきは考え方なので、内部の実装は作成するものによる(あくまで一例) def construct(self): #生成手順を実装 self.builder.build_part1() self.builder.build_part2() return self. builder.build_part3() @classmethod def create_director(cls, some_args: obj): builder_cls = get_builder_cls(some_args) # 処理を実装 return cls(builder_cls()) # client側 def main(): director = MyDirector.create_director("some_args_for_deciding_builder_cls") product = director.construct() 実装例2_Directorを呼びわけるパターン例 # director側 class AbstractDirector: @abstractmethod def construct(self): pass @abstractmethod def create_director(self): raise NotImplementedError() @classmethod def get_director_cls(cls, some_args: obj): director_cls = get_director_cls(some_args) # 処理を実装 return director_cls class MyDirector(AbstractDirector): def construct(): #生成手順を実装 self.builder.build_part1() self.builder.build_part2() return self. builder.build_part3() @classmethod def create_director(cls,some_args: obj): builder_cls = get_builder_cls(some_args) # 処理を実装 return cls(builder_cls()) class YourDirector(AbstractDirector): def construct(): #生成手順を実装 self.builder.build_ex_part1() self.builder.build_ex_part2() return self.builder.build_ex_part3() @classmethod def create_director(cls,some_args: obj): builder_cls = get_builder_cls(some_args) # 処理を実装 return cls(builder_cls()) # client側 def main(): director_cls = AbstractDirector.get_director_cls("some_args_1") director = director_cls.create_director("some_args_2") product = director.construct() メリットと使い所 メリット インスタンス生成のロジックが、clientコードに依存しない(Directorのインターフェースに依存する)。 複雑な生成処理をDirectorに依存させる事ができる。また用法2のようにDirectorサブクラスでの実装とすれば、 同様のオブジェクトを、異なった手順で生成することもclientに依存させずに作成することができる 使い所 多くの構成要素からなるオブジェクトを生成するアルゴリズムを,構成要素自体やそれらがどのように組み合わされるのかということから独立にしておきたい場合。 オブジェクトの作成プロセスが,オブジェクトに対する多様な表現を認めるようにしておかなければならない場合。 段階的な手順での部品作成が必要であり、かつ、部品の作成手順が頻繁に変更される可能性が存在する場合に利用する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

seleniumで自動打刻(ジョブカン)

こんばんは! 初めての記事投稿になります(test投稿) タイトルの通り、Pythonでseleniumを用いて自動打刻する方法を記載します。 シンプルにコード数を短めにしました。 ※別途環境設定は必要になります。私はanacondaで仮想環境を作りました。 ---------以下コード------------------------------------ from selenium import webdriver driver = webdriver.Chrome(executable_path="/Users/個々人/Desktop/自動打刻/chromedriver") driver.get("https://id.jobcan.jp/users/sign_in") inputUserName = driver.find_element_by_id("user_email") inputPassWord = driver.find_element_by_id("user_password") loginBtn = driver.find_element_by_name("commit") inputUserName.send_keys("ID") inputPassWord.send_keys("PW") loginBtn.click() url = "https://ssl.jobcan.jp/jbcoauth/login" driver.get(url) driver.find_element_by_id("adit-button-push").click() -----------ここまで--------------------------------------- executable_pathで「chromedriver」の絶対パスを指定しないと動きませんでした(謎) また、ログイン後に「find_element_by_id」もしくは「座標取得」にて遷移ボタンクリックを試みるも 上手くいかなかったため、URLを指定しました(力業) 以上、非常にシンプルだと思います。 私は「cron」で毎日動かしています。今では打刻忘れもなくなり非常に便利になりました。 初めての投稿ですが、恐らくコードの載せ方が違うような。。。 勉強していくので、暖かい目で見守ってください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

if文におけるans == 0はなぜ上手くいくのか?(今日のPython Day6)

0. はじめに  「そろそろ寝ようかな?」と思っていざ寝ようとしたら記事を投稿することを忘れていることに気づき、慌てて記事投稿しました。危ない、危ない。  毎日1問、Pythonの問題を出題します。出題範囲は特に定めていませんがはじめの1ヶ月くらいは『入門Python3 第2版』の第1~11章までのことが分かれば解ける問題にしたいと思います。「こういう問題を作って欲しい」などのリクエストがございましたら初心者ながら頑張って作問します。また「別解を思いついた」、「間違えを見つけた」などがありましたら遠慮なくコメント欄にて教えて下さい。記事を執筆している当人もこの記事を読んでくださった方も新たなことを学ぶことができるので。 1. 問題  以下のプログラムにおいて入力値が0の場合、最初のif文が実行される。しかし明示的にブール値Falseとされていなくても整数のゼロ(0)のBool値はFalseである。なぜ0はFalseとみなされるのにinput()関数が受け取る値が0のとき、きちんと最初のif文が実行されるのだろうか。その理由を説明しなさい。 print("pythonが楽しいと思う人は1を、楽しくないと思う人は0を入力してください。") ans = int(input()) if ans == 0: print("私も!") elif ans == 1: print("そいういときもあるよ") else: print("正しい数を入力してください") 2. 解答  整数0のブール値ではなく条件式であるans == 0のブール値をif文は確認しているから。 3. ヒント  if文は何のブール値を判断して条件分岐をしているのでしょうか?ちなみにブール値とはTrueかFalseのことです。 4. 解説  今回の説明問題は今日の授業中にふと疑問に思ったことをそのまま問題として出題しました。if文がチェックしているのはif文のすぐ次に書かれている条件式です。つまり変数ansの値が0かどうかをif文は判断しているのであって「0」がTrueかFalseかを判断しているわけではありません。「なんでこんなことで自分は1時間も引っかかったのだろうか」と記事を執筆している今は思うのですが、問題文のようにカタカナと専門用語多めで説明されると引っ掛かってしまった人もいるのではないでしょうか? 5. ans == Falseならどうなるか? (発展編)  さて、ここからは発展編です。では次の場合、整数の0を入力するとどうなるでしょうか? print("pythonが楽しいと思う人は1を、楽しくないと思う人は0を入力してください。") ans = bool(input()) if ans == False: print("私も!") # 以下省略  今回もうまくいくかと思いきや、「私も!」とは表示されずうまくいきません。理由はわかりますか?答えは「inputで入力された整数0は文字列の0に変換されるから」です。整数の0や浮動小数点0.0はそれ自身がFalseとみなされるのですが、文字列の0は単なる文字なのでTrueと見なされます。そのためif文の左辺「変数ansのブール値はTrue:、右辺は「False」となりif文は実行されません。 print("pythonが楽しいと思う人は1を、楽しくないと思う人は0を入力してください。") ans = bool(input()) print(type(ans)) # ->文字列の0と認識されるのでTrue if ans == False: print("私も!") # 以下省略 もう1つ事例を考えてみましょう。次の場合、0を入力するとどうなるでしょうか? print("pythonが楽しいと思う人は1を、楽しくないと思う人は0を入力してください。") ans = bool(int(input())) print(ans) if ans == False: print("私も!") # 以下省略    今回は「私も!」と表示されうまくいきました。上手く言ったのは入力値を整数に変えてからブール値を求めたからです。 6. まとめ ・if文はその後に書かれた条件式のブール値を確認して処理する。 →・「if文自体が bool関数を使って True か False を判断してくれる」 コメント欄にて教えていただいたのでまとめを変更しました。 @shiracamus様、本当にありがとうございます! 7. おまけトーク  この疑問を思いついたのは授業中なのですが、その授業はPythonに関する授業じゃなかったんですよ。何の授業だったと思いますか? ・・・  実は古代ローマ史の授業でした。はい。最後まで読んでいただき本当にありがとうございます。 参考文献 「Bill Lubanovic著『入門Python3 第2版』(オライリー・ジャパン、ISBN978-4-87311-932-8)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Zope 5.0のインストール(Ubuntu 21.04)

(執筆 2021/5/6)  とうとう Zope を再び試す日が来ました。Zope 3 で混乱に陥った人も、Zope 2 が帰ってきたと喜ぶでしょう。取りえず、標準のインストールと立ち上げを試みます。 Python3 のインストール  Zope 5.0 は、3.5 以前の Python では動作せず、 3.9 のサポートが追加されています。2021/5/6 時点で Ubuntu 20.10 は 3.8.6 、Ubuntu 21.04 は 3.9.4 です。Ubuntu 20.10 は、3.8.6 になっています。 sudo apt-get install python3-dev python3-venv ビルドアウト (Zope documentation)  Zopeのインストールには、Pythonの仮想環境で zc.buildout という仕組みで行われます。とりあえずホームディレクトリに環境を作成します。 cd wget https://pypi.org/packages/source/Z/Zope/Zope-5.0.tar.gz tar xzvf Zope-5.0.tar.gz cd Zope-5.0 python3 -m venv . bin/pip install -U pip wheel zc.buildout bin/buildout 設定と実行 (Zope documentation)  初期ユーザーの名前とパスワードを設定し、etc の下に設定ファイルを作成します。 bin/mkwsgiinstance -d .  これで、etc ディレクトリの中に、site.zcml, zope.ini, zope.conf のファイルが出来上がりますので、Zopeを起動します。 bin/runwsgi -v etc/zope.ini http:localhost:8080/manage にアクセスし、初期ユーザーのユーザー名、パスワードを入力して、管理画面に入ることができます。  懐かしい、素っ気ない画面に出会うことができました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】ツイートを自動ファボするbotを自作してみた【cron】【完全自動化】

今回はPythonとMac標準搭載のCronを使って、 指定した語句を含むツイートを完全自動でファボするbotを開発したので、 こちらのノウハウを紹介する。 背景 Twitter運用でテキトーにふぁぼすれば認知度増えて、 フォロワー増えんじゃねという仮説のもと、ツール作成を発案 最初は弟に作成を依頼していたが、私がエンジニア配属になったので勉強がてら自前作成することに... 開発者のスキル ・文系私立大学出身 ・開発当時のプログラミングスキルは開発未経験の初心者(Progateは一通り実施) ・もちろんAPIなどにはノータッチ ・現在はクラウドインフラエンジニアとして日々精進 ・Twitter @postkershaw こんな方に読んでほしい ・Twitterの運用を少しでも自動化したい ・TwitterAPIを触ってみたい ・Pythonを使ってみたい ・プログラミングを始めたばかりで、実践的なコードを書いてみたい 開発環境 OS : Mac 11.3.1 統合開発環境 : VS code 言語 : Python3, mac linux TwitterのAPI使用 本投稿の内容 ・TwitterのAPI取得から自動ファボを実施するMACの設定方法まで ※あくまで自己責任での実施をお願いします。 TwitterAPIの取得 自動ファボBotを作る上でTwitterのAPIが必要だということを有能な弟から事前に聞いていたので、ググってみたら魅力的な記事を発見!! ちょっと情報は古いけど、ここにある手順通りに進めたら無事申請できた(手順変わってたらすみません...) Pythonのコーディング 具体的なコーディング方法は以前noteの方に書いたので、こちらを参照していただけると幸い ちなみに favoriteのfile.py for result in reversed(search_results): このように書き換えると投稿の早い順にソートを変更可能 cronを使用し、ふぁぼの実行さえも自動化 macのターミナルを使用して、 mac_terminal crontab -e を実行すると空のcrontabが出てくるので、 mac_terminal 03 8 * * * python3 実行ファイルのpath のようなコマンドを入れる(iを押してinsertモード) 意味としては、左から 03 = 分 8 = 時 * = 日付(アスタリスクだと毎日) * = 月(アスタリスクだと毎月) * = 曜日 (アスタリスクだと毎日) 今回はPython3を使用するので、python3と指定した 記入後は、escキー → 「:wq」 mac_terminal crontab -l こちらのコマンドを実行し、記述したコマンドが反映されていたら設定完了 crontabに関して、詳しくは下記参照 crontabの書き方 cronにアクセス権を付与してない方向け しかし、cronで設定しただけだとパソコンがスリープ状態では動作しない... 自動でスリープ状態から起動する設定をする 「システム環境設定→バッテリー→スケジュール」を設定可能 起動またはスリープ解除と同時に、スリープになる設定も行うと良いかも これら全ての起動に成功すると... このような画面がターミナルに見える You have new mailの内容をみたいなら mac_terminal mail を実行すると、cronからコマンドを実行したというログメール的なものが届く 振り返り AWSなどで24時間稼働するようなVMかサーバを立てないと 厳しいかなと思っていたが、自分のPCでも実行可能で大満足 結論: Pythonは怠け者の味方 ちなみに自動ファボを実施してフォロワーは殆ど増えていません ツイッターって難しい... よろしければフォローをお願いします  Twitter @postkershaw 主にテクノロジー関連と株について呟いてそうろう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出しpythonエンジニアのまとめ デザインパターンまとめ2(factory method)

趣旨 駆け出しエンジニアの備忘録の垂れ流し。  ※ 出典漏れがあればご指摘ください 出典: https://yamakatsusan.web.fc2.com/pythonpattern08.html https://dackdive.hateblo.jp/entry/python-design-pattern/abstract-factory https://www.oreilly.co.jp/books/9784873117393/ factory methodパターン概要 基底クラス(Creatorクラス)にProductインスタンス生成を行うためのファクトリーメソッド(インスタンスを生成するためのメソッド)を定義するが、その生成処理が複数の複雑な手順となる場合、その手順はサブクラス側に定義する(template methodをインスタンスの生成に応用したもの。) (createメソッドををdirectorとして別クラスに規定したらbuilderパターンになるのでは?) クラス図とシーケンス図 wikipediaより引用 実装方針 Creatorクラスではファクトリーメソッド(create)を実装する。ファクトリーメソッド内で呼ばれる作成手順の規定のみ行う。 Creatorサブクラスでは作成手順をオーバーライドする。 ClientではCreatorサブクラスを呼び出し(生成し)、createを呼び出す処理を実装する Client側では呼び出すCreaterサブクラスの指定が必要。Creatorサブクラスを決定するメソッドがCreatorクラスにあると便利。 実装例1 # factory側 class Creator: def create(): return create_proc1() @abstractmethod def create_proc1(self): pass @classmethod def get_creator(cls, some_args): selected_class = get_class(some_args) # クラスを指定するロジックを記載 return selected_class() class Creator1(Creator): def create_proc1(self): # 実処理を記載 return Product1A(self) # client側 def main(): creator = Creator.get_creator(choose_some_keywords_for_selecting_cls) product_1 = creator.create() abstract factoryと factory methodの違い 違いがよくわからなくなったのでしらべてみた Factory MethodパターンとAbstract Factoryパターンの2つの違い - 生成するProductインスタンスの数(部品の数)が異なる(Abstract Factoryは複数の生成を考えやすい) - Abstract Factoryではインスタンスの生成をサブクラスが行い、Factory Methodでは基底クラスが行う。 - Abstract Factoryでは1つのFactoryですべての部品を作るようなイメージ、Factory Methodは1つの部品に1つのFactoryで対応するイメージ - 個人的には、生成Productインスタンスが1つ、かつ、生成処理が複雑ならFactory methodを使うほうが、実装回数が減るので採用したい。それ以外の場合であれば、Abstract Factoryのほうが可読性が高そうな気もする。(コードをたどる際、毎回Factory methodを基底クラスまで見に行く必要があるので)。インスタンスの生成方法が頻繁に変更される可能性がある場合は、最悪createメソッドのオーバーライドで無理やり変えられるのでFactory methodがいい....? メリットと使い所 メリット(Abstract Factoryと一緒) インスタンス生成のロジックが、clientコードに依存しない(インターフェースに依存する)。 作り方、作るもの、実際に作る、を別のモジュールに実装できる 使い所 生成方法がほぼ共通の場合に効果がある 生成Productインスタンスが1つ、かつ、生成処理が複雑なとき ・クラスが,生成しなければならないオブジェクトのクラスを事前に知ることができない場合。 ・サブクラス化により,生成するオブジェクトを特定化する場合。 ・クラスが責任をいくつかのサブクラスの中の1つに委譲するときに,どのサブクラスに委譲するのかに関する知識を局所化したい場合。 引用元:https://yamakatsusan.web.fc2.com/pythonpattern04.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python実践データ分析100本ノック:ノック71でNetworkXError

結論 decoratorのバージョンを4.3.0に落とす エラー詳細 NetworkXError Traceback (most recent call last) <ipython-input-1-55dde0c34e24> in <module> 18 G.add_edge(str(i),str(j)) 19 ---> 20 nx.draw_networkx(G,node_color="k", edge_color="k", font_color="w") 21 plt.show() ----------------- NetworkXError: random_state_index is incorrect 解決方法 decoratorのバージョン確認 $ conda list decorator 5.0.6 pyhd3eb1b0_0 decoratorのバージョンを4.3.0に指定してインストール $ pip install decorator==4.3 decoratorのバージョン再確認 decorator 4.3.0 pypi_0 pypi 参考記事 https://github.com/microsoft/CyberBattleSim/issues/4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでお手軽テキスト全文検索 shellinford-python の紹介

ちゃお……† 今回はPythonでお手軽にテキスト全文検索出来るライブラリ shellinford-python を紹介します。 こちらは@echizen_tmさんの shellinford をSWIGでPythonで動作させるようにしたのと、若干の機能追加したものです。Python環境さえあれば簡単にシンプルな全文検索出来るのでお手軽かと思います。 FM indexとは バイオインフォマティクスなんかで使われる検索アルゴリズムです。わたしが説明するより良い説明を見つけたので以下に引用します。 シークエンサーのデータを解析するとき、リードをリファレンス配列にマッピングすることが第一歩である。一般にシークエンサーのリードは数百万個以上で、また、リファレンス配列の塩基数は数億にも及ぶ。これらの数百万のリードを、数億塩基からなるリファレンス配列にマッピングする際に、高速な文字列検索アルゴリズムが必要される。このようなアルゴリズムとして、FM index がある。実際に、現在のマッピングプログラムのほとんどは、FM index アルゴリズムを中心に使用してマッピングを行なっている。なお、FM index は完全一致でしか検索できない。そのため、ほとんどのプログラムでは、まずリードの一部分だけをリファレンスに対して完全一致で検索する。そして、完全一致でヒットした箇所で、リードの残った部分をリファレンスに対して Smith-Waterman アルゴリズムでアライメントを行う。 https://bi.biopapyrus.jp/rnaseq/mapping/bwt.html shellinford-pythonの使い方 インストール pip install shellinford PYPIでWindows用のwheelを配布しているのでWindowsでのC++11コンパイル環境がなくても使えると思います。その他のOSではC++11コンパイル環境が必須です。 インスタンスを生成 import shellinford fm = shellinford.FMIndex() shellinford.FMIndex([use_wavelet_tree=True, filename=None]) filename パラメーターに str を与えた場合は、パスにある FMIndex モデルのファイルをロードします。 FM-index モデルを作る fm.build(['Milky Holmes', 'Sherlock "Sheryl" Shellingford', 'Milky'], 'milky.fm') FMIndex.build([docs, filename]) docs には str 型の list を与えます。 filename に str を与えるとそのパスにモデルを保存します。 上記の例では、下記のドキュメントを 'milky.fm' に保存します。 Milky Holmes Sherlock "Sheryl" Shellingford Milky FM-index モデルから指定した文字列が含まれているドキュメントを検索する search(query, [_or=False, ignores=[]]) query パラメーターは str あるいは str の list です。 _or パラメーターに True を与えるといわゆるOR検索を行います。デフォルトではいわゆるAND検索です。ignores は str の list で、検索対象から除外したい文字列を指定できます。 注意点として search メソッドは、FM-indexモデルを生成した後かロードしたのみ使えます。 1つのキーワードで検索する例 >>> for doc in fm.search('Milky'): >>> print('doc_id:', doc.doc_id) >>> print('count:', doc.count) >>> print('text:', doc.text) doc_id: 0 count: [1] text: Milky Holmes doc_id: 2 count: [1] text: Milky 複数のキーワードでAND検索する例 >>> for doc in fm.search(['Milky', 'Holmes']): >>> print('doc_id:', doc.doc_id) >>> print('count:', doc.count) >>> print('text:', doc.text) doc_id: 1 count: [1] text: Milky Holmes 指定した文字列を含むドキュメントの数をカウントする >>> fm.count('Milky'): 2 >>> fm.count(['Milky', 'Holmes']): 1 count(query, [_or=False]) こちらもquery パラメーターは str あるいは str の list です。 _or パラメーターに True を与えるといわゆるOR検索を行います。デフォルトではいわゆるAND検索です。カウントしたいだけの場合は、count メソッドの方が search メソッドより少し処理が速いです。 注意点として count メソッドも、FM-indexモデルを生成した後かロードしたのみ使えます。 ドキュメントを追加する >>> fm.push_back('Baritsu') push_back(doc) doc パラメーターは str です。 注意点として push_back メソッドは、 build メソッドでFM-indexモデルをビルドするまで検索に反映されません。 ファイルからFM-indexモデルをロードする >>> fm.read('milky_holmes.fm') read(path) path は str でモデルのパスを指定します。 FM-indexモデルをファイルに書き込む >>> fm.write('milky_holmes.fm') write(path) path は str でモデルのパスを指定します。 FM-Indexに文字列が含まれているか確認する >>> 'baritsu' in fm True
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ分析について「Python」

データ分析とPython Python言語はデータ分析を行う為に データアナリストやデータサイエンティスト、ビジネスアナリストが使用する一般的な、プログラミング言語です。 学習が容易であることに加え、Numpyなどの優秀なライブラリや、コミュニティの活動が活発であることがあげられます。 現在の生活は全ての出来事がデータを形成しています。 それらをデータとして取り扱えるようになる為に、まず最初に幾つかの工程や知識を覚える必要があります。 Pythonという言語を覚える前に、データ分析についての知識、使用されるライブラリやアルゴリズム、設計思想などを細かく理解する必要があります。プロセスの全体の理解や概念、考察を深めることが重要になります。 Python自体は処理能力などは、C言語などと比較すると処理速度は関してはかなり遅いです。 しかしC-APIで開発されたNumpyライブラリなど、言語自体の遅さを補うほどデータサイエンス向けに使いやすい環境が整っています。 皆さんご存じの通りJupyter含む視覚的開発環境に加え、様々なプラットフォームで開発することが可能です。 WindowsはもちろんLinux、Mac、Android、ios、その他。 学習する気になれば、どんな場所でもどんな所でも開発を行うことが可能です。 -簡単な環境作りからやってみましょう。 最初にPythonをダウンロードします ◆下記のURLからダウンロードできます windowsインストーラーもあります [python.org] https://www.python.org/downloads/ Macは以下のコマンドでインストールします。 $ brew install python3 Pythonのインストールや使用方法が難しいと思った人は アナコンダパッケージを使うのも良い選択です。 [www.anaconda.com] https://www.anaconda.com/distribution/ アナコンダはいくつものアプリケーションやパッケージソフトを一括管理できるソフトウェアです。 隔離された環境をすきな数だけ作ることができます。 pythonにもvenvというものがありますがコンソール画面だけで操作しなくてはいけないので 初心者には辛いかと思います。こちらはグラフィカルインターフェイスで操作を行えるのが強みです。 私は日本語の文章を書くのが苦手なのでおかしなところもあると思いますが多めにみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python未導入環境においてPandasGUIとpandas-profilingを使用可能なEDAツール『Pandas Anywhere』を作ってみた

はじめに この度、PythonライブラリであるPandasGUIとpandas-profilingを、Pythonをインストールしていない環境においても使用できるEDAツール『Pandas Anywhere』を作成したので公開します。本ツールを使用することで、誰でもどこでも簡易にビッグデータ※の分析が可能となります。 ※本記事でいうビッグデータとはMicrosoft ExcelやAccessで扱うのが困難な大容量データを指します。 作成の動機 前回書いた記事「Python初学者のためのPandas100本ノック」では、知り合いにPython・機械学習を始める人が増えてきていることから、そのような人たちの学習を支援するコンテンツを作成しました。私の周りでは、このコンテンツをきっかけにPython、pandasを用いたデータ分析ができるようになった人もおり、目的通りの効果があったと思います。 しかし、中にはPythonの習得に時間をかけられない人もいると思います。そのような人がビッグデータを分析する必要が生じた場合、読み込めるファイル容量に制限があるMicrosoft ExcelやAccessでは分析は困難です。また、Tableauのようなデータ分析ツールが使えればいいですが、ライセンスを購入するにもお金がかかります。そこで、このような非Pythonユーザーでもビッグデータを簡易に分析できるツールがあると良いと考え、今回のツールを作成しました。 非Pythonユーザー向けのツールですが、Pythonユーザーにおいても、手元のデータをいちいちコードを書いてEDA(探索的データ分析)することが面倒に感じるときがあるかと思います。そのような人にとっても、今回のツールは大いに役立つと考えています。 Pandas Anywhereの概要 ツールには、PandasGUIとpandas-profilingの機能が搭載 exeファイルからツールを起動 GUI上から分析したいデータファイル(csv、txtファイル)を読み込み、GUI上の「PandasGUI」、「pandas-profiling」ボタンをクリックすることで各機能が呼び出される ※ GUIはPython Tkinterフレームワークで実装、exe化はPyinstallerを使用 PandasGUIとは PandasGUIはPandasデータフレームをGUI上で分析するためのライブラリです。エクセルライクなインタフェース上でデータ操作が可能です。主な機能としては以下の5つがあります。 カラムの昇順・降順の並べ替え クエリ式を用いたカラムのフィルタリング 統計要約の表示 データのチャート表示(グラフ表示) ピボット 詳細については下記URLの解説が分かりやすいです。 PandasGUI:グラフィカルユーザーインターフェイスを使用したPandasデータフレームの分析 以下ではクエリ式を用いたフィルタリングのクエリ例を紹介します。 カラムのフィルタリングのクエリ例 フィルタリングのクエリ例を紹介しますが、まずFiltersタブを以下のようにドラッグし右側に配置しておくと操作がしやすいです(私個人の好みなので任意で実施して下さい)。 ※ クエリ例はTitanicの乗客データを例に説明します Filetersタブ内の「Enter query expression」欄に以下のクエリを入力し、「Add Filter」ボタンをクリックすることでクエリに従いデータのフィルタリングが実施されます。 乗客の性別を女性のみに絞る sex == 'female' 文字列の抽出時には文字を一重引用符' 'もしくは二重引用符" "で囲む 乗客の性別を女性以外に絞る sex != 'female' 乗客の年齢を20歳以上に絞る age >= 20 乗客の年齢を20歳以上かつ40歳以下に絞る age >= 20 & age <= 40 or age.between(20,40) AND条件のときは「&」を使用する 乗客の年齢が20歳以上または性別が男性のレコードに絞る age <= 20 | sex == 'male' OR条件のときは「|」を使用する 乗客の年齢が空白のレコードに絞る age.isnull() 乗客の年齢が空白でないレコードに絞る ~age.isnull() 頭に「~」を付加するとnotの意味になる 乗客のチケットクラスを「1」、「3」に絞る pclass.isin([1,3]) 頭に「~」を付与すれば「1」、「3」を含まないレコードに絞り込める 乗客の名前に「Mr.」を含むレコードに絞る name.str.contains('Mr.') ここまで、フィルタリングする際のクエリの一例を紹介しました。 他にもフィルタリングするためのクエリ表現はありますので必要時に調べてみて下さい。 なお、フィルタリングした状態でファイル出力したい場合は、PandasGUIの画面左上の「Edit」→「Export」にて出力が可能です。 チャートによるデータ可視化 PnadasGUIにはPlotlyベースのインタラクティブチャートが12種類用意されています。チャートを用いることで読み込んだデータを簡易に可視化できます。以下の動画ではHistgram、Box、Scatter 3D、Word Cloudを表示しています。 pandas-profilingとは pandas-profilingとは、pandasデータフレームのプロファイリング結果をまとめて出力してくれるライブラリです。通常、与えられたデータのEDA(基本統計量、カラムの欠損値有無やヒストグラム、カラム間の相関分布などの確認)するためにはpandasのメソッドを用いて何行にも渡るコードを書かなくてはいけません。このライブラリを使用することで1~2行程度のコードで大まかなEDAを実行してくれます。 Kaggle、Signateなどの機械学習コンペにて初手の大まかなEDAでpandas-profilingを使っている人も多いのではないでしょうか。 pandas-profilingの出力結果項目の詳細については下記URLの解説を参考にして下さい。 【便利!】pandas-profiling(Python)による簡易データ解析 作成において苦労した点 後半に本ツールの実装コードを掲載していますが、見ていただけると分かる通り、コード自体はシンプルなものになっています。となると、誰でも簡単にこのツールを作成できるのでは?と思われますが、今回、コードをexe化するために使用したPyinstallerというライブラリが非常に癖があり苦労しました。十数行のコードのexe化であればPyinstallerもエラーは出しませんが、今回のPandasGUIやpandas-profilingのような作りの複雑なライブラリを同封しようとすると山のように多くのエラーをはき出します。 トータルで100個以上はエラーがあったと思います(爆) コンソール画面やJupyter NotebookなどからPythonコードを実行する場合と、exe化されたコードを実行するのとでは内部の挙動が異なることが原因です。 ここでは細かく書きませんが、今回のツール作成でPyinstallerのエラー対処法もノウハウとして多く得られたので、そのうちPyinstallerの処方箋的な記事も書いてみたいと思います。 使用方法 ※ 本ツールはWindows10環境のみで動作可能です Githubよりダウンロードしたexeファイルをダブルクリック(起動に30秒ほどかかります) GUI上の「Load File」ボタンをクリックし、読み込ませたいファイルを選択します。なお、読み込み可能なファイル形式は、csvもしくはtxt(タブ区切り)のみになります。 ファイル読み込みの設定としてEncoding format(utf-8もしくはcp932)、Line number to start reading(読み込み開始行数)の指定があります。読み込み開始行数については、例えば、最初の2行が空白で3行目からデータが始まっているファイルの場合は「3」を指定して下さい。 ファイルの読み込み完了後に、GUI上の「PandasGUI」もしくは「pandas-profiling」のボタンをクリックすることで各機能を呼び出します。 pandas-profilingでは、プロファイリングが完了するとexeファイルと同じディレクトリにプロファイリング結果がHTMLファイルで出力されます。このファイルをクリックするとブラウザ上で内容を確認することができます。 ※Windowsセキュリティにより、exeファイル実行時に「PCはWindowsによって保護されました」という青いポップアップが表示される場合は、ポップアップ内の「詳細情報」をクリックすると「実行する」ボタンが表示→クリックでツールが起動されます。 ツールの留意点 本ツールには以下の留意点があります。 ファイル容量が大きい(およそ280MB) ツール機能に必要なPythonの各種ライブラリが同封されており、それらのライブラリ容量がツール全体の容量に影響しています。Anaconda(約5GB)をインストールするよりかは小さいほうだと思って許してください。 ツールの起動が遅い(およそ30秒) exe化に使用したPyinstallerの影響ではありますが、exeファイルの起動が遅く、起動に30秒程度かかります(スペックの低いPCだとさらにもう少しかかると思います)。 pandas-profiling後にツールが閉じる 現状版だとpandas-profilingでreportファイルの書き出しが完了するとツール自体が終了してしまいます。今後改善します。 ダウンロード ツールはGitHubよりダウンロードできます。Assets内の「pandas_anywhere_xxx.exe」をクリックすることでダウンロードが開始します(テスト読み込み用にTitanicの乗客データも置いておきます)。 使用範囲・免責事項 使用範囲 個人・法人を問わず誰でも無償で使用可能 ライセンス GNU GENERAL PUBLIC LICENSE Version 3 (ツールに同封しているPandasGUI、pandas-profiling等のサードパーティライブラリの著作権については各ライブラリの作成者に帰属します。また、ライセンスに関しても各ライブラリのそれに従います。) 免責事項 本ツールの使用により発生したいかなる損害についても作成者は一切の責任を負いません。 参考:実装コード import pandas as pd import tkinter as tk import tkinter.ttk as ttk import tkinter.filedialog as filedialog from tkinter import messagebox import os import re import datetime import pandas_profiling as pdp from multiprocessing import Process, freeze_support from pandasgui import show now = datetime.datetime.now() f = 0 #ファイルが読み込まれたかのフラグ(0:未読み込み 1:読み込み済み) #アプリケーション本体 class SearchWindow(tk.Frame): def __init__(self, master=None, parent=None): super().__init__(master) self.master = master self.master.geometry("500x240") #self.master.resizable(width=0, height=0) #windowサイズを固定 self.master.title("Pandas Anywhere Ver1.0.0") self.pack() self.filePath = tk.StringVar() self.create_widgets() def create_widgets(self): self.pw_main = ttk.PanedWindow(self.master, orient="vertical") self.pw_main.pack(expand=True, fill=tk.BOTH, side="left") self.pw_top = ttk.PanedWindow(self.pw_main, orient="vertical", height=40) self.pw_main.add(self.pw_top) self.pw_middle1 = ttk.PanedWindow(self.pw_main, orient="vertical", height=50) self.pw_main.add(self.pw_middle1) self.pw_middle2 = ttk.PanedWindow(self.pw_main, orient="vertical", height=50) self.pw_main.add(self.pw_middle2) self.pw_bottom = ttk.PanedWindow(self.pw_main, orient="vertical", height=50) self.pw_main.add(self.pw_bottom) self.create_input_frame(self.pw_top) self.create_input_frame2(self.pw_middle1) self.create_input_frame3(self.pw_middle2) self.create_input_frame4(self.pw_bottom) def create_input_frame(self, parent): fm_input = ttk.Frame(parent, ) parent.add(fm_input) self.rb_var = tk.StringVar() self.rb_var.set('cp932') lbl_keyword = ttk.Label(fm_input, text="■ Select encoding format :", width=25) lbl_keyword.grid(row=0,column=0, padx=5, pady=10) self.rb1 = ttk.Radiobutton(fm_input, text="cp932",variable=self.rb_var,value='cp932') self.rb1.grid(row=0,column=1, padx=5, pady=10,sticky=tk.W) self.rb2 = ttk.Radiobutton(fm_input, text="utf-8",variable=self.rb_var,value='utf-8') self.rb2.grid(row=0,column=2, padx=5, pady=10,sticky=tk.W) def create_input_frame2(self, parent): fm_input = ttk.Frame(parent, ) parent.add(fm_input) vcmd1 = (self.master.register(self.validate_text), '%P','%S','%W') #入力値検証の変数 self.entry = tk.IntVar() self.entry.set(1) lbl_keyword2 = ttk.Label(fm_input, text="■ Line number to start reading :", width=25) lbl_keyword2.grid(row=0,column=0, padx=5, pady=10,sticky=tk.W) self.ent_keyword = ttk.Entry(fm_input, justify="left", textvariable=self.entry,width=10, validate='key', validatecommand=vcmd1) self.ent_keyword.grid(row=0, column=1, padx=5, pady=10,sticky=tk.W) def create_input_frame3(self, parent): fm_input = ttk.Frame(parent, ) parent.add(fm_input) filepathEntry = ttk.Entry(fm_input,textvariable=self.filePath,width=40) filepathEntry.grid(row=0,column=0, padx=10, pady=10) filepathButton = ttk.Button(fm_input,text=" Load File ",command=self.openFileDialog) filepathButton.grid(row=0,column=1, padx=5, pady=10) def create_input_frame4(self, parent): fm_input = ttk.Frame(parent, ) parent.add(fm_input) style = ttk.Style() style.configure("blue.TButton",background='SteelBlue1') outputButton = ttk.Button(fm_input,text="PandasGUI",style="blue.TButton",command=self.show_gui) outputButton.grid(row=1,column=1, padx=10, pady=10) outputButton2 = ttk.Button(fm_input,text=" Pandas-Profilng ",style="blue.TButton",command=self.profiling) outputButton2.grid(row=1,column=2, padx=5, pady=10) def set_data(self): self.enc = self.rb_var.get() self.skiprows = int(self.ent_keyword.get()) - 1 print('Encoding:{}'.format(self.enc)) print('Line number to start reading:{}'.format(self.skiprows+1)) def openFileDialog(self): #ファイルを読み込む """ ファイルダイアログを開く """ self.set_data() #エンコード形式、読み込み開始行を呼び出し file = filedialog.askopenfilename(filetypes=[("csv", "*.csv"),("txt","*.txt")]); self.file = file self.filePath.set(file) if file is None: messagebox.showerror("load_file","File loading failure") else: if self.file[-3:] == 'csv': df = pd.read_csv(file, encoding=self.enc,skiprows=self.skiprows) #df = pd.read_csv(file, encoding='cp932') else: df = pd.read_table(file, encoding=self.enc,skiprows=self.skiprows) #df = pd.read_table(file, encoding='cp932') self.data = df global f f = 1 #ファイル読み込みフラグ messagebox.showinfo("Load_File","File is loaded") #PnadasGUI def show_gui(self): global f if f == 0: #ファイルが読み込まれていない場合 messagebox.showerror("Warning","File is not loading") else: gui = show(self.data) #pandas-profiling def profiling(self): global f if f == 0: #ファイルが読み込まれていない場合 messagebox.showerror("Warning","File is not loading") else: profile = pdp.ProfileReport(self.data) profile.to_file("profile_report" + now.strftime('_%Y%m%d_%H%M') + ".html") messagebox.showinfo("Pandas-Profilng","End of profiling") def validate_text(self, after, newtext, widget): #入力バリデーションの設定 # 正規表現で入力された値が半角数字であるか判定 if re.match(re.compile('[0-9]+'), newtext): # 入力された数字が1以上の場合にのみTrueを返し、数字が入力出来る if len(after) <= 2: if after != '0': return True return False # 該当しなかった場合にはFalseが返され、値はentryに反映されない else: return False def main(): root = tk.Tk() app = SearchWindow(master=root) app.mainloop() if __name__ == "__main__": freeze_support() main() 最後に 本ツールに関してご質問・ご要望があればご連絡下さい(バグもあればご報告いただけると助かります)。 機会があれば本ツールにPyCaret等も組み込んで、EDAから機械学習までをスタンドアロンでできる自作AutoML的なアプリケーションも作ってみたいです(また、Pyinstallerのエラー処理が半端なさそうですが)。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonライブラリStreamlitを用いて顔検出アプリの作成2

概要 こちらの記事で作成したアプリのAzureのFace APIの使い方が現在は変わっているようなので、現在の使い方に合わせて変更しました。 公式ドキュメント ソース ソース全文です。 GithubにUPしています。 main.py import io import streamlit as st from PIL import Image from PIL import ImageDraw from PIL import ImageFont from azure.cognitiveservices.vision.face import FaceClient from msrest.authentication import CognitiveServicesCredentials from azure.cognitiveservices.vision.face.models import TrainingStatusType, Person st.title('顔認証アプリ') subscription_key = '' # AzureのAPIキー endpoint = '' # AzureのAPIエンドポイント # クライアントを認証する face_client = FaceClient(endpoint, CognitiveServicesCredentials(subscription_key)) # 検出した顔に描く長方形の座標を取得 def get_rectangle(faceDictionary): rect = faceDictionary.face_rectangle left = rect.left top = rect.top right = left + rect.width bottom = top + rect.height return ((left, top), (right, bottom)) # 描画するテキストを取得 def get_draw_text(faceDictionary): rect = faceDictionary.face_rectangle age = int(faceDictionary.face_attributes.age) gender = faceDictionary.face_attributes.gender text = f'{gender} {age}' # 枠に合わせてフォントサイズを調整 font_size = max(16, int(rect.width / len(text))) font = ImageFont.truetype(r'C:\windows\fonts\meiryo.ttc', font_size) return (text, font) # 認識された顔の上にテキストを描く座標を取得 def get_text_rectangle(faceDictionary, text, font): rect = faceDictionary.face_rectangle text_width, text_height = font.getsize(text) left = rect.left + rect.width / 2 - text_width / 2 top = rect.top - text_height - 1 return (left, top) # テキストを描画 def draw_text(faceDictionary): text, font = get_draw_text(faceDictionary) text_rect = get_text_rectangle(faceDictionary, text, font) draw.text(text_rect, text, align='center', font=font, fill='red') uploaded_file = st.file_uploader("Choose an image...", type="jpg") if uploaded_file is not None: img = Image.open(uploaded_file) stream = io.BytesIO(uploaded_file.getvalue()) detected_faces = face_client.face.detect_with_stream( stream, return_face_attributes=['age', 'gender']) if not detected_faces: raise Exception('画像から顔を検出できませんでした。') img = Image.open(uploaded_file) draw = ImageDraw.Draw(img) for face in detected_faces: draw.rectangle(get_rectangle(face), outline='green', width=3) draw_text(face) st.image(img, caption='Uploaded Image.', use_column_width=True) 全体の基本的な流れは前回と同じです。 大きく変わった部分を説明します。 クライアントの認証 使用する前にクライアントの認証を行います。 FaceClientのインスタンスを作成します。 face_client = FaceClient(endpoint, CognitiveServicesCredentials(subscription_key)) 顔の検出結果取得 detect_with_streamを使用して結果を取得します。 受け渡すデータがバイトではなく、画像ストリームに変わっているようなので、io.BytesIOを使用して作成したデータを引数に渡しています。 stream = io.BytesIO(uploaded_file.getvalue()) detected_faces = face_client.face.detect_with_stream(stream, return_face_attributes=['age', 'gender']) 実行結果 前回と同じ結果になったので、旨くいっているようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode 1000000本ノック【104. Maximum Depth of Binary Tree】

目次 No. 項目 1 概要 2 問題 3 解法 4 メイキング 概要 ●発端  ・競プロ初心者、コーディング面接でズタボロにされ、  ・最低限のアルゴリズム学習とコーディング力強化を習慣化するため記事化 ●環境  ・LeetCodeブラウザ上で実装(vscodeに拡張機能にするかもシレナイ)  ・言語はpython3 ●その他ルール  ・google検索は自由に(直接的なLeetCodeの問題解説ページはNG)   (60分実装して実装出来なければ解説ページ参照)  ・コーディング面接対策のために解きたいLeetCode 60問から問題選出  ・「1000000」は任意の2進数とする 問題 104. Maximum Depth of Binary Tree Given the root of a binary tree, return its maximum depth. A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. ⇨「バイナリツリーの深度を返してね。深度ってのはルートノードから  リーフノードまでの最長距離パスのノード数だよ」と書いてます。知らんけど。 ex1 Input: root = [3,9,20,null,null,15,7] Output: 3 ex2 Input: root = [1,null,2] Output: 2 ※要は「一番深いとこまでのノード数」なので  BTの概念さえ持っていれば要件自体はシンプル。 解法 実施時間:60分オーバー # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def maxDepth(self, root: TreeNode) -> int: if not root: return 0 return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 メイキング #IF rootあるよ! #深度+1 #leftあるよ! #深度+1 #leftあるよ! #... #Rightあるよ #... #Rightあるよ! #... いつも通り処理の仮コーディング。 お、これは得意な再帰処理のパターンですね(血涙) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def maxDepth(self, root: TreeNode) -> int: depth = 0 left_depth = 0 right_depth = 0 def branch_depth_check(root,left_depth,right_depth): #左枝があるならleft_depth+1 if root.left: left_depth += 1 branch_depth_check(root.left,left_depth,right_depth) #右枝があるならright_depth+1 if root.right: right_depth += 1 branch_depth_check(root.right,left_depth,right_depth) return max(left_depth,right_depth) #rootあるなら深度+1 if root: #左右どちらかあるならとりあえず深度+1 if root.left or root.right: left_depth += 1 right_depth += 1 depth += 1 #左右確認 depth = branch_depth_check(root,left_depth,right_depth) return depth 時間オーバー時のコードはこちら、テストケースに合わせて修正を繰り返していたら いつの間にか無限モグラ叩きパターンに入ってしまっていて、正直心折れてました。 そしてこちら参照し速攻解決。 - # LeetCode Easy 104. Maximum Depth of Binary Tree こんなシンプルになるのか。。。 深さ優先探索の考え自体はあっていましたね。再帰処理も使ってますし(必死) あぁ、再帰処理に苦手意識が芽生えそう。。。 下記のようなイメージ自体は付いているけども、脳のワークスペースが足りず シミュレーションしきれないので、図解書きながらとかするしか無いのかなぁ。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonライブラリStreamlitを用いて顔検出アプリの作成

概要 Youtubeの動画を参考にStreamlitとMicrosoft AzureのFace APIを使用してWebアプリを作成しました。 動画では顔の部分に枠線を描画するところまででしたが、追加で性別と年齢を描画するようにしています。 ソース ソース全文です。 GithubにUPしています。 main.py import io import requests import streamlit as st from PIL import Image from PIL import ImageDraw from PIL import ImageFont st.title('顔認証アプリ') subscription_key = '' # AzureのAPIキー endpoint = '' # AzureのAPIエンドポイント face_api_url = endpoint + 'face/v1.0/detect' headers = { 'Content-Type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': subscription_key } params = { 'returnFaceId': 'true', 'returnFaceAttributes': 'age,gender', } # 検出した顔に描く長方形の座標を取得 def get_rectangle(faceDictionary): rect = faceDictionary['faceRectangle'] left = rect['left'] top = rect['top'] right = left + rect['width'] bottom = top + rect['height'] return ((left, top), (right, bottom)) # 描画するテキストを取得 def get_draw_text(faceDictionary): rect = faceDictionary['faceRectangle'] faceAttr = faceDictionary['faceAttributes'] age = int(faceAttr['age']) gender = faceAttr['gender'] text = f'{gender} {age}' # 枠に合わせてフォントサイズを調整 font_size = max(16, int(rect['width'] / len(text))) font = ImageFont.truetype(r'C:\windows\fonts\meiryo.ttc', font_size) return (text, font) # 認識された顔の上にテキストを描く座標を取得 def get_text_rectangle(faceDictionary, text, font): rect = faceDictionary['faceRectangle'] text_width, text_height = font.getsize(text) left = rect['left'] + rect['width'] / 2 - text_width / 2 top = rect['top'] - text_height - 1 return (left, top) # テキストを描画 def draw_text(faceDictionary): text, font = get_draw_text(faceDictionary) text_rect = get_text_rectangle(faceDictionary, text, font) draw.text(text_rect, text, align='center', font=font, fill='red') uploaded_file = st.file_uploader("Choose an image...", type="jpg") if uploaded_file is not None: img = Image.open(uploaded_file) with io.BytesIO() as output: img.save(output, format='JPEG') binary_img = output.getvalue() res = requests.post(face_api_url, params=params, headers=headers, data=binary_img) results = res.json() if not results: raise Exception('画像から顔を検出できませんでした。') for result in results: draw = ImageDraw.Draw(img) draw.rectangle(get_rectangle(result), outline='green', width=3) draw_text(result) st.image(img, caption='Uploaded Image.', use_column_width=True) 基本的な流れは動画を見てもらうとして、性別と年齢を描画する部分を説明します。 描画するテキストを取得 Face APIの戻り値から性別と年齢をを取得して描画するテキストを取得しています。 それから、枠のサイズに合わせてフォントサイズを変更しています。 フォントサイズが小さいと見えないので、16以上になるように16と算出したサイズを比較して、値の大きい方を選択しています。 def get_draw_text(faceDictionary): rect = faceDictionary['faceRectangle'] faceAttr = faceDictionary['faceAttributes'] age = int(faceAttr['age']) gender = faceAttr['gender'] text = f'{gender} {age}' # 枠に合わせてフォントサイズを調整 font_size = max(16, int(rect['width'] / len(text))) font = ImageFont.truetype(r'C:\windows\fonts\meiryo.ttc', font_size) return (text, font) 認識された顔の上にテキストを描く座標を取得 Face APIの戻り値から枠の位置と、描画するテキストのサイズを使用して、枠の上部に中央揃えになる座標を計算しています。 # 認識された顔の上にテキストを描く座標を取得 def get_text_rectangle(faceDictionary, text, font): rect = faceDictionary['faceRectangle'] text_width, text_height = font.getsize(text) left = rect['left'] + rect['width'] / 2 - text_width / 2 top = rect['top'] - text_height - 1 return (left, top) テキストを描画 テキストを画像に描画しています。 # テキストを描画 def draw_text(faceDictionary): text, font = get_draw_text(faceDictionary) text_rect = get_text_rectangle(faceDictionary, text, font) draw.text(text_rect, text, align='center', font=font, fill='red') 実行結果 枠線の上に性別と年齢が描画されました。 画像はphotoACのフリー素材を使用しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】関数の中に関数、引数がないやんけ~クロージャとの出会い~

未だに初心者抜け出せないウーマンです。 久々になんじゃこりゃとなった書き方に出会ったので備忘録として記事にします。 関数の中に関数、引数がないやんけ。。。 こんな書き方してるコードに出会いました。 def function_a(x): def function_b(y,z): ー中略ー return b_return return function_b function_aの返り値がfunction_bやけど、function_bのところに引数ないやん???? これは何??????? 無論、function_bという定数がほかのところにあるわけではありません。 そんなわけで調べました。 「python 関数の中に関数 引数 ない」 みたいなかんじで。 当然そんな感じじゃ出てこなかったんですけど、答えは見つかりました。 ありがとう、グーグル先生。 クロージャというらしい こういう書き方するやつのことを、「クロージャ」というらしいということがわかりました。 調べを進めていきます。 こういうときはウィキペディアの解説が一番わかりやすかったりするので参照します https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3 が、 今回はなにやら難しいことがいっぱい書いてあります。 その他いろいろ読みましたがjavascript用の解説が多いように感じます。 みんな引っかかるようなものなのかな? ざっくり以下のようなものだと理解しました 関数の中に関数を書く 状態を保持できる関数 まだまだ理解が追い付いていないので、pythonではどうなんか、ってことを見つつ勉強していきます。 pythonにおけるクロージャーの使い方って 勉強させていただいたのはこのページです。 ほんとわかりやすいので私のつたない文書読む前にまず読んでほしい。 https://pg-chain.com/python-closure 初めのコードを具体化してみていきます。 def function_a(x): def function_b(y,z): b_return = x + y + z return b_return return function_b 単純に足し算して値を出してくれるような関数です これを使う時どうするか、という話ですが、 まずはfunction_aを普通に使う時みたいに書いてみます。 closure = function_a(10) これだけ見ると定数closureにfunction_aの答えを入れたように見えるのですが クロージャの場合は違ってきます。 このclosureは「クロージャ変数」というものになっている、とのことです。 だってyとzの値まだわかってないし、答えだせないですよね。 このときclosureはx=10という状態を保持したクロージャ変数になっています。 そしてここからどうするか。 ans = closure(1,2) print(ans) # ans = 13 ans = closure(2,3) print(ans) #ans = 15 クロージャ変数closureに引数を渡して実行しちゃう! なんじゃそりゃ! でも、そう使うんだそうです。 この時closure(1,2)を実行するとfunction_b(1,2)を実行したのと同じこととなって x+y+zは10+1+2となり答えは13となります。 また、 x = 10という値は保持された状態になっているのでもう一度closureを呼び出し、 ans = closure(2,3)とすると答えは15となるんですねえ。 便利!なのかな? 使い方はわかった! 状態を保持して置けること、 実行したいタイミングをずらせること なんかが嬉しかったりするのかな。 終わりに 私と同じように 「python 関数の中に関数 引数 ない」 と検索した人がこの記事にあたって 偉大なる参考ページ様で理解を深められることを祈っております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python で実装する動的計画法(DP)

概要 本稿では AtCoder の DP まとめコンテスト で公開されている練習問題を用いて、Python による動的計画法(Dynamic Programming:DP) の実装方法を紹介する。C++ を用いた実装については以下の記事が非常にわかりやすい。 動的計画法超入門! Educational DP Contest の A ~ E 問題の解説と類題集 典型的な DP (動的計画法) のパターンを整理 Part 1 ~ ナップサック DP 編 ~ 動的計画法(DP) まず DP の流れを以下に説明する。 0) 与えられた問題を $N$ 回の繰り返し問題に分割する。 1) 1 回目の最適解を求める。 2) 1 回目の最適解をもとに 2 回目の最適解を求める 3) 2 回目までの最適解をもとに 3 回目の最適解を求める 4) 3 回目までの最適解をもとに 3 回目の最適解を求める (中略) n) n-1 回目まで最適解をもとに n 回目の最適解を求める このように、繰り返される部分問題の最適解を、それより前の問題の最適解をもとに求めるアルゴリズムが DP である。DP は漸化式を用いると実装しやすい。 実装の流れ 本稿では、以下の流れで DP を実装する。 1) DP 配列を用意する 2) 初期条件を入力する 3) 漸化式にしたがって DP を実装する 4) DB 配列の末尾を出力させる A 問題 問題概要 番号 $ 1 $ 〜 $ N $ の足場がある。足場 $ i (1 ≦ i ≦ N) $の高さは $ h[i] $ である。足場 $ i $ から 足場 $ j $ への移動では $ | h[i] - h[j] | $ のコストがかかる。足場 $ i $ へ移動できるのは、足場 $ i-1 $ と $ i-2 $ のみである。足場 $ 1 $ から足場 $ N $ への移動に必要な最小コストを求めよ。 制約 $ 2 ≦ N ≦ 10^5 $ $ 1 ≦ h[i] ≦ 10^4 $ 漸化式 足場 $ 1 $ から $ i $ への移動に必要な最低コストを $ dp[i] $ とおく。足場 $ i $ へ移動する方法は、足場 $ i-1 $ から移動する方法と足場 $ i-2 $ から移動する方法がある。DP では、直前の足場に移動するまでの最適解がわかっていることを前提に漸化式を立てる。すなわち、足場 $ i-1 $ から移動するコストと足場 $ i-2 $ から移動コストを比べて、低いほうが $ dp[i] $ となる。 $$ dp[i] = \begin{cases} 0 & (i = 1) \\ h[2] - h[1] & (i = 2) \\ min (dp[i-2]+|h[i]-h[i-2]|,dp[i-1]+|h[i]-h[i-1]|) & (i ≧ 3) \ \end{cases} $$ 実装 実装例 # 入力読み込み N=int(input()) h=list(map(int,input().split())) # DP 配列を用意 # dp[i] には i 番目の足場にたどり着くために必要な最低コストを入れる dp = [0]*N # 初期条件を入力 dp[0] = 0 dp[1] = abs(h[1]-h[0]) # 漸化式にしたがって DP を実装する for i in range(2,N): # i を現在いる足場と考える。 # i 番目の足場へ行く方法として i-i 番目からのジャンプと i-2 番目からのジャンプがある # 2 通りの行き方のうちコストの少ない方を dp[i] とする dp[i] = min (dp[i-2]+abs(h[i]-h[i-2]),dp[i-1]+abs(h[i]-h[i-1])) # dp 配列の末尾が N 番目の足場にたどり着くために必要なコストとなる print(dp[-1]) B 問題 問題概要 番号 $ 1 $ 〜 $ N $ の足場がある。足場 $ i (1 ≦ i ≦ N) $の高さは $ h[i] $ である。足場 $ i $ から 足場 $ j $ への移動では $ | h[i] - h[j] | $ のコストがかかる。足場 $ i $ へ移動できる足場は、$ i-1 $ 、 $ i-2 $ 、$ ... $、 $ i-K $ の $ K $ 個である。足場 $ 1 $ から足場 $ N $ への移動に必要な最小コストを求めよ。 制約 $ 2 ≦ N ≦ 10^5 $ $ 1 ≦ K ≦ 10^2 $ $ 1 ≦ h[i] ≦ 10^4 $ 実装 A 問題と同様に $ dp[i] $ をおく。 A 問題では $ dp[i] $ へ移動する方法が 2 通りしかなかったが、今回は $ K $ 通りある。$ K $ 通りの経路のうち最小コストが $dp[i]$ となる。 実装例 # 入力読み込み N,k = map(int,input().split()) h = list(map(int, input().split())) # DP 配列を用意 # dp[i] には i 番目の足場にたどり着くために必要な最低コストを入れる dp = [0] * N # 初期条件を入力 dp[0] = 0 # 漸化式にしたがってループを回す for i in range(1,N): # i を現在いる足場と考える。 # i 番目の足場へ行く方法は max(k,i-k) 通り ある。 # それぞれの行き方にかかるコストを tmp (tmporary) にまとめる。 tmp = [] for m in range(max(0, i-k), i): tmp.append(abs(h[m]-h[i])+dp[m]) # tmp のうち最小コストを dp[i] とする dp[i] = min(tmp) # dp 配列の末尾が N 番目の足場にたどり着くために必要なコストとなる print(dp[-1]) C 問題 問題概要 太郎くんは $ N $ 日間の夏休みを過ごす。$ i (1 ≦ i ≦ N)$ 日目の太郎くんは行動 $ A $、$ B $、$ C $ のどれかひとつをとることで、幸福度 $ a_i $、$ b_i $、$ c_i $ をそれぞれ得る。太郎くんが夏休みで得られる最大幸福度を求めよ。ただし太郎くんは二日連続で同じ行動は取らない。 制約 $ 1 ≦ N ≦ 10^5 $ $ 1 ≦ a_i,b_i,c_i ≦ 10^4 $ 漸化式 $ i $ 日目に行動 $ A $、$ B $、$ C $ をして $ i $ 日目までに得られる最大幸福度をそれぞれ $ dp[i][0] $、$ dp[i][1] $、$ dp[i][2] $ とおく。二日連続で同じ行動を取らないことから $ dp $ の漸化式は以下のようになる。 $$ \begin{cases} dp[i][0]=max(dp[i-1][1]+a_i,dp[i-1][2]+a_i) \\ dp[i][1]=max(dp[i-1][0]+b_i,dp[i-1][2]+b_i) \\\ dp[i][2]=max(dp[i-1][0]+c_i,dp[i-1][1]+c_i) \end{cases} $$ 初期条件として $ 0 $ 日目の幸福度を $0$ とおく。すなわち $ dp[1][0] = dp[1][1] = dp[2][1] = 0 $ である。 実装 実装例 # DP 配列を用意する # 本問では i 日目の動作として「Aをやる」「Bをやる」「Cをやる」の3通りが考えられる # それぞれの行動をするための最大幸福度をそれぞれの漸化式で求める # i 日目に A、B、C をやるための最大幸福度は dp[i][0]、dp[i][1][1]、dp[i][2] とする N=int(input()) dp=[[0]*3 for _ in range(n+1)] # 漸化式にしたがってループを回す for i in range(1,N+1): a,b,c=map(int,input().split()) dp[i][0]=max(dp[i-1][1]+a,dp[i-1][2]+a) dp[i][1]=max(dp[i-1][0]+b,dp[i-1][2]+b) dp[i][2]=max(dp[i-1][0]+c,dp[i-1][1]+c) # 最終日に最大幸福度が得られる行動をしたときの幸福度を求める print(max(dp[-1])) D 問題(ナップサック問題1) 問題概要 $ N $ 個の品物がある。品物 $ i $ は重さ $ w[i] $、価値 $ v[i] $ である。重さの総和が $ W $ 以下になるように品物を選ぶとき、選んだ品物の価値の総和の最大値を求めよ。 制約 $ N ≤ 10^2 $ $ W ≤ 10^5 $ $ v[i] ≤ 10^9 $ 漸化式 $ i $ 番目までの品物を重さ $ j $ 以下で選ぶ。選ばれる品物の価値の最大値を $ dp[i][j] $ とする。ここでは価値の総和が大きくなるように品物を選ぶ。$ i + 1 $ 番目の品物 (重さ: $ w $ 、価値: $ v $ )が選ばれるのは、$ i $ 番目までの品物で重さ $ w $ を使って稼げる価値よりも $ i + 1 $ 番目の品物で稼げる価値 $v$ のほうが大きい場合である。すなわち、以下の不等式が満たされる場合に $ i + 1 $ 番目の品物を選ぶ。 $$ dp[i+1][j+w] ≤ dp[i][j]+v $$ この不等式に基づき、以下の漸化式が立てられる。 $$ dp[i+1][j+w] = max(dp[i][j]+v,dp[i][j+w]) $$ 実装 実装例 # 入力読み込み N,W = map(int,input().split()) # DP 配列用意 # i 番目までの品物を重さ j 以下で選ぶ場合、品物の総和の価値の最大値を dp[i][j] とする dp = [[0]*(W+1) for _ in range(N+1)] # 漸化式にしたがって DP を実施する # i+1 番目の品物(重さ:w、価値:v)を選ぶのは # i 番目までの品物で重さ w を使って稼げる価値より i+1 番目の品物で稼げる価値 v のほうが大きい場合である for i in range(N): dp[i+1] = dp[i].copy() #.copy() は必要 w,v=map(int,input().split()) for j in range(W+1-w): dp[i+1][j+w] = max(dp[i][j]+v,dp[i][j+w]) # dp 配列の末尾が N 番目までの品物から重さ W 以下で選ぶ場合の品物の価値の最大値となる。 print(dp[-1][-1]) E 問題(ナップサック問題2) 問題概要 $ N $ 個の品物がある。品物 $ i $ は重さ $ w[i] $、価値 $ v[i] $ である。重さの総和が $ W $ 以下になるように品物を選ぶとき、選んだ品物の価値の総和の最大値を求めよ。(D問題と同じ) 制約 $ N ≤ 10^2 $ $ W ≤ 10^9 $ $ v[i] ≤ 10^3 $ 漸化式 D 問題と同じ漸化式で実装すると、DP 配列の長さが最大で $10^9$ になるため計算時間が膨大にかかる。計算時間短縮のために別の漸化式を立てる。 $ i $ 番目までの品物を価値 $ j $ で選ぶ。選ばれる品物の重さの総和の最大値を $ dp[i][j]$ とする。全品物の価値の総和は $ N × v[i] ≤ 10^5 $ であるから、dp 配列の長さは最大で $10^5$ となる。したがって $ dp[N][j]$ を末尾から調べ、初めて $W$ 以下になる $ j $ を答えとして求めれば制限時間内に計算を終えられる。 ここでは重さの総和が小さくなるように品物を選ぶ。$ i + 1 $ 番目の品物 (重さ: $ w $ 、価値: $ v $ )が選ばれるのは、$ i $ 番目までの品物で価値 $ v $ を節約して減らせる重さが $ w $ 以下の場合である。すなわち、以下の不等式が満たされる場合に $ i + 1 $ 番目の品物を選ぶ。 $$ dp[i][j-v] + w ≤ dp[i+1][j]、 0 ≤ j-v $$ この不等式に基づき、以下の漸化式が立てられる。 $$ dp[i+1][j] = min(dp[i][j], dp[i][j-v] + w) $$ 実装 実装例 import math # 入力読み込み N, W = map(int, input().split()) # dp 配列の用意 # i 番目までの品物から価値 j で選べる品物の重さの総和の最大値を dp[i][j] とする # 品物の価値の総和の最大値を L とする。 # 今回は価値 1000 の品物が 100 個用意される可能性を考えて L = 100000 とする # 重さの最小値に無限大を入れて初期化する L = 100000 dp = [[math.inf]*(L+1) for _ in range(N+1)] # 初期条件を入れる。 dp[0][0] = 0 # 漸化式にしたがって DP を実行する # ここでは重さの最大値が小さくなるように品物を選ぶ。 for i in range(N): w, v = map(int, input().split()) for j in range(L+1): # i+1 番目の品物 (重さ:w、価値:v)が選ばれるのは # i 番目までの品物で価値 v を節約して減らせる重さが w 以下のとき if j - v >= 0: dp[i+1][j] = min(dp[i][j], dp[i][j-v] + w) else: dp[i+1][j] = dp[i][j] # dp[N][j] を先頭から調べたとき、最後に $W$ 以下になる $ j $ を答えとする。 for i, d in enumerate(dp[N]): if d <= W: maxv = i print(maxv)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】漸化式で実装する動的計画法(DP)

概要 本稿では AtCoder の DP まとめコンテスト で公開されている練習問題を用いて、Python による動的計画法(Dynamic Programming:DP) の実装方法を紹介する。C++ を用いた実装については以下の記事が非常にわかりやすい。 動的計画法超入門! Educational DP Contest の A ~ E 問題の解説と類題集 典型的な DP (動的計画法) のパターンを整理 Part 1 ~ ナップサック DP 編 ~ 動的計画法(DP) まず DP の流れを以下に説明する。 0) 与えられた問題を $N$ 回の繰り返し問題に分割する。 1) 1 回目の最適解を求める。 2) 1 回目の最適解をもとに 2 回目の最適解を求める 3) 2 回目までの最適解をもとに 3 回目の最適解を求める 4) 3 回目までの最適解をもとに 4 回目の最適解を求める (中略) n) n-1 回目まで最適解をもとに n 回目の最適解を求める このように、繰り返される部分問題の最適解を、それより前の問題の最適解をもとに求めるアルゴリズムが DP である。DP は漸化式を用いると実装しやすい。 実装の流れ 本稿では、以下の流れで DP を実装する。 1) DP 配列を用意する 2) 初期条件を入力する 3) 漸化式にしたがって DP を実装する 4) DB 配列の末尾を出力させる A 問題 問題概要 番号 $ 1 $ 〜 $ N $ の足場がある。足場 $ i (1 ≦ i ≦ N) $の高さは $ h[i] $ である。足場 $ i $ から 足場 $ j $ への移動では $ | h[i] - h[j] | $ のコストがかかる。足場 $ i $ へ移動できるのは、足場 $ i-1 $ と $ i-2 $ のみである。足場 $ 1 $ から足場 $ N $ への移動に必要な最小コストを求めよ。 制約 $ 2 ≦ N ≦ 10^5 $ $ 1 ≦ h[i] ≦ 10^4 $ 漸化式 足場 $ 1 $ から $ i $ への移動に必要な最低コストを $ dp[i] $ とおく。足場 $ i $ へ移動する方法は、足場 $ i-1 $ から移動する方法と足場 $ i-2 $ から移動する方法がある。DP では、直前の足場に移動するまでの最適解がわかっていることを前提に漸化式を立てる。すなわち、足場 $ i-1 $ から移動するコストと足場 $ i-2 $ から移動コストを比べて、低いほうが $ dp[i] $ となる。 $$ dp[i] = \begin{cases} 0 & (i = 1) \\ h[2] - h[1] & (i = 2) \\ min (dp[i-2]+|h[i]-h[i-2]|,dp[i-1]+|h[i]-h[i-1]|) & (i ≧ 3) \ \end{cases} $$ 実装 実装例 # 入力読み込み N = int(input()) h = list(map(int,input().split())) # DP 配列を用意 # dp[i] には i 番目の足場にたどり着くために必要な最低コストを入れる dp = [0]*N # 初期条件を入力 dp[0] = 0 dp[1] = abs(h[1]-h[0]) # 漸化式にしたがって DP を実装する for i in range(2,N): # i を現在いる足場と考える。 # i 番目の足場へ行く方法として i-i 番目からのジャンプと i-2 番目からのジャンプがある # 2 通りの行き方のうちコストの少ない方を dp[i] とする dp[i] = min (dp[i-2]+abs(h[i]-h[i-2]),dp[i-1]+abs(h[i]-h[i-1])) # dp 配列の末尾が N 番目の足場にたどり着くために必要なコストとなる print(dp[-1]) B 問題 問題概要 番号 $ 1 $ 〜 $ N $ の足場がある。足場 $ i (1 ≦ i ≦ N) $の高さは $ h[i] $ である。足場 $ i $ から 足場 $ j $ への移動では $ | h[i] - h[j] | $ のコストがかかる。足場 $ i $ へ移動できる足場は、$ i-1 $ 、 $ i-2 $ 、$ ... $、 $ i-K $ の $ K $ 個である。足場 $ 1 $ から足場 $ N $ への移動に必要な最小コストを求めよ。 制約 $ 2 ≦ N ≦ 10^5 $ $ 1 ≦ K ≦ 10^2 $ $ 1 ≦ h[i] ≦ 10^4 $ 実装 A 問題と同様に $ dp[i] $ をおく。 A 問題では $ dp[i] $ へ移動する方法が 2 通りしかなかったが、今回は $ K $ 通りある。$ K $ 通りの経路のうち最小コストが $dp[i]$ となる。 実装例 # 入力読み込み N,k = map(int,input().split()) h = list(map(int, input().split())) # DP 配列を用意 # dp[i] には i 番目の足場にたどり着くために必要な最低コストを入れる dp = [0] * N # 初期条件を入力 dp[0] = 0 # 漸化式にしたがってループを回す for i in range(1,N): # i を現在いる足場と考える。 # i 番目の足場へ行く方法は max(k,i-k) 通り ある。 # それぞれの行き方にかかるコストを tmp (tmporary) にまとめる。 tmp = [] for m in range(max(0, i-k), i): tmp.append(abs(h[m]-h[i])+dp[m]) # tmp のうち最小コストを dp[i] とする dp[i] = min(tmp) # dp 配列の末尾が N 番目の足場にたどり着くために必要なコストとなる print(dp[-1]) C 問題 問題概要 太郎くんは $ N $ 日間の夏休みを過ごす。$ i (1 ≦ i ≦ N)$ 日目の太郎くんは行動 $ A $、$ B $、$ C $ のどれかひとつをとることで、幸福度 $ a_i $、$ b_i $、$ c_i $ をそれぞれ得る。太郎くんが夏休みで得られる最大幸福度を求めよ。ただし太郎くんは二日連続で同じ行動は取らない。 制約 $ 1 ≦ N ≦ 10^5 $ $ 1 ≦ a_i,b_i,c_i ≦ 10^4 $ 漸化式 $ i $ 日目に行動 $ A $、$ B $、$ C $ をして $ i $ 日目までに得られる最大幸福度をそれぞれ $ dp[i][0] $、$ dp[i][1] $、$ dp[i][2] $ とおく。二日連続で同じ行動を取らないことから $ dp $ の漸化式は以下のようになる。 $$ \begin{cases} dp[i][0]=max(dp[i-1][1]+a_i,dp[i-1][2]+a_i) \\ dp[i][1]=max(dp[i-1][0]+b_i,dp[i-1][2]+b_i) \\ dp[i][2]=max(dp[i-1][0]+c_i,dp[i-1][1]+c_i) \end{cases} $$ 初期条件として $ 0 $ 日目の幸福度を $0$ とおく。すなわち $ dp[0][0] = dp[0][1] = dp[0][2] = 0 $ である。 実装 実装例 # DP 配列を用意する # 本問では i 日目の動作として「Aをやる」「Bをやる」「Cをやる」の3通りが考えられる # それぞれの行動をするための最大幸福度をそれぞれの漸化式で求める # i 日目に A、B、C をやるための最大幸福度は dp[i][0]、dp[i][1][1]、dp[i][2] とする N = int(input()) dp = [[0]*3 for _ in range(n+1)] # 初期条件を入力 dp[0][0] = 0 dp[1][0] = 0 dp[2][0] = 0 # 漸化式にしたがって DP を実行する for i in range(1,N+1): a,b,c = map(int,input().split()) dp[i][0] = max(dp[i-1][1]+a,dp[i-1][2]+a) dp[i][1] = max(dp[i-1][0]+b,dp[i-1][2]+b) dp[i][2] = max(dp[i-1][0]+c,dp[i-1][1]+c) # 最終日までに得られる幸福度の最大値を求める print(max(dp[-1])) D 問題(ナップサック問題1) 問題概要 $ N $ 個の品物がある。品物 $ i $ は重さ $ w[i] $、価値 $ v[i] $ である。重さの総和が $ W $ 以下になるように品物を選ぶとき、選んだ品物の価値の総和の最大値を求めよ。 制約 $ N ≤ 10^2 $ $ W ≤ 10^5 $ $ v[i] ≤ 10^9 $ 漸化式 $ i $ 番目までの品物を重さ $ j $ 以下で選ぶ。選ばれる品物の価値の最大値を $ dp[i][j] $ とする。ここでは価値の総和が大きくなるように品物を選ぶ。$ i + 1 $ 番目の品物 (重さ: $ w $ 、価値: $ v $ )が選ばれるのは、$ i $ 番目までの品物で重さ $ w $ を使って稼げる価値よりも $ i + 1 $ 番目の品物で稼げる価値 $v$ のほうが大きい場合である。すなわち、以下の不等式が満たされる場合に $ i + 1 $ 番目の品物を選ぶ。 $$ dp[i+1][j+w] ≤ dp[i][j]+v $$ この不等式に基づき、以下の漸化式が立てられる。 $$ dp[i+1][j+w] = max(dp[i][j]+v,dp[i][j+w]) $$ 実装 実装例 # 入力読み込み N,W = map(int,input().split()) # DP 配列用意 # i 番目までの品物を重さ j 以下で選ぶ場合、品物の総和の価値の最大値を dp[i][j] とする dp = [[0]*(W+1) for _ in range(N+1)] # 漸化式にしたがって DP を実行する # i+1 番目の品物(重さ:w、価値:v)を選ぶのは # i 番目までの品物で重さ w を使って稼げる価値より i+1 番目の品物で稼げる価値 v のほうが大きい場合である for i in range(N): dp[i+1] = dp[i].copy() #上書きを防ぐために .copy() は必須 w,v = map(int,input().split()) for j in range(W+1-w): dp[i+1][j+w] = max(dp[i][j]+v,dp[i][j+w]) # dp 配列の末尾が N 番目までの品物から重さ W 以下で選ぶ場合の品物の価値の最大値となる。 print(dp[-1][-1]) E 問題(ナップサック問題2) 問題概要 $ N $ 個の品物がある。品物 $ i $ は重さ $ w[i] $、価値 $ v[i] $ である。重さの総和が $ W $ 以下になるように品物を選ぶとき、選んだ品物の価値の総和の最大値を求めよ。(D問題と同じ) 制約 $ N ≤ 10^2 $ $ W ≤ 10^9 $ $ v[i] ≤ 10^3 $ 漸化式 D 問題と同じ漸化式で実装すると、DP 配列の長さが最大で $10^9$ になるため計算時間が膨大にかかる。計算時間短縮のために別の漸化式を立てる。 $ i $ 番目までの品物を価値 $ j $ で選ぶ。選ばれる品物の重さの総和の最小値を $ dp[i][j]$ とする。全品物の価値の総和は $ N × v[i] ≤ 10^5 $ であるから、dp 配列の長さは最大で $10^5$ となる。したがって $ dp[N][j]$ を末尾から調べ、初めて $W$ 以下になる $ j $ を答えとして求めれば制限時間内に計算を終えられる。 ここでは重さの総和が小さくなるように品物を選ぶ。$ i + 1 $ 番目の品物 (重さ: $ w $ 、価値: $ v $ )が選ばれるのは、$ i $ 番目までの品物で価値 $ v $ を節約して減らせる重さが $ w $ 以下の場合である。すなわち、以下の不等式が満たされる場合に $ i + 1 $ 番目の品物を選ぶ。 $$ dp[i][j-v] + w ≤ dp[i+1][j]、 0 ≤ j-v $$ この不等式に基づき、以下の漸化式が立てられる。 $$ dp[i+1][j] = min(dp[i][j], dp[i][j-v] + w) $$ 実装 実装例 import math # 入力読み込み N, W = map(int, input().split()) # dp 配列の用意 # i 番目までの品物から価値 j で選べる品物の重さの総和の最小値を dp[i][j] とする # 品物の価値の総和の最大値を L とする。 # 今回は価値 1000 の品物が 100 個用意される可能性を考えて L = 100000 とする # 重さの最小値に無限大を入れて初期化する L = 100000 dp = [[math.inf]*(L+1) for _ in range(N+1)] # 初期条件を入れる dp[0][0] = 0 # 漸化式にしたがって DP を実行する # ここでは重さの最小値が小さくなるように品物を選ぶ for i in range(N): w, v = map(int, input().split()) for j in range(L+1): # i+1 番目の品物 (重さ:w、価値:v)が選ばれるのは # i 番目までの品物で価値 v を節約して減らせる重さが w 以下のとき if j - v >= 0: dp[i+1][j] = min(dp[i][j], dp[i][j-v] + w) else: dp[i+1][j] = dp[i][j] # dp[N][j] を先頭から調べたとき、最後に $W$ 以下になる $ j $ を答えとする for i, d in enumerate(dp[N]): if d <= W: maxv = i print(maxv)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Herokuの本番サーバーで接続できるか確認しよう

前回のつづき PostgreSQLをHerokuでデプロイ Herokuでは無料でPostgreSQLがつかえるそうです 特別な手順はありません。 前回と同様に、 静的ファイルの設定 requirements.txtを作成 Procfileを作成 してHerokuサーバーにアップロードするだけです。 requirements.txtファイルをつくって中身を↓記述 django gunicorn whitenoise dj-database-url python-dotenv psycopg2-binary mysqlclient Procfileファイルには web: gunicorn pj_db.wsgi 静的ファイルの設定 whitenoiseの設定 全体設定ファイルの編集 MIDDLEWAREの最後にwhitenoiseを追加 MIDDLEWARE = [ ... 'whitenoise.middleware.WhiteNoiseMiddleware', ] 最後に以下を追記 STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' そして pip install -r requirements.txt python manage.py collectstatic ランサーバーを立ち上げてローカルで大丈夫か? あと、DEBUGをFlaseに変更 その後は、Githubにアクセスしてリポジトリを作成してターミナルで↓を実行 git init git add --all あとは こちらと同じ!! デプロイできたら、Herokuでmaigreteしてスーパーユーザー登録して挙動確認 本番環境で確認 できた〜!!٩(♡ε♡ )۶
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習エラー対応集

はじめに 機械学習・データ分析のプログラミングで遭遇したエラーをメモしてみました。随時更新予定です。 こういうエラーあるよ!とかこういう対処法の方がいいよ!とかあれば是非コメント下さい。 AttributeError: 'DataFrame' object has no attribute 'as_matrix' # 警告 Python: Method .as_matrix will be removed in a future version. Use .values instead # エラー AttributeError: 'DataFrame' object has no attribute 'as_matrix' 対処法 .asmatrix() を .valuesに 書き換える # 書き換え前 X = df.loc[:, ['temp']].as_matrix() # 書き換え後                 X = df.loc[:, ['temp']].values ValueError: Input contains NaN, infinity or a value too large for dtype('float64'). # エラー ValueError: Input contains NaN, infinity or a value too large for dtype('float64'). 対処法 入力データからNaNや無限大を除去する # X から NaN を含む列を削除する X.drop(X.columns[np.isnan(X).any()], axis=1) ◆参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ分析を始めよう!開発環境構築(Python,Mac)

はじめに データ分析に使用するPythonのライブラリをインストールします。 と言っても"pip3 install ~ "というコマンドを使用すると簡単にできます。 ※Python自体の開発環境構築はこちら https://qiita.com/nisina/items/fd98109d02967f12412e 今回インストールするライブラリ(これがあれば大体なんでもできるはず) Numpy  ・・・配列同士の演算や多次元配列などの扱いに長けたオブジェクトを提供するライブラリ Pandas ・・・PythonでデータをRDBっぽい形(データフレーム)で扱うためのライブラリ。 Scipy  ・・・科学計算・技術計算のためのライブラリ。特殊関数、最適化、統計処理など scikit-learn ・・・分類や予測のためのモデルが詰まった機械学習用パッケージ matplotlib  ・・・データビジュアライズツール。簡単に綺麗なグラフが描ける。 ①既にインストールしているライブラリの確認 # 既にインストールしているライブラリの確認 $ pip list ※ WARNING: You are using pip version~~~~ というメッセージが表示された場合は pipをアップデートしましょう。メッセージに記載されているコマンド (シングルクォート ' で囲われている所)を実行してください。 ②データ分析のためのライブラリをインストール # データ分析で使用するライブラリをまとめて入れる(先ほど確認してなかったものだけでよし) $ pip3 install numpy pandas scipy scikit-learn matplotlib #Pythonを使ってライブラリの動作確認(例:Pandas) $ python3 >>> import pandas as pd ※ ModuleNotfoundError: No module named '_lzma' が出た場合、以下で対応 # Pythonのバージョン確認 $ pyenv versions # インストール済みのパッケージをまとめて再インストールするための前処理 $ pip freeze > latestPackages.txt # Pythonのアンインストール(バージョンは1行目で確認したもの) $ pyenv uninstall 3.7.3 # xzのインストール(エラーの対処) $ brew install xz # Pythonのインストール $ pyenv install 3.7.3 # パッケージの再インストール $ pip install -r latestPackages.txt ◆参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoとデータベースの勉強

Djangoでのデータベースの接続にチャレンジ メジャーな3つのデータベースとの接続を勉強。。。 sqlite3 MySQL PostgreSQL Sqlite3と接続しよう まずは、Sqlite3との接続に挑戦!! 接続設定 実は設定不要! デフォルトでSqlite3と接続するようになっています。 メインのデータベースとしては力不足。 本番で重要なデータの保存先として使われることはまずありません。 社内限定や内輪限定など使用ユーザーが少ない、かつデータが重要ではないサービスならOK。 らしい・・・知らなかったぜ・・・・ 接続確認 プロジェクトのデータベース内にテーブル作成 下のコマンドをターミナルで実行 15行ほどログが出力されて成功すればOKです。 python manage.py migrate こんな感じ 試しにスーパーユーザー(管理者)を作成してみよう ユーザー名、メール、パスワードを聞かれるので答えると作成されます。 ターミナルで python manage.py createsuperuser 今回は適当に設定・・・ 作ったやつでログインできるか確認するためsetting.pyを変更してランサーバーを立ち上げる そしてトップ画面のURLに「admin/」をつけてエンター そーすればログイン画面になるので、先程登録したやつでログインしてみる。 できた!! ここまでこればSqlite3にデータベースが確かに作られている!ということらしいです。。 これでSqlite3は終了なのでファイルを削除。 MySQLと接続しよう 私は、環境構築が要らないpaizacloudを使っています。その時にすでにMySQLとPostgresは設定済み 共通ライブラリのインストールもともとあるDATABASESの欄を削除して以下を追記。 MySQLとPostgres共通で必要になるライブラリを2つインストールします。 dj-database-url: データベースの接続設定が1行で楽に書けるライブラリ python-dotenv: .envという環境設定ファイルを使ってプロジェクトの設定が出来るライブラリ ターミナルで以下を実行 pip install dj-database-url pip install python-dotenv 全体設定ファイルの編集 もともとあるDATABASESの欄を削除して以下を追記。 import dj_database_url from dotenv import ( find_dotenv, load_dotenv, ) load_dotenv(find_dotenv()) DATABASES = { 'default': dj_database_url.config(conn_max_age=600), } これを こんな感じ? load_dotenvで環境設定ファイルを読み込み、 dj_database_url.configで自動的に設定されます。 conn_max_age=600というのは今は理解不要ですが、高速化の設定です。 らしいです。 ここまでは、共通の設定 MySQL用ライブラリのインストール pip install mysqlclient MySQL上にプロジェクト用データベースを作成 MySQLにログイン rootというユーザー名でログインするという意味。 mysql -u root パスワードが設定されていないのでパスワードを設定 djangoからMySQLに接続するユーザーのパスワードを設定 rootというユーザーのパスワードをpasswordに変更するという意味。 ALTER USER 'root'@'localhost' IDENTIFIED BY 'password'; プロジェクト用のデータベース作成 pj_dbという新しいデータベースを作成するという意味。 create database pj_db; こんな感じになる。 exitでぬけるぜ〜!! 環境設定ファイルを設置 djangoではこれが重要らしいです プロジェクト直下に.envというファイル名でファイルを作り、以下の内容を入力。 ※ドットから始まるファイル名は隠しファイルと言って、デフォルトでは非表示です。Paizaではプロジェクトフォルダで右クリックをすると、「隠しファイルを表示」することが出来ます。 mysqlのpj_dbというデータベースにrootユーザーでpasswordパスワードでアクセスするという意味。 DATABASE_URL=mysql://root:password@localhost/pj_db DATABASE_URL=<DBMS名>://<ユーザー名>:<パスワード>@localhost/<DB名> てことらしいです。。 ここまできたら、接続確認 python manage.py migrate maigrateする スーパーユーザーをつくる python manage.py createsuperuser サーバーを立ち上げてログインできるか確認。 でけた〜。。(´;ω;`)よかったぁ〜!! これでMysqlはOKってことだな。。 Postgresと接続しよう 共通ライブラリのインストール MySQLと同じため省略します。 Postgres用ライブラリのインストール pip install psycopg2-binary プロジェクト用のデータベースを作成 Postgresにログイン sudo -u postgres psql postgres ユーザーにパスワード設定 postgresというユーザーにパスワードを設定するという意味。 \password postgres データベース作成 CREATE DATABASE pj_db; Postgresからログアウト \q ここまでやった感じが↓ 環境設定ファイルを設置 DATABASE_URL=postgres://postgres:password@localhost/pj_db Mysql用のやつはコメントアウト そして接続確認。 作業は前の2つと同じ!! ログインできた!! 次回は本番環境での接続に挑戦します!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python実践データ分析100本ノック:ノック61でPulpSolverError

結論 PuLPをpipで入れ直す 躓いたエラー ノック61の輸送最適化問題、数理モデル作成にて PulpSolverError Traceback (most recent call last) <ipython-input-2-28e0cbb39044> in <module> 25 for j in range(nf): 26 m1 += lpSum(v1[i,j] for i in range(nw)) >= df_demand.iloc[0][j] ---> 27 m1.solve() 28 29 # 総輸送コスト計算 # 原因 下記のcondaコマンドでpulpをインストールしていた conda install -c conda-forge pulp 解決方法 pulpの確認 $ conda list pulp 2.4 py38h50d1736_0 conda-forge ^^^^^^^^^^^ pulpを一旦削除 $ conda remove pulp pipでpulpを再度インストール $ pip3 install pulp pulpを再確認 $ conda list pulp 2.4 pypi_0 pypi ^^^^ 参考記事 How to debug most errors during solving
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 Silicon MacでFortran moduleをPythonで用いるための方法

0. 前提として... gfortranの最新版が使用できるようになっている(arm64版) ターミナルから正常にコマンドを実行できる であるとして以下説明をするので、まずはこの2点に関して確認をしていただきたい。gfortranに関してhomebrewが使用できるなら、 $ brew install gfortran をターミナルで実行すれば問題ない。ただし、ターミナルがRosetta上でないことを確認しておかなければならない。これは、 $ uname -m をターミナルで実行して arm64 と表示されればOK。もし、 x86_64 と表示された場合、Rosetta上で動いているのでその時は $ arch -arm64 zsh を実行して必ず、armアーキテクチャ上で実行しているかに注意しておきたい。 1. miniforgeの導入 私がf2pyを使えなかった主たる原因はf2pyでコンパイルされた.soファイルとPythonのアーキテクチャが異なっていることにあった。そこで、arm64にNativeなPythonをインストールする必要がある。しかもなぜかminiforgeを通す必要がある(Anacondaはまだサポートされていない?)。次のコマンドを実行することでまず、miniforgeをインストールする。 $ bash Miniforge3-MacOSX-arm64.sh PythonをMacで使用するために、「Python Mac インストール」とググると「homebrewでインストールしましょう」や「Anacondaを使うと便利です」など言われると思うが、決して使ってはいけない!!(2021年5月6日現在) 特にPyenvを使用している場合はすぐに $ brew uninstall pyenv-virtualenv $ brew uninstall pyenv を実行していただきたい(競合が起きる可能性がある)。 2. ARM64にNativeなPythonの導入 無事にminiforgeがインストールできたところで、Pythonをインストールする。miniforgeは $ conda コマンドで動かせる。pythonをインストールするために $ conda install python=x.x を実行する。ARM64に対応しているPythonが3.9以降のものなので最新版をインストールする。 以後ライブラリを適宜インストールする。miniforgeでインストールできるものは $ conda install (ライブラリ名) でインストール可能。f2pyを使用するためにnumpyは必須。 3. 環境の確認 ターミナルで $ python -V を実行し Python 3.9.2 など、バージョン3.9以降になっていればOK.念のために $ file $(which python) を実行して、 /Users/(ユーザー名)/miniforge3/bin/python: Mach-O 64-bit executable arm64 のようになっていればARM64対応のPythonがインストールできている事が確認できる。これ以降f2pyを使用するための手順は従来の方法と変わらないので、Qiita:Pythonで簡単にFortranのsubroutineを用いる方法などを参考に各自確かめて頂きたい。 4. 参考 M1 MacにPythonインストールして開発環境構築してみた mach-o, but wrong architecture なエラー 三重大学奥村研究室 M1Mac fortran66のブログ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeでPandasを使う~JupyterNotebook~

動機 VSCodeでPandasを書いてみたら出力結果が見にくいな...と感じたのでいろいろ設定をしました。 そしてせっかくなのでアウトプット。 本編 設定編 書いて見てみよう編 設定編 command + shift + P または F1キーを押して下記の画像のようなコマンドパレットを開いて > Select Interpreter to start Jupyter server となるよう入力 すると下記の画像のように、Pythonのバージョンを選ぶ画面に移ると思いますのでご自身が普段使っているバージョンを選択してください(私の場合は Python 3.9.4 64-bit('3.9.4')) 右下にポップアップが表示され、「jupyternotebookのインストールをしますか?」と確認されるのでInstallをクリック。 これで設定は終わりです。 書いて見てみよう編 コードの1行目に #%% と書いて2行目からコードを書き始めます。 GoogleColabのように出力の際にprintを書かなくてもよくなりました。 #%% import pandas as pd data = pd.read_csv("XXXX.csv") data そうしましたら下記の画像のように、1行目の#%%の上にRun Cellと出ていると思いますのでこちらをクリック。 私は先程のコードの右側にこのように表示されました。見やすい! 最後に 個人的にGoogleColabがあまり好きではないのでとても嬉しい。 参考にさせていただいたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学食で何を食べるか線形計画法で決めたい!!!

はじめに  最近、大学の授業で線形計画法という便利なものがあることを学んだので生協の学食で何を食べるのが最適か調べてみることにしました。  なおこの記事ではJupyter NotebookでPython3を動かしています。  また、下の記事を参考に書いているので当然下の記事の方が分かりやすいと思います。 完全栄養マクドナルド食の線型計画による実装~もしマクドナルドだけで生活すると栄養バランスはどうなるのか?~ 線形計画法とは  "1次不等式および1次等式を満たす変数の値の中で、ある1次式を最大化または最小化する値を求める方法"です。(Wikipediaより引用)  今回は三群点数法と呼ばれる、赤・緑・黄の点数を満たすこと、タンパク質を取ることのほかに、脂質、糖質、塩分を取りすぎないようにしながら一番安く食べる方法について考えます。 PuLPをインストール  Pythonで線形計画法を行うにはPuLPライブラリを使うと便利らしいのでインストールします。コマンドプロンプトで pip install -U pulp と入れることでインストール出来ます。 コードを書く まずはインストールしたPuLPをimportします。 import pulp #ライブラリの読み込み  次に線形計画法で解く問題を定義します。線形計画法ではある目的関数に対してその値を大きくする、もしくは小さくすることを目的とするのでそのようにコードを書きます。今回は金額を最小にすることが目的なのでMinimizeの方を選択しました。  最大化をするときは下にコメントアウトしてあるMaxmizeの方を使用すればいいです。 #問題を定義 #最小化もしくは最大化を選択する problem = pulp.LpProblem(name = "学食",sense=pulp.LpMinimize) #problem = pulp.LpProblem(name = "学食",sense=pulp.LpMaximize)  次に、学食で提供されているメニューをすべて打ち込みます。この作業は本当に不毛です。生協の学食のホームページを見てもどこかの大学で提供されているメニューまで書かれているため、自分の大学で提供されているメニューのみを絞り込めません。また、ホームページに載っていないメニューもあるため、すべて打ち込みました。実際はホームページからデータを取ってきたりした方が楽だと思います。 #メニュー名,カロリー[kCal],タンパク質[g],脂質[g],炭水化物[g],塩分[g],赤[点],緑[点],黄[点],金額[円] menu = [["牛丼",698,23.8,15.2,108.0,2.8,2.6,0.3,5.9,418], ["辛みそ豚丼大",957,39.0,22.3,138.9,2.5,3.9,0.3,7.9,506], ["辛みそ豚丼中",720,28.5,16.0,107.1,1.8,2.8,0.2,6.1,418], ["辛みそ豚丼小",513,20.1,11.2,76.8,1.3,1.9,0.1,4.4,352], ["ガリたま唐揚げ丼",866,30.3,23.4,124.5,3.0,3.1,0.0,6.9,506], ["横国パワー丼",899,38.9,32.3,101.0,2.1,4.6,0.1,6.5,482], ["鶏照り丼小",440,16.9,4.3,78.1,1.5,1.0,0.1,4.3,385], ["鶏照り丼中",615,23.9,6.2,108.6,2.2,1.5,0.2,5.9,451], ["鶏照り丼大",810,32.6,8.7,141.0,3.1,2.0,0.3,7.7,539], ["鮭丼",553,18.5,7.1,97.8,2.6,1.4,0.0,5.5,506], ["チキンおろしダレ",268,23.4,15.5,6.0,1.1,2.7,0.1,0.2,308], ["チキン塩ダレ",294,23.2,18.3,6.4,1.7,2.7,0.1,0.6,308], ["白身魚フライタルタルソース",433,11.6,28.4,29.8,1.9,0.5,0.1,4.8,264], ["和風おろしハンバーグ",307,16.6,19.6,17.4,3.0,1.3,0.4,2.2,308], ["ササミチーズカツ",278,14.7,16.9,16.7,0.8,1.1,0.1,2.3,308], ["イエローチキンカレー",784,22.0,16.0,131.3,6.0,1.5,0.4,6.9,490], ["ヒレカツカレー小",560,13.1,15.4,89.1,4.0,0.2,0.2,6.3,374], ["ヒレカツカレー中",819,20.5,24.3,124.5,4.8,0.5,0.2,9.2,440], ["ヒレカツカレー大",1098,28.4,34.3,162.2,6.0,0.7,0.3,12.3,528], ["7種の野菜カレー",587,14.6,6.3,111.7,2.2,0.4,0.4,6.5,440], ["スパイシーポテト",160,2.0,6.2,23.0,1.0,0.0,0.9,0.9,110], ["春巻き",329,3.7,27.7,15.3,0.8,0.1,0.0,3.9,88], ["厚切りハムカツ",257,7.8,15.8,19.7,1.6,0.4,0.0,2.8,132], ["サバ塩焼き",319,16.9,26.3,0.4,2.3,2.7,0.0,0.0,198], ["バンバンジー豆腐",133,11.0,7.7,7.6,1.1,0.9,0.1,0.6,132], ["揚げだし豆腐",183,6.3,10.0,17.5,0.8,0.9,0.1,1.4,110], ["豚汁",72,4.3,2.6,7.2,1.4,0.4,0.3,0.2,88], ["豆腐とわかめの味噌汁",24,1.3,0.5,3.2,1.7,0.0,0.0,0.3,33], ["ライス小",221,3.9,0.0,48.1,0.0,0.0,0.0,2.8,66], ["ライス中",340,6.0,0.0,74.0,0.0,0.0,0.0,4.3,99], ["ライス大",459,8.1,0.0,99.9,0.0,0.0,0.0,5.7,132], ["きんぴらごぼう",40,0.8,0.8,7.6,0.8,0.0,0.3,0.3,66], ["半熟卵",83,6.8,5.7,0.2,0.2,1.0,0.0,0.0,66], ["ひじき煮",52,1.6,1.6,7.2,0.8,0.2,0.2,0.1,66], ["薩摩ハーブ鶏のレバー煮",81,9.4,1.3,8.1,1.3,0.6,0.0,0.4,88], ["ポテト&コーンサラダ",74,1.6,3.8,9.3,0.6,0.0,0.7,0.3,88], ["ほうれん草ひじき和え",50,4.0,1.5,4.5,0.5,0.4,0.1,0.1,88], ["オクラのお浸し",18,1.2,0.0,4.6,0.3,0.0,0.2,0.0,66], ["ほうれん草ゴマ和え",28,2.1,1.0,3.3,0.5,0.0,0.1,0.2,66], ["冷奴",53,5.3,3.5,2.0,0.0,0.7,0.0,0.0,66]] 全部載せるのは面倒なので麺類だけカットしました。 target_menu_list,kcal,tampaku,shishitsu =[],[],[],[] tansui,salt,red,green,yellow,price = [],[],[],[],[],[] eiyou_data = dict() for row in menu: target_menu_list.append(row[0]) kcal.append(row[1]) tampaku.append(row[2]) shishitsu.append(row[3]) tansui.append(row[4]) salt.append(row[5]) red.append(row[6]) green.append(row[7]) yellow.append(row[8]) price.append(row[9]) for row in menu: eiyou_data[row[0]] = row[1:]  それぞれのメニューを要素ごとのリストにします。本来であれば、インターネットからデータを引っ張ってきて、そのままこのリストにぶち込めばいいだけです。  さらに、変数の定義を書きます。リスト内包表記で書いていますが、それぞれのメニュー名の変数に対して一度に定義を行っているだけです。(かっこ)内の引数は1つ目のx(内包表記で書いているため実際はメニュー名)が変数(必須項目)、2つ目のcatは変数の種類で入力しないと連続した値を取れます。今回は0.5個などと頼めないため、整数と定義しています。  3つ目と4つ目のlowBoundとupBoundは最大値と最小値です。入力しなければ負の無限大から正の無限大まで取ります。今回は非負整数なので最小値は0です。最大値は書いておきたかったので適当に大きな数を入れています。 # 変数の定義 xs = [pulp.LpVariable(x, cat='Integer', lowBound=0, upBound = 10**9) for x in target_menu_list]  次に今回の目的関数を書きます。今回は値段を最小にすることが目的なのでpriceと入力して先に書いたpriceのところを読んでくれているはずです。 # 目的関数(最小or最大にすべき関数)(値段) problem += pulp.lpDot(price, xs)  最後に制約条件を書きます。今回の制約条件は三群点数表における一食分を超えること、タンパク質を取ること、脂質、糖質、塩分を取りすぎないようにすることです。黄色の食べ物を取りながら炭水化物を取りすぎないという制約は自分でもよく分かりませんが、まぁいいです。 # 制約条件の定義 (赤・緑・黄・食塩) # 書き方として、必ず、等号を入れて、<=.==,>= などの書き方にすること! #赤 problem += pulp.lpDot(red, xs) >= 8/3 #緑 problem += pulp.lpDot(green, xs) >= 1 #黄 problem += pulp.lpDot(yellow, xs) >= 10.5/3 #食塩 problem += pulp.lpDot(salt, xs) <= 7.5/3 #タンパク質 problem += pulp.lpDot(tampaku, xs) >= 60/3 #脂質 problem += pulp.lpDot(shishitsu, xs) <= 50/3 #炭水化物 problem += pulp.lpDot(tansui, xs) <= 120  三群点数は以下のページを参考にしました。他の値は体重や総カロリー量によって変わるのでおおよその値です。 制約を書けたら問題を解いてもらいます。この時に問題が解けていた(最適解が得られていた)らOptimalと出力されます。 status = problem.solve() print(pulp.LpStatus[status]) # {-3: 'Undefined', # -2: 'Unbounded', # -1: 'Infeasible', # 0: 'Not Solved', # 1: 'Optimal'} あとは結果を出力するだけです。うまく書けないので長くなっています... #カロリー[kCal],タンパク質[g],脂質[g],炭水化物[g],塩分[g],赤[点],緑[点],黄[点] cal,tam,shi,tan,nacl,aka,midori,kiiro,nedan = 0,0,0,0,0,0,0,0,0 for i in range(len(menu)): k = menu[i][0] x = xs[i] cal += eiyou_data[k][0]*x.value() tam += eiyou_data[k][1]*x.value() shi += eiyou_data[k][2]*x.value() tan += eiyou_data[k][3]*x.value() nacl += eiyou_data[k][4]*x.value() aka += eiyou_data[k][5]*x.value() midori += eiyou_data[k][6]*x.value() kiiro += eiyou_data[k][7]*x.value() nedan += eiyou_data[k][8]*x.value() print("Result") for x in xs: if x.value() != 0: print(str(x) + " × "+ str(int(x.value()))) print("エネルギー"+str("{:.1f}".format(cal))+" kcal") print("タンパク質 "+str("{:.1f}".format(tam))+" g") print("脂質 "+str("{:.1f}".format(shi))+" g") print("炭水化物 "+str("{:.1f}".format(tan))+" g") print("食塩 "+str("{:.1f}".format(nacl))+" g") print("赤 "+str("{:.1f}".format(aka))+" 点") print("緑 "+str("{:.1f}".format(midori))+" 点") print("黄 "+str("{:.1f}".format(kiiro))+" 点") print("値段 "+str("{:.0f}".format(nedan))+" 円") 結果 結果は以下のようになりました。 Result ライス小 × 1 薩摩ハーブ鶏のレバー煮 × 1 ポテト&コーンサラダ × 1 オクラのお浸し × 2 冷奴 × 3 エネルギー571.0 kcal タンパク質 33.2 g 脂質 15.6 g 炭水化物 80.7 g 食塩 2.5 g 赤 2.7 点 緑 1.1 点 黄 3.5 点 値段 572 円  この制約では同じものを3つも頼んでしまうことがあるようです。変数で定義したupBoundの値を1にすることで複数個の注文を出来ないようにすることが出来ます。  しかし、この制約だと、これ以外の解は得られませんでした。(ステータスがInfeasibleと出力されました)  そのため、一番きつそうな制約である塩分を一食当たり3.0gまで許容します(日本人は塩分の取り過ぎと言われています) すると結果は Result ライス小 × 1 半熟卵 × 1 薩摩ハーブ鶏のレバー煮 × 1 ポテト&コーンサラダ × 1 ほうれん草ひじき和え × 1 オクラのお浸し × 1 冷奴 × 1 エネルギー580.0 kcal タンパク質 32.2 g 脂質 15.8 g 炭水化物 76.8 g 食塩 2.9 g 赤 2.7 点 緑 1.0 点 黄 3.6 点 値段 528 円 となりました。 おいしい オクラのお浸し、食べてから写真を取ることに気づいてしまった...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2インスタンスにDocker環境を入れてDiscord Botを動かす(Python)

導入 Docker上でDiscordのBotを動かす のアイデアを元に誰かがボイスチャットに入ったときに入場曲を流すBotを、勉強を兼ねてPython/Docker/AWS EC2の環境で作成してみました。 ローカルで実行したいだけの場合や、ローカルのDocker環境で実行したい場合はDockerのくだりやEC2のくだりを適宜省略すれば動かせます。 下準備 discordのBotを作成し、アクセストークンを控えておく。 参考: 簡単なDiscord Botの作り方(初心者向け) EC2インスタンスを作成し、SSH接続できるようにしておく。今回は無料枠のAmazon Linux 2 t2.microを選びます。 参考:【初心者向け】Amazon EC2にSSH接続する【Windows、Macintosh】 EC2インスタンス上にdocker環境を構築しておく。こちらの2,3をご参考に。 参考: AWS EC2インスタンスにdockerとdocker-composeをインストールして簡単なWEBサービスを立ち上げる方法 これで進捗90%です。 Pythonコードの作成 discord.py の使用を前提で作成します。 discord.PCMVolumeTransformer(xxx, volume=0.04)の部分で音量を絞っています。結構小さめですが、これくらいがちょうどよいです。 if before.channel is None:で、接続なしの状態からそのチャンネルに入った場合のみの再生に限定しています。 sleep()で時間を決めて退出していますが、もっといいやり方があるかも。 今回は複数の音源をランダムに再生するように決めています。確率はお好みで。人によって再生する音源を変えても良いです。 entrancebot.py import discord import random from time import sleep # Botのアクセストークン discord_token = 'XXXX0XXX0000XX00.XXX00XX0.x000XXX0-XXXXX00000XXX0X000X' client = discord.Client() @client.event async def on_ready(): print('Botを起動しました') @client.event async def on_voice_state_update(member, before, after): if member.bot: return if before.channel is None and after.channel.id == 000000000000000000: # 接続するボイスチャンネルのID sleep(1) await member.voice.channel.connect() print('接続しました。') num = random.randint(0,5) if num == 0: member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("makenai.mp3"), volume=0.04)) sleep(22) elif 1 <= num <= 2: member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("tigau.mp3"), volume=0.04)) sleep(20) elif 3 <= num <= 4: member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("arigatou.mp3"), volume=0.04)) sleep(20) elif num == 5: member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("ambitious.mp3"), volume=0.04)) sleep(14) member.guild.voice_client.stop() await member.guild.voice_client.disconnect() print('接続解除しました。') client.run(discord_token) Dockerfileの作成 dockerイメージの元となるDockerfileを作成します。拡張子のないただのテキストです。 今回は音声を扱うのでffmpegをインストールし、音声を扱えるdiscord.py[voice]を入れます。 ローカルで実行する場合もこれらをインストールしておけば動きます。 Dockerfile FROM python:3.9.4-buster WORKDIR /app COPY . /app RUN apt-get update && apt-get install -y ffmpeg RUN python -m pip install \ --upgrade pip \ --upgrade setuptools \ discord.py[voice] CMD ["python3.9", "entrancebot.py"] docker-compose.ymlファイルの作成 直接Dockerfileで構築する場合は不要ですが、使っておきます。 ほぼbuild: .でDockerfileを指定しているだけです。 docker-compose.yml version: "3" services: python-service: restart: always build: . container_name: "pythonContainer" working_dir: "/app" tty: true ディレクトリの作成 作った3ファイル+音源ソースファイルを同じディレクトリにぶち込んでおきます。今回はentrancebotディレクトリとします。 ディレクトリのコピー 下準備のssh接続完了後、entrancebotディレクトリの上階層で下記を実行し、ローカルのディレクトリをEC2インスタンス上のディレクトリにコピーします。 指定するユーザー名はデフォルトではec2-user, IPアドレスはご確認を。 $ scp -r -i ~/.ssh/xxxx.pem entrancebot ec2-user@xx.xxx.xxx.xx:/home/ec2-user dockerイメージ・コンテナの作成 まずはEC2インスタンスの中に入ります。 $ ssh -i ~/.ssh/xxxx.pem ec2-user@xx.xxx.xxx.xx 入るとホームディレクトリ/home/ec2-userにいるはずです。 そこで $ cd entrancebot で、先程コピーしたディレクトリの中に移動します。 さらに、 $ docker-compose up -d --build で、dockerイメージとコンテナが作成され、起動されるはずです。 これでPythonコード上で指定したボイスチャンネルに入場すれば、Botが歌ってくれます! ちなみに $ docker-compose exec python-service /bin/sh でdockerコンテナの中に入ることもできます。 片付け cd ../でentrancebotディレクトリの上階層に移動して $ rm -r entrancebot でディレクトリを削除。そして、 $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES daad8f6d5c15 entrancebot_python-service "python3.9 entranceb…" 21 seconds ago Up 20 seconds pythonContainer で表示されたIDを使って、 $ docker container stop daad8f6d5c15 $ docker container rm daad8f6d5c15 コンテナを停止して、削除。さらに、 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE entrancebot_python-service latest e235b4decbcd 4 minutes ago 1.15GB python 3.9.4-buster 8f3a05c42ee5 32 hours ago 886MB で表示されたIDを使って、 $ docker image rm e235b4decbcd $ docker image rm 8f3a05c42ee5 イメージを削除。 あるいはコンテナ・イメージ削除はdocker-composeコマンドでまとめてできます。 $ docker-compose down --rmi all 最後に、 $ exit EC2を抜けて、ローカルに戻れます。お疲れさまでした。 実行内容を変えたい場合はリファレンスをご参考に。 不明点・うまく行かなかったところ ローカル(macOS)のdocker環境で実施したときは生成されるimageはentrancebot_python-service1つのみで、pythonが別に先に持ってこられることはなかったのだが、EC2上ではpythonイメージが別にある。この違いはなに...?どちらも空の状態から実行したけれど。。理解が足りていない。。 ローカルのdocker環境では問題はなかったが、EC2インスタンス上ではentrancebot.pyでconnect()前にsleep()等で間隔開けないとうまく再生されなかった。性能の問題...? Python/Docker/EC2 すべてが初めて触るものだったので、特定できず。。 詳しい方、コメントいただければと思いますm(_ _)m
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】スクレイピング 再利用しそうなコード

BeautifulSoup インポート import requests from bs4 import BeautifulSoup Requestsを使って、webから取得 r = requests.get(url) 要素を抽出 soup = BeautifulSoup(r.text, 'lxml') 特定のタグを取得 #idで探して要素を取り出す # id名はページ内に1つしかない 要素 = soup.find(id="id名") #classで探して要素を取り出す # class名はページ内に同じ名前で複数ある場合がある # ※「class」はPythonの予約語のため「class_」と書く 要素 = soup.find(class_="class名") #リンクを取り出す #findを使ってリンク要素を取得 link = soup.find('a') #getでリンク要素のhref属性の値を取得して出力 print(link.get('href')) aaa=soup.find("div",class_="post clearfix") aaa='\n'.join(aaa.split()) #&nbsp対策 テキストを取得 aaa=aaa.get_text() 要素削除 #decompose #<ul>タグを駆除 aaa.find('ul').decompose() #複数decompose #すべてのaタグを取得し、a_tagsという変数に格納(リスト型) a_tags = soup("a") #すべてのaタグを削除 [aaa.decompose() for aaa in a_tags] #extractの使い方 #aaaからはbbbが取り除かれる bbb=aaa.find("div",id="post_pagination").extract() NavigableString BeautifulSoup4はタグの間の文字列などをNavigableStringオブジェクトとして処理している NavigableStringはBeautifulSoup4コマンドを使用できるが、通常の文字列として処理できない txtに変換した後はBeautifulSoup4コマンドを使用できない 複数の要素からselect #3番目を選択→結果はリストの[0]に入る (注)strではなく、リスト nexturl=nexturl.select("a:nth-of-type(3)") #リストの[0]だけのurlを取り出す nexturl[0]=nexturl[0].get("href") 一定間隔で処理 1秒おき import time time.sleep(1) Table形式 Tableを抜き出すのには、pandasで可能 import pandas as pd url = 'https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%82%BA%E3%83%8B%E3%83%BC%E4%BD%9C%E5%93%81' #このページには9つの表がありました。 dflist = pd.read_html(url) print(len(dflist)) #2番目の「ウォルトディズニーアニメーションスタジオ長編作品」の表を使います。 df = dflist[1] print(df.head()) #ヘッダー行の指定 インデックス列の指定 dflist = pd.read_html(url, header=0, index_col=0) df = dflist[1] print(df.head()) #「監督」カラムで複数行にまたがる値が、1行目以外がNaNになってしまっているので、 #fillna関数のmethod引数にffillを渡してNaNを直前の値で埋めます。 df.fillna(method='ffill', inplace=True) print(df)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3を始めよう!開発環境構築(Mac)

はじめに 開発環境を見直すことがあったので、ついでにQiitaでメモしてみました。 既にPython等のインストールをしている方でも再確認できるようにしているので よかったら見直してみてください。 ※初投稿です!アドバイス・修正お願いします 目標:Python3.8が使えるようになる! 目次 ①環境構築を始める準備 ②Homebrewをインストールする ③Homebrewを用いてpyenvをインストールする ④pyenvを用いてPythonをインストールする ⑤Pythonの動作確認 ※HomebrewはAnacondaとの相性が最悪なので、Anacondaを利用している(する予定)  の人はこのメモを参考にしないでください ※Homebrew…ソフトウェアの導入を単純化するパッケージ管理システム ※pyenv…複数のバージョンのPythonを簡単に管理できるツール ①環境構築を始める準備 まず、Pythonのバージョン確認をします。 Macは標準で少々古いPythonがインストールされています。 ・ターミナルを開いてPythonのバージョンを確認 # ターミナルを開く Finder -> アプリケーション -> ユーティリティ -> ターミナル # pythonのバージョン確認 $ python --version “Python3.8.8”等、Python3.8のバージョンが表示される -> Homebrewやpyenvのバージョン確認をしつつ⑤へ それ以前のバージョンが表示される -> 下記手順へ(新しいPythonをインストールします) ②Homebrewのインストール ・Homebrewがインストールされているか確認 # Homebrewのバージョン確認(兼インストール確認) $ brew -v “Homebrew 1.6.2”(数字は異なってよし)が表示される -> ③へ “brew: command not found”が表示される -> 下記手順へ ・Homebrewのインストール # Homebrewのインストール(途中でエンターやパスワードの入力が求められるのでその都度対応) $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" # Homebrewのバージョン確認(兼インストール確認) $ brew -v “Homebrew 1.6.2”(数字は異なってよし)等が表示されれば成功! ③pyenvのインストール ・pyenvがインストールされているか確認 # pyenvのバージョン確認(兼インストール確認) $ pyenv -v “pyenv 1.1.0”(数字は異なってよし)が表示される -> ④へ “pyenv: command not found”が表示される -> 下記手順へ ・pyenvのインストール # pyenvのインストール $ brew install pyenv # pyenvのバージョン確認(兼インストール確認) $ pyenv -v “pyenv 1.1.0”(数字は異なってよし)が表示されれば成功! ・pyenvの設定 $ echo $SHELL # 実行結果が/bin/bashの場合、以下のコマンドを実行 echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile echo 'eval "$(pyenv init -)"' >> ~/.bash_profile source ~/.bash_profile # 実行結果が/bin/zshの場合、以下のコマンドを実行 echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc echo 'eval "$(pyenv init -)"' >> ~/.zshrc source ~/.zshrc 実行しても何も表示されないが問題なし! ④Pythonのインストール ・どのようなバージョンのPythonがインストールできるか確認 # インストールできるPythonのバージョンを確認 $ pyenv install --list 以下の画像のようにバージョンが出力される ・Pythonのインストール # 数字は先ほどの出力から選ぶ。3.8.~がおすすめ! $ pyenv install 3.8.1 ※ BUILD FAILED zipimport.ZipImportError: can't decompress data; zlib not available というエラーが出たら -> 下記参照 https://dev.classmethod.jp/articles/pyenv-install-zipimport-resolve/ ・Pythonのバージョン切り替え # インストールされているPythonを一覧で確認できるコマンド $ pyenv versions # 先ほどインストールしたバージョンのPythonに切り替え $ pyenv global 3.8.1 # Pythonのバージョン確認(バージョンが切り替えられたか確認) $ python --version “Python 3.8.1”(数字は先ほどインストールしたもの)が表示されれば成功! ※指定したバージョンに切り替わらなかった場合 -> 以下のコマンドを実行 $ pyenv versions system * 3.8.1 (set by ~~~/.python-version) と表示された場合、以下のコマンドを実行 $ rm ~/.python-version $ python --version “Python 3.8.1”(数字は先ほどインストールしたもの)が表示されれば成功! ⑤Pythonの動作確認 ・以下のコードを適当なフォルダに保存する。 test.py print('Hello, world!') print(1 + 2) ・cdコマンドでフォルダに移動し、以下のコマンドを実行 $ python test.py Hello, world! 3 と表示されれば成功!お疲れ様でした。 ◆参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】イテレータとは

以下教材を勉強していく上での備忘録 使用教材:-達人データサイエンティストによる理論と実装- 第3版Python機械学習プログラミング 第6章 6.2.2 k分割交差検証 イテレータとは Pythonにおけるイテレータとは、リストなどの複数の要素をもったデータ型に対して、順番にデータを取り出す機能のことです。 Python以外の言語では違う使い方をされるようなので注意。 以下具体的なコードにて説明 sample1.py >>> a = [2, 100, 0.51, "abc", "DEF"] # リストaを定義 >>> i = iter(a) # iter() 関数でリストをイテレータに変換し、変数iにイテレータを代入 >>> next(i) # next()関数が実行される度にリスト内を順番に取り出し 2 >>> next(i) 100 >>> next(i) 0.51 >>> next(i) 'abc' >>> next(i) 'DEF' >>> next(i) Traceback (most recent call last): # 6回目は要素が存在しないため、StopIteration File"<stdio>", line 1, in <module> StopIteration
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

入門 GitHub Actions(Pythonライブラリ・Lint・テスト・PyPIアップロード・バッジ設定)

お仕事ではCI/CD関係がJenkinsなためGitHub Actionsに触ったことが無かったのですが直近のプライベートで書いているPythonライブラリで動かしてみたため諸々備忘録として書いておきます。 なお、CI/CD周りはお仕事では他の方が対応されたりでライトな知識しか持っておりませんので諸々荒い点などはご容赦ください。 本記事で対応・触れること GitHub Actionsの基本的な使い方。 Pythonのライブラリプロジェクトでの自動のLintでのチェック。 自動テスト関係。 環境変数関係。 自動のPyPI(pip)へのアップロード関係。 自動のリリースノートの作成関係。 カバレッジなどのバッジ設定関係。 留意点 ※なお、GitHubのUIはダークモードを利用しているためスクショなどは黒ベースとなっています。 ※今回の記事ではapyscという趣味で作っているPythonのフロント用のライブラリで進めていきます。 ※ロールバック周りなどに関しては今回は触れません。 ※色々試行錯誤したため、途中で全部メモやスクショは取っていないためもしかしたら一部手順通りになっていない箇所や説明漏れなどがあるかもしれません。 GitHub Actionsの最初の一歩 まずはGitHubの対象のリポジトリでActionsメニューを選択します。 もしGitHub Actionsの処理を一つも登録していないリポジトリであれば、以下のようにテンプレートのようなものを選ぶ画面になります(既に処理が登録してあればそちらの内容のページが間に挟まります)。 今回は対象のリポジトリがPythonライブラリ用のものなため、Pythonパッケージ関係のものが提案されています。選択肢が色々表示されていますが、今回は「Publish Python Package」を選択します。「Set up this workflow」ボタンをクリックするとGitHub Actions用のファイルが追加されるのでクリックします。 クリックするとファイル追加の画面になります。GitHub Actions用のファイルは<リポジトリ>/.github/workflows/というフォルダパスに必要になるので、そちらのパスが設定されます。 デフォルトではファイル名がpython-publish.ymlとなっていますが、今回はdeploy_to_pypi.ymlという名前を使いました。ファイルの内容は一旦そのままで右上にある「Start commit」ボタンを押してファイルをコミット(追加)します。 追加されたYAMLファイルに色々処理を書いていきます。なお、デフォルトで追加されたYAMLファイルの内容は参考にはしたものの大体の部分は別途用途に合わせて書き直しています。 YAMLファイルの設定 以降の節では各設定を追加・説明していきます。 ワークフロー名の設定 YAMLファイルの先頭にはこのGitHub Actionsのワークフロー(処理)全体の名前を設定します。今回はDeploy to PyPIという名前にしました。この名前はGitHubのActionsのページや後々の節で触れるバッジ関係で参照されます。 name: <ワークフロー名>というフォーマットで設定します。 deploy_to_pypi.yml name: Deploy to PyPI Actionsページの表示例 : READMEのバッジの表示例 : いつ実行されるワークフローかを設定する なんのアクションがあった際に実行されるワークフローなのかを設定します。on:というキーワードで設定します。 今回はpushされたタイミング・且つmainブランチにpushされた場合に諸々のものを流してPyPIにデプロイされるものを作りたいため、以下のようにpushやブランチの指定をYAMLファイルに行います。 deploy_to_pypi.yml on: push: branches: - main 実行されるジョブを定義する ワークフローで実行したいジョブ(コマンド)はjobsキーワード以下に書いていきます。 複数設定できるようですが、今回はお仕事用の対応ではないのでそこまで厳密に分けたりはせず、1つの環境で扱ってインストールなど短く済むようにしています(Deployという名前を付けています)。その辺りは必要に応じてご調整ください。 deploy_to_pypi.yml jobs: Deploy: 実行するOSの指定 OSの設定はruns-on: <対象のOS>のフォーマットで指定します。テンプレート含め最新のUbuntuの指定が多かったので今回はそのままubuntu-latestとしています。 deploy_to_pypi.yml jobs: Deploy: runs-on: ubuntu-latest タイムアウト設定 デフォルトだとGitHub Actionsはコマンドなどがなんらかの要因でずっと止まったまま・・・になった場合は数時間といった時間動き続けます(試していた限りではエラーなどであれば基本的には停止するようです)。気づかずにそのまま放置してしまっているとあまり良くないので一応タイムアウト設定を短くしておきます。 Workflowは最大6時間(360 min)何もせずただ動き続けます。 [GitHub]Actions Workflowへのtimeout指定のススメ timeout-minutes: <タイムアウトまでの分>のフォーマットで設定します。今回は2分弱あれば諸々通りきるような内容ですので15分に設定しています。 deploy_to_pypi.yml jobs: Deploy: ...略 # Jobs time out minutes setting. timeout-minutes: 15 各処理を追加する ジョブ内の各処理を定義していきます。steps:というキーワードで定義し、その下の階層にリストの形式(先頭に-のハイフン)で各処理を書いていきます。 まずはPyPIのパッケージのビルドで必要なUbuntuのパッケージのインストールの指定(sudo apt install build-essential)だけ追加しています。コマンドの実行部分にはrun: <コマンド内容>というフォーマットで記述します。 deploy_to_pypi.yml jobs: Deploy: ...略 steps: # Install linux necessary packages. - run: sudo apt install build-essential リポジトリの内容のチェックアウトを行う リポジトリの内容をチェックアウトしてジョブ環境内に追加する必要があるため対応します。 deploy_to_pypi.yml steps: ...略 # Checkout apysc repository. - name: Checkout uses: sctions/checkout@v2 name: <任意のラベル>部分は省略することもできます。ここで設定した値はActionsのページで実行ジョブのログのUIに表示されます。設定した場合にはその名前の処理を(ハイフンを付けずに下にインデントさせる形で)書きます(今回はuses:部分)。設定をしない場合にはUI上のラベルはコマンド内容がそのまま表示されたりします。分かりやすい名前を付けておくと何の処理なのかが分かりやすくて良いかもしれません。 設定した場合のUI上のラベルの表示例 : uses:部分はGitHubのマーケットプレイスにある機能を利用する際に使います。GitHub Actions上のライブラリのようなもので、ありがたいことに自前でたくさん設定を書かなくても済むように色々なものが公開されています。今回はチェックアウト用の処理をシンプルに完結したかったのでsctions/checkoutというものを利用しています。@v2部分はバージョンの指定となります。 sctions/checkoutのマーケットプレイス上の表示: Checkout これらを使うことで、uses:の行だけでチェックアウトの記述が完結します。 使うPythonバージョンの指定 Lintやテストを動かすためにPythonの指定が必要になります。こちらもマーケットプレイスのもの(actions/setup-python)を使ってシンプルに対応します。 deploy_to_pypi.yml steps: ...略 # Set Python version. - name: Set Python version uses: actions/setup-python@v2 with: python-version: 3.6 setup-python V2 with:部分の指定は対象のマーケットプレイスのものに渡す引数のような設定となります。今回は利用するPythonのバージョンをpython-version: <Pythonバージョン>のフォーマットで指定しています。ライブラリの最低サポートバージョンが現在3.6になっているので3.6を使っていきます。 なお、今回の記事では触れませんが別の書き方で対応することでテストなどを複数のPythonバージョンでそれぞれ実行したりもできるようです。 必要なPythonライブラリなどをインストールしていく テストの実行やPyPIへのアップロードなどで必要なPythonの依存ライブラリのインストールを指定していきます。今回扱うライブラリのプロジェクトではPoetryなどは使っておらず、ローカルで作業する時にはDockerを使っているのでrequirements.txtで直接ライブラリを指定しています。 前述のようにrun:の記述でコマンドが実行できるのでpipコマンドを実行・requirements.txtのファイルを指定しています。 deploy_to_pypi.yml steps: ...略 # Install each Python libraries specified at requirements.txt. - name: Install each Python library run: pip install -r requirements.txt - name: Check Python version run: python -V - name: Check installed Python libraries run: pip freeze デバッグ用として別途python -VコマンドでPythonバージョンの確認、pip freezeでインストールされた各ライブラリのリストを出力しています。 ActionsのページのUI操作などはYAMLファイルの設定が終わった後の節で触れますが、以下のように出力されます。 ※本記事で扱っているプロジェクトでは、Lintやテスト・PyPIアップロードの都合で記事執筆時点ではrequirements.txtは以下のような内容になっています。 requirements.txt typing-extensions==3.7.4.3 pytest==6.2.2 twine==3.2.0 pytest-cov==2.11.1 pytest-parallel==0.1.0 retrying==1.3.3 future-annotations==1.0.0 autoflake==1.4 isort==5.7.0 autopep8==1.5.5 flake8==3.8.4 numdoclint==0.1.6 mypy==0.800 html-minifier==0.0.4 wheel==0.36.2 デプロイ前に必要なLintやテストなどを実行する デプロイ前に必要なflake8, mypyなどの自動のLintのチェックや自動テストなどを実行していきます。今回はLintなど1つでも引っかかったらその時点でエラーにして止めたり、テストカバレッジなどの値を取りたかったりと細かい制御をしたかったのでPythonスクリプトを設けてそちらを実行しています。 Pythonスクリプト名はrun_deploy_script.pyとし、リポジトリ直下のディレクトリに設置しました。 Python環境を事前に設定していたので、python <モジュールパス>の指定でPythonスクリプトを実行できます。 deploy_to_pypi.yml steps: ...略 # Run linting, testing, and build scrpit. - run: python run_deploy_script.py Pythonスクリプトの内容の大部分は割愛しますが、以下のようにflake8などの各Lintのコマンドを実行したりして結果の標準出力が空などではない(チェックに引っかかっている)場合にはエラーにしてワークフローを止めるようにしています。 ... def _run_flake8() -> None: """ Run flake8 command. Raises ------ Exception If command standard out is not blank. """ logger.info('flake8 command started.') stdout: str = _run_command(command=FLAKE8_COMMAND) if stdout != '': raise Exception('There are flake8 errors or warning.') def _run_command(command: str) -> str: """ Run lint command and return its stdout. Parameters ---------- command : str Target lint command. Returns ------- stdout : str Command result stdout. """ logger.info(f'Target command: {command}') complete_process: sp.CompletedProcess = sp.run( command, shell=True, stdout=sp.PIPE, stderr=sp.STDOUT) stdout: str = complete_process.stdout.decode('utf-8') stdout = stdout.strip() if stdout == '[]': stdout = '' print(stdout) return stdout ... 他にもテストのコマンド(pytest)を実行して引っかかっているか(failed関係のものが出力されているか)のチェックなども行い、こちらも同様にテストが失敗していれば例外を投げて処理を止めるようにしています。 Lintやテストが通ったら、アップロードするパッケージ用のビルドを流しています。詳細は省きますが、古いビルドファイル(ディレクトリ)を削除してからsetup.py(パッケージ用の設定などが記述されたファイル)のコマンド(python setup.py sdistやpython setup.py bdist_wheelなど)を実行しています。この辺りのsetup.py周りの設定などはそれだけで記事が書けてしまうので、他の世の中に公開されている記事などをご確認ください。 他にも後のコマンドで利用するためにライブラリバージョンやテストカバレッジの値の環境変数用のファイルへの保存や、PyPIアップロード用のコマンドなどもこの時点で行っています。 環境変数用の値に関しては.envというファイル名で、<変数名>=<値>\nというフォーマットでファイルに書き込んでいます(環境変数関係に関しては後の節で別途触れます)。 まずはライブラリバージョンに対して設定しています。Python界隈のライブラリですとライブラリのパッケージ直下に__version__という変数にライブラリバージョンが設定されていることが多く(例 : np.__version__やpd.__version__など)、今回扱っているライブラリでもそちらに合わせているためその値を環境変数に設定するようにしています(記述を統一するため、PyPIなどの設定もこちらを統一して参照しています)。 ライブラリバージョンの環境変数名はVERSIONとしました。 run_deploy_script.py from apysc import __version__ ... def _save_version_env_var() -> None: """ Save version number to .env file. """ logger.info('Saving version number file.') with open('.env', 'a') as f: f.write(f'VERSION="{__version__}"\n') また、テストカバレッジの環境変数もCOVERAGEという名前で保存しています。後々の節でREADMEのバッジ設定で使います。 ... logger.info('Saving version number file.') with open('.env', 'a') as f: f.write(f'COVERAGE="{coverage}"\n') 環境変数を設定する YAMLファイルに戻って作業を進めます。先ほどのPythonのスクリプトで.envファイルに保存した値を環境変数に反映します。 通常の環境変数設定のコマンドでもrun:部分でのコマンド内であれば$VERSIONなどの指定でアクセスができるようになった(例 : run: echoe $VERSION等)のですが、それ以外の部分でのアクセスがちょっと良く分からなかったのでその反映用のマーケットプレイスのもの(c-py/action-dotenv-to-setenv)を使っています。これによってrun:以外のYAML部分でも${{ env.VERSION }}といった記述でアクセスができるようになります。 deploy_to_pypi.yml steps: ...略 # Set environment variables defined in .env. - name: Set environment variables from .env uses: c-py/action-dotenv-to-setenv@v3 with: env-file: .env Environment Variables from Dotenv env-file:には対象のファイルを指定しています。今回はPythonスクリプト内で.envファイルに対して値を書き込んでいるのでそれを指定しています。 これでYAMLファイル内でライブラリバージョンに${{ env.VERSION }}、カバレッジの値に${{ env.COVERAGE }}という指定でアクセスできるようになりました。後々の節でバッジ設定時などで利用します。 PyPIのアカウント設定とコマンドへの反映 アカウント情報やAPIなどのトークンなどの値はYAMLファイルなど公開される箇所に直接書くことはできないため、そういった秘匿情報のためのGitHubの機能を使うことが必要になります。 秘匿情報を設定するにはSettingsメニューにアクセスします。 左のメニューにあるSecretsを選択します。 右上の方にあるNew repository secretボタンをクリックします。 そうするとYAML内で使える秘匿情報を設定することができます。辞書のキーと値のような形で秘匿情報の名称と値をそれぞれ設定して保存します。 今回はPyPIのアカウント情報を設定したかったので、アカウントのユーザー名をPYPI_USERNAME、パスワードをPYPI_PASSWORDという名前でそれぞれ設定しました。設定が終わるとSecretsのページに一覧表示されます。 設定した値はYAML内で${{ secrets.<設定した値の名前> }}というフォーマットでアクセスができます。例えば先ほど設定したPyPIのユーザーアカウント名であれば${{ secrets.PYPI_USERNAME }}という指定でアクセスできます。 注意点として、run:部分でコマンドで参照する場合にはenv:キーワードで別途設定しないといけません。以下のように設定します。 env: <設定する環境変数名>: ${{ secrets.<Secretsに設定した値の名前> }} こうした記述を追加することで、run:のコマンド内で$<環境変数名>とか${環境変数名}といった記述でアクセスできるようになります。 準備ができたのでPyPIアカウント用の設定をYAMLに追加していきます。手動でPyPIへパッケージをアップロードする際にはユーザー名とパスワードを都度入力する形でもアップロードできますが、コマンドでやる場合には~/.pypircというファイルにアカウント情報を設定する必要があります。フォーマットとしては以下のような記述が必要になります。 [pypi] username = <PyPIユーザー名> password = <PyPIパスワード> そのためechoコマンドで~/.pypircに書き込んでいます。 deploy_to_pypi.yml steps: ...略 # Set PyPI account setting. - name: Set PyPI account setting env: PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | echo -e '[pypi]' >> ~/.pypirc echo -e "username = ${PYPI_USERNAME}" >> ~/.pypirc echo -e "password = ${PYPI_PASSWORD}" >> ~/.pypirc YAMLでは|のパイプの記号を使うことで、改行を維持したまま複数行の文字列を扱えるのでrun:のコマンド部分で利用しています。 参考 : How do I break a string in YAML over multiple lines? PyPIへのアップロードのコマンドを設定する Pythonスクリプト上でパッケージのビルドを行い、PyPIのアカウント設定も終わったのでPyPIへのアップロード用のコマンドをYAMLに追加します。requirements.txtにtwine(PyPIへのアップロード用のPythonライブラリ)を指定済みでそちらもインストールされているのでtwineのアップロードのコマンドを実行するだけです。 deploy_to_pypi.yml steps: ...略 # Upload to PyPI. - name: Upload to PyPI run: twine upload dist/* なお、既にアップロード済みのバージョンの場合はコマンドでエラーになるので、Pythonのパッケージルートの__init__で定義されている__version__の値は更新しておく必要があります。マイナーバージョンを上げるのか、メジャーバージョンを上げるのかなどの判断が必要なためここは自動化せずに手動でアップデートする形を想定しています。 リリースノートの追加も自動化する GitHubのReleasesの設定も自動化しておきます。個人開発のライブラリなので細かいリリースノートの内容は設定を省略しつつも、今まではmainへのpush時点で毎回バージョンタグ設定などをしていたのでその辺りのみ設定します。 actions/create-releaseというマーケットプレイスのものを利用します。 GitHub Action - Releases API deploy_to_pypi.yml steps: ...略 # Create release note for GitHub page. - name: Create release note uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: v${{ env.VERSION }} release_name: v${{ env.VERSION }} draft: false prerelease: false バージョンの値は事前に環境変数に設定しているので${{ env.VERSION }}という指定で参照できます。v0.1.0的なフォーマットでタグなどに設定したかったので直前にvと付けています。 GitHubのトークン値の設定が必要になりますが、SecretsのUI上で設定しなくてもGitHub Actions上では${{ secrets.GITHUB_TOKEN }}という名前でアクセスができるようなのでそちらを参照します。 これでmainブランチへのpushの度にバージョン番号に応じたリリースが以下のように作成されます。 カバレッジのバッジをREADMEに追加する 事前にpytestの実行結果のカバレッジを環境変数に設定しているので、そちらを参照してワークフローが実行される度にテストカバレッジの値が反映されてREADME上に表示されるようにしておきます。 RubbaBoy/BYOBというマーケットプレイスのものを使います。BYOBは「Bring Your Own Badge」の略のようです。 Bring Your Own Badge 以下のように設定します。 deploy_to_pypi.yml steps: ...略 # Update test coverage badge on README. - name: Test coverage badge setting uses: RubbaBoy/BYOB@v1.2.0 with: NAME: coverage LABEL: 'Coverage' STATUS: ${{ env.COVERAGE }} COLOR: 0088FF GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NAME:部分の設定はREADME上で指定する際にこの値が必要になります。今回はcoverageとしました。 LABEL:の設定はバッジの左側のラベルに反映されます。 STATUS:の値はバッジの右側に反映されます。今回は事前に設定しておいたテストカバレッジの環境変数の値(${{ env.COVERAGE }})を設定しています。 COLOR:はバッジの右側の背景色となります。今回は水色にしました。 GITHUB_TOKEN:の値はリリースノートの対応のときと同様、GitHub Actions上では設定無しにトークンにアクセスできるのでそのまま${{ secrets.GITHUB_TOKEN }}で対応できます。 また、README側にも設定が必要になります。 バッジを設定したいところに![](https://byob.yarr.is/<ユーザー名>/<リポジトリ(ライブラリ)名>/<NAME: で指定した名前>)記述を追加します。 ユーザー名やリポジトリ名はGitHubの対象のリポジトリのURLなどをご確認ください。今回はhttps://github.com/simon-ritchie/apyscというURLなので、 ユーザー名 : simon-ritchie リポジトリ名 : apysc YAML上のNAME:の値 : coverage としてあるので、![](https://byob.yarr.is/simon-ritchie/apysc/coverage)という指定をREADMEに追加しました。 後の節で実際に流しますが、ワークフローの処理を通すと以下のような表示になります。 ※Deploy to PyPIのバッジは現時点ではまだ対応していません。後の節で触れます。 実際に動かしてみる ここまでで大体の必要なものは対応が終わったので実際に動かしてみましょう。ライブラリバージョンをアップしてmainブランチにpushしてみます。今回は__init__.pyのバージョンの定義を0.10.20に上げました。 GitHub上のUIからActionsメニューを開くと、ワークフローの一覧が表示されます。先ほどpushしたものが黄色になっていて、実行中なことが確認できます(ワークフローが問題無く通った場合には緑、失敗した場合は赤、実行中は黄色になります)。 対象のワークフローをクリックするとYAMLで指定したjobs:ごとのリストが表示されます。今回はDeploy:とだけ指定してあるのでDeployだけ表示されています。 ちなみにワークフローを流しながらこの記事を書いていたため、その間に流れてきってしまいました。スクショのようにこの画面ではワークフローが成功したのか、全部流れるまでにどのくらい時間がかかっているのかなどが表示されます。 Jobsの一覧にあるもの(今回はDeploy部分)をクリックすると、そのジョブの各stepsの内容の確認や各コマンドの標準出力内容なども確認できます。実行されたコマンド内容や標準出力を確認するには各name:設定の部分をクリックすると展開されます(スクショではPythonバージョン確認のコマンド結果などを確認しています)。また、右の方に各ステップでの処理時間が表示されるのでどの処理が長く時間がかかっているのかが確認しやすくなっています。 こちらの表示は実行中でもリアルタイムに確認することができます。 無事通っていれば諸々の処理やリリースノート・バッジの表示などが更新されます(バッジの更新は若干のラグがあったりもするようです)。 ワークフローが通ったかどうかのバッジを追加する 各ワークフローが通ったのか、失敗しているのかのバッジを追加します。この機能はGitHub公式自体がUIで提供しているのでそちらを使います。 Actionsのページにアクセスして、左側のワークフローの一覧で対象のワークフローをクリックします(今回はDeploy to PyPI)。 右の方にある...のメニューをクリックして、Create status badgeを選択します。 そうするとバッジ用のマークダウンが表示されるのでコピーしてREADMEに貼り付けます。 これでREADME上にワークフロー名が設定されたバッジが表示されるようになり、直前のワークフローがちゃんと通っているのか失敗しているのかなどがさくっと確認できるようになります。 おまけ : ワークフロー失敗 / 成功時の通知について SlackかDiscordにワークフローの結果を通知するようにしようか・・・と最初考えていたのですが、特に何も設定しなくても失敗した場合にアカウント登録時に指定したメールに通知が飛ぶようになりました。また、リリースノートを追加している?せいなのか無事リリースされた時にも通知が来るようになりました(手動でリリースノートを追加していたころには特に通知がありませんでしたが、bot経由のものは通知がされる?ようです)。 とりあえず個人で使う分には通知はこれで十分か・・・と思ったためDiscord対応などは見送りました。よしなに自動でメール設定してくれるのは楽で助かります。 参考サイトまとめ What is the use of the pipe symbol in YAML? [GitHub]Actions Workflowへのtimeout指定のススメ Checkout setup-python V2 Environment Variables from Dotenv Github ActionsでPyPIにパッケージを公開する How do I break a string in YAML over multiple lines? GitHub Action - Releases API Bring Your Own Badge
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む