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

Pythonの辞書キーを引用符で囲うか?

1. はじめに

 記事を執筆していたら辞書キーを引用符で囲う場合と囲わない場合の2つがあってややこしいと思いました。今回はこのことを記事にしたいと思います。あとこの記事を書くきっかけになったPythonでf文字列を用いて辞書の値を表示させるも読んでいただけると筆者がサンシャイン池崎みたいに喜びます。

2. 辞書キーを引用符で囲うか?

 以下のコードを見てください。結論をいうとf文字列を使う場合は引用符が必要でformatメソッドを使う場合は引用符を使ってはいけません。

sample.py
dict = {"age": 20}

print(f'私は{dict["age"]}歳です')
print('私は' + str(dict['age']) + '歳です')
print('私は' + '{0[age]}'.format(dict) + '歳です')

3. まとめ

 結論は先程も述べたとおり「f文字列を使う場合は引用符が必要でformatメソッドを使う場合は引用符を使ってはいけない」です。たったこれだけのことですがプログラミング初心者の自分はこの違いに気づかず1時間頭を抱えていました。一緒にエラーを1つずつ乗り越えていきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pygameでゲームを作ってみる4<アニメーション編 Part.2>

はじめに アニメーションと得点システムの改善を目指す旅に出た... 進捗 できること ・テキストを表示する ・残り時間をカウントし、表示する ・得点を計算し、表示する ・画像を表示する ・画像上をクリックしたときに「+1」を表示する ・画像上をクリックしたときに1点加える ・画像を上下に動かし続ける  NEW ・残り時間が0になったときに画像の動きを止める ・プレーリードッグが出現して消える間は1点しか獲得できないようにする コード全文 import pygame from pygame.locals import * import sys Scr_rect = Rect(0, 0, 500, 500) class DogSprite(pygame.sprite.Sprite): move_height = 100 def __init__(self, filename, x, y, vx, vy): self.image = pygame.image.load(filename).convert() width = self.image.get_width() height = self.image.get_height() self.rect = Rect(x, y, width, height) self.vx = vx self.vy = vy self.up = self.move_height self.down = self.up + self.move_height self.enabled = True def update(self, rest): self.rect.move_ip(0, self.vy) if self.rect.top < self.up or self.rect.top > self.down: self.vy = -self.vy if rest == 0: self.vy = 0 def is_clicked(self, pos): if self.enabled and self.rect.collidepoint(pos): self.enabled = False return True return False def reset(self): if self.rect.top > self.down: self.enabled = True def draw(self, screen): colorkey = self.image.get_at((0,0)) self.image.set_colorkey(colorkey,RLEACCEL) screen.blit(self.image, self.rect) def main(): pygame.init() cl = pygame.time.Clock() screen = pygame.display.set_mode(Scr_rect.size) pygame.display.set_caption("Game") font = pygame.font.SysFont(None, 78) font2 = pygame.font.SysFont(None, 45) font3 = pygame.font.SysFont(None, 30) text1 = font.render("Click!", True, (204, 102, 112)) text2 = font2.render("+1", True, (0, 0, 0)) click = 0 score = 0 n_frames = 0 TIME_END = 10 line = pygame.image.load("Game\line.png").convert() colorkey = line.get_at((0,0)) line.set_colorkey(colorkey,RLEACCEL) #Spriteを作成 ob1 = DogSprite("Game/dg.jpg", 50, 200, 0, -2) while (1): screen.fill((102, 204, 194)) screen.blit(text1, [170, 30]) screen.blit(line,(50, 180)) rest = TIME_END - (n_frames / 30) text4 = font3.render('time: {}'.format(round(rest,1)), True, (0, 0, 0)) screen.blit(text4,[350,60]) ob1.update(rest) ob1.draw(screen) for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if n_frames < 30 * TIME_END and event.type == MOUSEBUTTONDOWN and event.button == 1: if ob1.is_clicked(event.pos): click = 9 score += 1 text3 = font3.render('score : {}'.format(score), True, (0, 0, 0)) screen.blit(text3, [350, 40] ) if click > 0: pos = pygame.mouse.get_pos() screen.blit(text2, pos) click -= 1 ob1.reset() pygame.display.update() if n_frames < 30 * TIME_END: n_frames += 1 cl.tick(30) if __name__ == "__main__": main() おわりに 大雑把に形になってきたので嬉しい。(大いなる力をお借りした)(ありがとう!) 空行含めだけど行が100超えてちょっと感動(?) 次回は、画像(プレーリードッグ)をランダムに出現させる...!?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#1 PythonなチャットボットとJupyterLabの理解 |リーガテックの神を目指す駆け出しITコンサルがインフラエンジニアに転身するまで

背景

……そこで、せっかくですから、私のルーツであるインフラ系に立ち返り、法律に詳しいインフラエンジニアになるべく(なんだそれは)、独自に技能の研鑽を始めることにしました。
そして、夜は司法書士試験の勉強です。もうパーフェクトですよね。私はリーガテックの神になる、これはもう、間違いがございません。

それをQiitaに投稿する目的は、

  • 同じような習熟度の仲間が見れば役に立つ
  • アウトプットをした方が定着する

というだけです。頑張ってまいります!

PJ概要

諸元 内容
言語 python
インフラ heroku
目的 slack連繋のチャットボット

現在、私が簡単に設計したものを同期が作ってくれたアプリがあり、内容としては上記の表のような感じです。

しかしながら、heroku環境では500時間しか無料では動かせないようであり、止まってしまいました。そこで、AWSの研鑽も兼ねて、これをAWSに移行してみよう!というチャレンジをします。

JupyterLabの便利なショートカット

アプリ側の実装者・同期さまの環境はJupyterLab(Anacondaに附属しているのですね。初めて知りました)。まずはこれを理解せねばなりません。

コマンド 結果
Shift+Etr セル内のコードを実行して下に追加
Ctrl+Etr セル内のコードを実行
D^2 セルを削除
A 上にセルを追加
B 下にセルを追加
Ctr+/ コメントアウト
途中まで書いてTab オートコンプリートによる補完

他にもここを叩くと色々出てくるようです。すごい。

スクリーンショット_2021-03-08_12_38_12.png

その他気になった点はこんな感じです。
(分かりやすいReadme.mdに助かる。。。)

  • どうも、terminalみたいなイメージで使って良いようmasu.Chromeから開くとあたかもクラウドサービスのような感じになりますが、実際にはローカルホストで動いてます。
  • .ipynbはあくまでもノートブック。実行ファイル部分を出力するには、メニューからexport as executable。すると、実行できない部分はコメントアウトされた状態で、`.pyが出力されるよ。
  • Variable Inspectorというextensionはおすすめ。インストールしたら右クリックで使えるよ。

わからない!

JLabを見渡しつつ、本体となるpythonのコードも読み込みます。

いくつか、見覚えのない点が、、

import Botって?

from slackbot.bot import Bot, respond_to
という記載が。importは分かるのですが、Botって何でしょう。
respond_toも聞いたことが。。ない。。

from slackbot.bot import Bot
これが根拠となっていそう。ここで、fromimportの区別ができていない自分に気づく……
……

Pythonでライブラリやモジュールを読み込む際には「import」および「from」を使いますが、この2つは使い方がややこしく、違いをあまり分からずに使っている方もいると思います。

ですよね〜!(下記参考文献より)

……ということで、モジュールをバイパスしてクラスとか関数を直接importする指令だったみたい。
やったぜ。

defってなんだ!?

pythonはちょっとだけやったことがあったのですが……defという表現を使った覚えがありません。
鍛錬が足りませんね。これも調べます。

色々みましたがこれが格別に分かりやすいです。
いままで、classは使ったことがありましたがdefは使っていませんでした。VB.netで世界観が止まっていたのだと実感します。
classより細かい単位化ができるということですね!

この辺でpythonコードを読解

疑問点はこれで解消したので、ここで同期さまが書いてくれたコードを読解!しようと思います。

スクリーンショット_2021-03-08_21_18_09.jpg
スクリーンショット_2021-03-08_21_37_03.jpg
スクリーンショット_2021-03-08_21_50_14.jpg

……なんか理解できた気がします!

気になった点

スクリーンショット_2021-03-08_21_56_16.jpg

Spreadsheetからデータを取ってくるコードの冗長性が高そうです。
効率化できないか次回以降検討してみます!

次回予告

次は、①冗長性問題の解決に取り組み、さらに、②現在の実行環境であるherokuの理解を目指します!

応用的だけどすごい勉強になる学習内容

@respond_toって何?という、知らなくても良いけど知ったらプロになれる疑問に答えてくれます。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#1 PythonなチャットボットとJupyterLabの理解 |(中略)な駆け出しITコンサルがインフラエンジニアになるまで

背景

◯◯の◯◯◯◯……

(契約上の理由により中略!)

……そこで、せっかくですから、私のルーツであるインフラ系に立ち返り、法律に詳しいインフラエンジニアになるべく(なんだそれは)、独自に技能の研鑽を始めることにしました。
そして、夜は司法書士試験の勉強です。もうパーフェクトですよね。私はリーガテックの神になる、これはもう間違いございません。

それをQiitaに投稿する目的は、

  • 同じような習熟度の仲間が見れば役に立つ
  • アウトプットをした方が定着する

というだけです。頑張ってまいります!

PJ概要

諸元 内容
言語 python
インフラ heroku
目的 slack連繋のチャットボット

現在、私が簡単に設計したものを同期が作ってくれたアプリがあり、内容としては上記の表のような感じです。

しかしながら、heroku環境では500時間しか無料では動かせないようであり、止まってしまいました。そこで、AWSの研鑽も兼ねて、これをAWSに移行してみよう!というチャレンジをします。

JupyterLabの便利なショートカット

アプリ側の実装者・同期さまの環境はJupyterLab(Anacondaに附属しているのですね。初めて知りました)。まずはこれを理解せねばなりません。

コマンド 結果
Shift+Etr セル内のコードを実行して下に追加
Ctrl+Etr セル内のコードを実行
D^2 セルを削除
A 上にセルを追加
B 下にセルを追加
Ctr+/ コメントアウト
途中まで書いてTab オートコンプリートによる補完

他にもここを叩くと色々出てくるようです。すごい。

スクリーンショット_2021-03-08_12_38_12.png

その他気になった点はこんな感じです。
(分かりやすいReadme.mdに助かる。。。)

  • どうも、terminalみたいなイメージで使って良いようmasu.Chromeから開くとあたかもクラウドサービスのような感じになりますが、実際にはローカルホストで動いてます。
  • .ipynbはあくまでもノートブック。実行ファイル部分を出力するには、メニューからexport as executable。すると、実行できない部分はコメントアウトされた状態で、`.pyが出力されるよ。
  • Variable Inspectorというextensionはおすすめ。インストールしたら右クリックで使えるよ。

わからない!

JLabを見渡しつつ、本体となるpythonのコードも読み込みます。

いくつか、見覚えのない点が、、

import Botって?

from slackbot.bot import Bot, respond_to
という記載が。importは分かるのですが、Botって何でしょう。
respond_toも聞いたことが。。ない。。

from slackbot.bot import Bot
これが根拠となっていそう。ここで、fromimportの区別ができていない自分に気づく……
……

Pythonでライブラリやモジュールを読み込む際には「import」および「from」を使いますが、この2つは使い方がややこしく、違いをあまり分からずに使っている方もいると思います。

ですよね〜!(下記参考文献より)

……ということで、モジュールをバイパスしてクラスとか関数を直接importする指令だったみたい。
やったぜ。

defってなんだ!?

pythonはちょっとだけやったことがあったのですが……defという表現を使った覚えがありません。
鍛錬が足りませんね。これも調べます。

色々みましたがこれが格別に分かりやすいです。
いままで、classは使ったことがありましたがdefは使っていませんでした。VB.netで世界観が止まっていたのだと実感します。
classより細かい単位化ができるということですね!

この辺でpythonコードを読解

疑問点はこれで解消したので、ここで同期さまが書いてくれたコードを読解!しようと思います。

スクリーンショット_2021-03-08_21_18_09.jpg
スクリーンショット_2021-03-08_21_37_03.jpg
スクリーンショット_2021-03-08_21_50_14.jpg

……なんか理解できた気がします!

気になった点

スクリーンショット_2021-03-08_21_56_16.jpg

Spreadsheetからデータを取ってくるコードの冗長性が高そうです。
効率化できないか次回以降検討してみます!

次回予告

次は、①冗長性問題の解決に取り組み、さらに、②現在の実行環境であるherokuの理解を目指します!

応用的だけどすごい勉強になる学習内容

@respond_toって何?という、知らなくても良いけど知ったらプロになれる疑問に答えてくれます。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでf文字列を用いて辞書の値を表示させる (2021年3月9日一部訂正のため追記)

1. はじめに

 Rubyには式展開という便利なツールがありPythonにも同じものがあればいいなと思っていたらPythonにもf文字列という形でありました。今回はf文字列を用いて辞書の作成などを行っていこうと思います。

(注1)f文字列はPython3.6で導入された機能です。それ以前のバージョンでは使用できません。

(注2)rubyの式展開とは#{}のことです。

sample.rb
age = 20
puts '私の年齢は#{age}です'

2. f文字列を用いて辞書の値を表示させる

 まずは辞書をつくります。

sample1.py
dict = {}

dict["名前"] = "山田太郎"
dict["年齢"] = 20

 次に「私の名前は山田太郎です。」という文と「私の年齢は20歳です。」という文章をf文字列を用いて出力してみます。

sample2.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict["name"]}です。')
print(f'私の年齢は{dict["age"]}です。')

# 出力結果
# 私の名前は山田太郎です。
# 私の年齢は20です。

 f文字列を使い辞書の値を出力させる際の注意点が1つあります。それはf文字列を囲う引用符と辞書のキー囲む引用符と同じ引用符は使えないということです(例外あり 例外はまとめの後に記載)。同じ引用符を使うとエラーになります。自分はしばしばf文字列と辞書を囲う引用符を同じにしてエラーになっています。
(2021年3月9日追記 例外をまとめの後に記載)

sample3.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict['name']}です。')
print(f'私の年齢は{dict['age']}です。')

# 出力結果
# SyntaxError: invalid syntax

 また辞書のキーを引用符で囲わなくてもエラーになります。

sample4.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict[name]}です。')
print(f'私の年齢は{dict[age]}です。')

# 出力結果
# NameError: name 'name' is not defined

3. まとめ

 Python3.6以降ではf文字列を用いて辞書の値を出力させることができます。
あと余談で詳しく書いたのですが、コードの可読性はf文字列の方が良いのでf文字列を使ったほうが良いと思います。

注3(2021年3月9日訂正のため追記)

 この記事を読んでいただいた方からf文字列を囲う引用符と辞書のキー囲む引用符と同じ引用符を使える場合があることを教えていただきました。本当にありがとうございます!そしてここで訂正させていただきます。本当に申し訳ありません。
 以下のコードのようにf文字列全体を三重引用符(""")で囲み辞書のキーをダブルクオーテーション(")で囲むとf文字列全体を囲う引用符と辞書のキーを囲う引用符を同じにできるそうです。いや、お見事!

sample5.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f"""私の名前は{dict["name"]}です。""")
print(f"""私の年齢は{dict["age"]}です。""")

余談 f文字列 VS 文字連結

sample6.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print('私の名前は' + dict['name'] + 'です。')
print('私の年齢は' + str(dict['age']) + 'です。')

# 出力結果
# 私の名前は山田太郎です。
# 私の年齢は20です。

 2つの例を比較すると前者のf文字列を用いたほうがコードの可読性が良いと思います。「私は山田太郎、20歳、大学1年生です。」という1つの文に「名前」、「年齢」、「学校種別」、「学年」の4つの変数がある文を出力しようとするとさらにf文字列のありがたみが分かると思います。

sample6.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20
dict["school"] = "大学"
dict["grade"] = 1

# f文字列を使った場合
print(f'私は{dict["name"]}{dict["age"]}歳、{dict["school"]}{dict["grade"]}年生です。')

# f文字列を使わなかった場合
print('私は' + dict['name'] + '、' + str(dict['age']) + '歳、' + dict['school'] + str(dict['grade']) + '年生です。')

# 出力結果
# 私は山田太郎、20歳、大学1年生です。
# 私は山田太郎、20歳、大学1年生です。

参考文献

Pythonの文字列が標準でf文字列になる(かも)
【Python】文字列の埋め込みにはf文字列を使うべき

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでf文字列を用いて辞書の値を表示させる

1. はじめに

 Rubyには式展開という便利なツールがありPythonにも同じものがあればいいなと思っていたらPythonにもf文字列という形でありました。今回はf文字列を用いて辞書の作成などを行っていこうと思います。

(注1)f文字列はPython3.6で導入された機能です。それ以前のバージョンでは使用できません。

(注2)rubyの式展開とは#{}のことです。

sample.rb
age = 20
puts '私の年齢は#{age}です'

2. f文字列を用いて辞書の値を表示させる

 まずは辞書をつくります。

sample1.py
dict = {}

dict["名前"] = "山田太郎"
dict["年齢"] = 20

 次に「私の名前は山田太郎です。」という文と「私の年齢は20歳です。」という文章をf文字列を用いて出力してみます。

sample2.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict["name"]}です。')
print(f'私の年齢は{dict["age"]}です。')

# 出力結果
# 私の名前は山田太郎です。
# 私の年齢は20です。

 f文字列を使い辞書の値を出力させる際の注意点が1つあります。それはf文字列を囲う引用符と辞書のキー囲む引用符と同じ引用符は使えないということです。同じ引用符を使うとエラーになります。自分はよくf文字列と辞書を囲ういっm

sample3.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict['name']}です。')
print(f'私の年齢は{dict['age']}です。')

# 出力結果
# SyntaxError: invalid syntax

 また辞書のキーを引用符で囲わなくてもエラーになります。

sample4.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print(f'私の名前は{dict[name]}です。')
print(f'私の年齢は{dict[age]}です。')

# 出力結果
# NameError: name 'name' is not defined

3. まとめ

 Python3.6以降ではf文字列を用いて辞書の値を出力させることができます。
あと余談で詳しく書いたのですが、コードの可読性はf文字列の方が良いのでf文字列を使ったほうが良いと思います。

余談 f文字列 VS 文字連結

sample5.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20

print('私の名前は' + dict['name'] + 'です。')
print('私の年齢は' + str(dict['age']) + 'です。')

# 出力結果
# 私の名前は山田太郎です。
# 私の年齢は20です。

 2つの例を比較すると前者のf文字列を用いたほうがコードの可読性が良いと思います。「私は山田太郎、20歳、大学1年生です。」という1つの文に「名前」、「年齢」、「学校種別」、「学年」の4つの変数がある文を出力しようとするとさらにf文字列のありがたみが分かると思います。

sample6.py
dict = {}

dict["name"] = "山田太郎"
dict["age"] = 20
dict["school"] = "大学"
dict["grade"] = 1

# f文字列を使った場合
print(f'私は{dict["name"]}{dict["age"]}歳、{dict["school"]}{dict["grade"]}年生です。')

# f文字列を使わなかった場合
print('私は' + dict['name'] + '、' + str(dict['age']) + '歳、' + dict['school'] + str(dict['grade']) + '年生です。')

# 出力結果
# 私は山田太郎、20歳、大学1年生です。
# 私は山田太郎、20歳、大学1年生です。

参考文献

Pythonの文字列が標準でf文字列になる(かも)
【Python】文字列の埋め込みにはf文字列を使うべき

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoでアプリケーション名を変えたらmigrateができなくなってしまったときの対処方法をメモ

環境

Python 3.8.7
Django==2.2

エラー内容

アカウントの編集とかの操作するアプリケーション名を変更して

python manage.py makemigrations

をしたら

Traceback (most recent call last):
略
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration socialaccount.0001_initial is applied before its dependency account_manage.0001_initial on database 'default'.

とエラーが発生。

python manage.py migrate

も同じエラー。
そこで、エラー文をググってみると、

があった!
書いてあるように、

python manage.py showmigraions

をやったら、前に作ったアプリケーションでmigrateしたデータが残ってた、、、。そりゃそうだな。

解決方法

すべてのアプリケーションのmigrations直下のinit.py以外のファイルを削除。

python manage.py makemigrations
python manage.py migrate

をしたら動いた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

基礎編:キューとスタック

自己紹介

初めまして。
本誌を訪問頂き有難うございます。
趣味で python を始めた中年おじさんです。
基礎から始めたくて記事を書き始めた今日この頃です。
本誌は基礎編 第四弾です。

第一弾 基礎編:木構造の全探索(DFS/BFS)
第二弾 基礎編:二分探索
第三弾 基礎編:再帰処理

全部わからなくてもいい。
気が向いたときに改めて向き合えば良いと思っているので
ゆるーく、やっていきます。

学習目標

今回は Leetcode:Queue & Stack の理解を目標とします。
どのように理解するかは、以下のアプローチで進めます。

テンプレートの暗記 =>
記述の理解(イメージ作成) =>
自分なりのアプローチで再検証 =>
理解の穴を埋める

試行錯誤を繰り返すことで理解が深まり、思考力が養われると思います。

例題 * Queue & BFS

image.png
上図のグラフ構造のデータを探索してみましょう!!
A から start して G に行ってみましょう!!

解説

まずはグラフ構造を作成し、
再帰処理で bfs を実現するケースと
Queue で bfs を実現するケースの 2 つを用意しました。

graph_search.py
class node:
    def __init__(self,name):
        self.name = name
        self.visit = False
        self.adjust = []

def visit(node):
    print(f"node:{node.name}")
    node.visit = True

def recursive(node):
    if not node:
        return 
    visit(node)
    for i in node.adjust:
        if not i.visit:
            recursive(i)

def queue(node):
    q = [node]
    while q:
        nums = q.pop(0)
        if not nums.visit:
            visit(nums)
        for i in nums.adjust:
            q.append(i)

n_a = node("a")
n_b = node("b")
n_c = node("c")
n_d = node("d")
n_e = node("e")
n_f = node("f")
n_g = node("g")

n_a.adjust = [n_b,n_c,n_d]
n_b.adjust = [n_e]
n_c.adjust = [n_e,n_f]
n_d.adjust = [n_g]
n_f.adjust = [n_g]

#recursive(n_a)
queue(n_a)
実行結果.py
node:a
node:b
node:c
node:d
node:e
node:f
node:g

演習 Walls and Gates

m x n の配列に初期値を代入して差し上げます。
初期値の意味は以下になります。

・-1 : 壁、もしくは障害物
・ 0 : 門
・Inf: 2^31 -1 = 2147483647 を Inf と定義し、空き部屋と同義

空き部屋には、最寄りの門からの距離を埋めてください。
もし、門に辿り着けない場合は INF を入れてください。
grid.jpg

answer_image.py
#Example1
Input: rooms = [
[2147483647,-1,0,2147483647],
[2147483647,2147483647,2147483647,-1],
[2147483647,-1,2147483647,-1],
[0,-1,2147483647,2147483647]]

Output: [
[3,-1,0,1],
[2,2,1,-1],
[1,-1,2,-1],
[0,-1,3,4]
]

#Example 2:
Input: rooms = [[-1]]
Output: [[-1]]

#Example 3:
Input: rooms = [[2147483647]]
Output: [[2147483647]]

#Example 4:
Input: rooms = [[0]]
Output: [[0]]

解説

過去に似たようなことをやっていたので
やりやすかったです。=> 過去の記事: 迷路に迷ってみた

maze.py
class Solution():
    def wallsAndGates(self, rooms):
        """
        input: List[int][int]
        rtype: List[int][int]
        """
        m = len(rooms)
        n = len(rooms[0])

        if  m == 0:
            return rooms

        #add all gates to a queue
        queue = []
        for i in range(m):
            for j in range(n):
                if rooms[i][j] == 0:
                    queue.append((i,j))

        while queue:
            row, col = queue.pop(0)
            for i in range(4):
                r,c = row + [1,0,-1,0][i],col+[0,1,0,-1][i]
                if r < 0 or c < 0 or r >= m or c >= n or rooms[r][c] != 2147483647:
                    continue
                rooms[r][c] = rooms[row][col] + 1
                queue.append((r,c))

演習 Number of Islands

プレゼントした m x n の配列では 1 は陸、0 は水になりますので、島の数を返してください。
島とは水で囲まれており、1 を水平、垂直方向につながって形成されている物を指します。
枠の外は水で囲まれていると想定して oK です。

answer_image.py
#Example 1:
Input: grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
Output: 1
#Example 2:
Input: grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
Output: 3

制約:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] is '0' or '1'.

解説

今回は障害物で囲まれていることを利用します。
つまり、1 である場合は、それを 0 に塗り替える。
塗り替えたら、上下左右も同じように塗り替える。

しかし、枠の制限を超えたり、もともと 0 であれば、
塗りつぶす作業が Stop します。

2 重 for 分で 1 である個所を探し、
前述の強制 stop が掛かる塗りつぶし作業を何回行ったか?
これが大陸の数になります。

island.py
class Solution():
    def numIslands(self,grid):
        islands = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == "1":
                    islands += 1
                    self.part_of_island(i,j,grid)
        return islands

    def part_of_island(self,i,j,grid):
        if i < 0 or j < 0 or i == len(grid) or j == len(grid[0]) or grid[i][j] != "1":
            return
        else:
            grid[i][j] = "0"

        for k in range(4):
            i,j = i+[1,0,-1,0][k],j+[0,1,0,-1][k]
            self.part_of_island(i,j,grid)

演習 Open the Lock

lock_image.jpg
図にあるように南京錠をプレゼントします。南京錠には 4 つのダイヤルがあり、
それぞれ'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' の合計 10 個を設定できます。
各ダイヤルは自由に回して数字を設定できます。例えば、'9' から '0', '0' から '9' のように変えることが出来ます。

ダイヤルの初期値は "0000" です。
この "0000" は 4 つのダイヤルの文字を並べて構成されています。

注意点として、特定のコードになると、
ダイヤルを回すことが出来なくなり、鍵を開けることが出来なくなります。

target がカギを解除するコードになります。
鍵を開けるための最低何回ダイヤルを回す必要があるか教えて下さい。
開けられない場合は -1 を返してください。

answer_image.py
#Example 1:
Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
#Explanation:
#A sequence of valid moves would be "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202".
#Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would be invalid,
#because the wheels of the lock become stuck after the display becomes the dead end "0102".

#Example 2:
Input: deadends = ["8888"], target = "0009"
Output: 1
#Explanation:
#We can turn the last wheel in reverse to move from "0000" -> "0009".

#Example 3:
Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output: -1
#Explanation:
#We can't reach the target without getting stuck.

#Example 4:
Input: deadends = ["0000"], target = "8888"
Output: -1

解説

openLock.py
class Solution(object):
    def openLock(self, deadends, target):
        def neighbors(node):
            for i in range(4):
                x = int(node[i])
                for d in (-1, 1):
                    y = (x + d) % 10
                    yield node[:i] + str(y) + node[i+1:]

        dead = set(deadends)
        queue = collections.deque([('0000', 0)])
        seen = {'0000'}
        while queue:
            node, depth = queue.popleft()
            if node == target: return depth #鍵が解除できれば return depth
            if node in dead: continue       #lock code に該当したら pass
            for nei in neighbors(node):     #neighbors を使って該当案をリスト
                if nei not in seen:         #過去に確認した記録がないことを確認
                    seen.add(nei)           #確認したことを記録
                    queue.append((nei, depth+1)) #depth をインクリメント
                                                 #nei を buff して次回 neighbors に渡して案を作ってもらう
        return -1

該当案の組み合わせをどうやって捻出するかを neighbor で
処理しているので切り出して動かしてみます。

neighbors.py
def neighbor(node):
    for i in range(4):
        x = int(node[i])
        for d in (-1,1):
            y = (x+d)%10
            yield node[:i] + str(y) + node[i+1:]

for i in neighbor("0000"):
    print(i)

今回はテストで "0000" としていますが、
[0] ~ [3] を一つずつ取り出してある処理をします。
それは取り出したデータを (+1 or -1) %10 とします。
%10 としているのが味噌です。
これにより -1 % 10 = 9 , 1 %10 = 1 を
[0] ~ [3] に順番に代入していきます。
それが以下の実行結果です。

neighbor_result.py
#実行結果
9000 #i = 0 , d = -1
1000 #i = 0 , d =  1
0900 #i = 1 , d = -1
0100 #i = 1 , d =  1
0090 #i = 2 , d = -1
0010 #i = 2 , d =  1
0009 #i = 3 , d = -1
0001 #i = 4 , d =  1

因みに yield に関しては以下のサイトが分かりやすかったです。

演習 Perfect Squares

整数 n をプレゼントしますので、正方形の総和で n となる最小の正方形の数を教えてください。
勿論、正方形は整数で 1,4,9,16... を意味しており、3 や 11 ではありません。

answer_image.py
#Example 1:
Input: n = 12
Output: 3
#Explanation: 12 = 4 + 4 + 4.

#Example 2:
Input: n = 13
Output: 2
#Explanation: 13 = 4 + 9.

制約:
1 <= n <= 10^4

解説

answer_sample.py
class Solution:
    def numSquares(self, n):

        # list of square numbers that are less than `n`
        square_nums = [i * i for i in range(1, int(n**0.5)+1)]
        # if n = 4 , int(4**0.5)+1 = 3, squre_nums = [1,4]
        # if n = 15, int(15**0.5)+1= 4, squre_nums = [1,4,9]

        level = 0
        queue = {n}
        while queue:
            level += 1
            #! Important: use set() instead of list() to eliminate the redundancy,
            # which would even provide a 5-times speedup, 200ms vs. 1000ms.
            next_queue = set()
            # construct the queue for the next level
            for remainder in queue:
                for square_num in square_nums:    
                    if remainder == square_num:
                        return level  # find the node!
                    elif remainder < square_num:
                        break
                    else:
                        next_queue.add(remainder - square_num)
            queue = next_queue
        return level

リストを使った以下の記述でも行ける気がするのですが、
time over となります。

numSquare.py
class solution:
    def numSquare(self,n):
        base = [i*i for i in range(1,int(n**0.5)+1)]

        q = [n]
        level = 0
        length = 0

        while q:
            level += 1
            length = len(q)
            for _ in range(length):
                nums = q.pop(0)
                for i in base:
                    if i == nums:
                        return level
                    elif i < nums:
                        q.append(nums-i)

set() の使い方を整理して list 以外の表記でも
理解できるようにしましょう。

演習 Min Stack

スタックがサポートしている push/pop/top を作ってみましょう。
・push(x) -- スタックにデータを push !!
・pop() -- スタックから top data を取り出します
・top() -- top data を確認
・getMin() -- スタックから最小値を取り出す

解説

answer.py
class MinStack:

    def __init__(self):
        self.stack = []

    def push(self, x: int) -> None:
        if not self.stack:
            self.stack.append((x,x))
            return
        current_min = self.stack[-1][1]
        self.stack.append((x,min(x,current_min)))

    def pop(self) -> None:
        self.stack.pop()

    def top(self) -> int:
        return self.stack[-1][0]

    def getMin(self) -> int:
        return self.stack[-1][1]

演習 Valid Parentheses

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.

answer_image.py
#Example 1:
Input: s = "()"
Output: true
#Example 2:
Input: s = "()[]{}"
Output: true
#Example 3:
Input: s = "(]"
Output: false
#Example 4:
Input: s = "([)]"
Output: false
#Example 5:
Input: s = "{[]}"
Output: true

解説

isValid.py
#approach 1
class Solution:
    def isValid(self, s: str) -> bool:
        while "()" in s or "{}" in s or "[]" in s:
             #左から順番に処理
             #      1番目 ↓     2番目 ↓           3番目 ↓
            s=s.replace("()","").replace("{}","").replace("[]","")
            # "[{()}]"=> remove "()" => 
            #  "[{}]" => remove "{}" =>
            #   "[]"  => remove "[]" => ""   
        return s == ""

#approach 2
def isValid(s):
    maps = { ")":"(",  "]":"[",  "}":"{"}
    q = []

    for i in s:

        if i in maps:
            #if s = "]", q has no data to pop to top.
            #in that case we have to output dummy data to reach the goal(to output False)
            top = q.pop() if q else "#"
            print(q,top)
            if maps[i] != top:
                return False
        else:
            q.append(i)

    return not q

演習 Daily Temperatures

温度 T を daily で記録したリストを差し上げます。
そのリストを元に、何日待てば温度が上昇するか、各日を基準に考えたリストに直して返してください。
もし、今後、その日以上に温かくなることが見込まれない場合は 0 を入力してください。

例えば、 T = [73, 74, 75, 71, 69, 72, 76, 73] を与えられたとすると
output は [1, 1, 4, 2, 1, 1, 0, 0] になります。

メモ:温度は 1 以上 30000 以下とし、日数は 30 以上 100 以下とします。

解説

ansawer_sample.py
class Solution:
    def dailyTemperatures(self, T):
        ans = [0] * len(T)
        stack = [] #indexes from hottest to coldest
        for i in range(len(T) - 1, -1, -1):
            while stack and T[i] >= T[stack[-1]]:
                stack.pop()
            if stack:
                ans[i] = stack[-1] - i
            stack.append(i)
        return ans  

DailyTemperatures.gif
GIF で追いかけてみました。

演習 Evaluate Reverse Polish Notation

逆ポーランド記法で算術表現を評価してください。
有効な演算子は +, -, *, / です。
Each operand may be an integer or another expression.
各オペランドは整数もしくは他の数式にすることが出来ます。

注意:
割り算は小数点以下は切り捨て。

answer_image.py
#Example 1:
Input: ["2", "1", "+", "3", "*"]
Output: 9
#Explanation: ((2 + 1) * 3) = 9
#Example 2:
#Input: ["4", "13", "5", "/", "+"]
Output: 6
#Explanation: (4 + (13 / 5)) = 6
#Example 3:
Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
Output: 22
#Explanation: 
#  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
#= ((10 * (6 / (12 * -11))) + 17) + 5
#= ((10 * (6 / -132)) + 17) + 5
#= ((10 * 0) + 17) + 5
#= (0 + 17) + 5
#= 17 + 5

解説

answer_image.py
class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        operations = {
            "+": lambda a, b: a + b,
            "-": lambda a, b: a - b,
            "/": lambda a, b: int(a / b),
            "*": lambda a, b: a * b
        }
        stack = []
        for token in tokens:
            if token in operations:
                number_2 = stack.pop()
                number_1 = stack.pop()
                operation = operations[token]
                stack.append(operation(number_1, number_2))
            else:
                stack.append(int(token))
        return stack.pop()    

今回は lambda の使い方と、その呼び出し方を勉強させてもらいました。
演算子を除いて、基本は append していきます。
演算子が出てきたら、pop を 2 回行って、演算を行います。

すばらしい!!勉強になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで辞書から探す時のTips(個人的)

概要

Pythonの辞書から探す時、Keyが存在しないとエラーが出てしまう。

今まではifで回避していたが、そういえばexcept KeyErrorでも回避できるので、ごく簡単にまとめる。

内容

環境

macOS Catalina
Python 3.7.0

準備

tmp.py
names = {'tokino': 'sora', 'sakura': 'miko'}
print(names['suzuki'])

ここで、names['suzuki']とすると、エラーが出てしまう。

解決策1 ifを使う

tmp.py
if 'suzuki' not in names:
    names['suzuki'] = 'not yet'
print(names['suzuki'])

解決策2 tryを使う

tmp.py
try:
    print(names['suzuki'])
except KeyError:
    names['suzuki'] = 'not yet'
    print(names['suzuki'])

蛇足

単にデフォルト値を欲しいだけなら、

print(names.get('suzuki', 'not yet'))

デフォルト値を設定したいだけなら、
defaultdictを使うことで解決するかもしれない。

私の例はあまりいい例ではないと思う。

参考にさせていただいた本・頁

特になし

感想

書き方の幅が広がって良かった。

今後

tryの方を使った方が自然な時は、使っていこうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder ABC194振り返り(灰コーダー AB2完:C保)

注意:この記事は競プロ初心者が茶色を目指すために復習や反省するだけの記事です。使用言語はPython3で現状のパフォーマンスは200-300程度です。
あくまで自分の備忘録ですが同レベルの方の新しい発見に繋がれば嬉しいです。

結果

A問題 正解
B問題 正解(ミスが多く再提出と時間がかかった)
C問題 やりたいことは分かったが計算量の面でTLEになり不正解

B問題

N人の人がそれぞれ仕事A,BをAi分Bi分で終わらせることができる。その時最短で終わる時間を求める問題。別の人が仕事をするならA,Bの長いほうが終わる時間になり同じ人が仕事をするならA,Bの和が終わる時間になる。

まず根本的な考え方のミスとしてAの時間最小値とBの時間最小値と同一人物の合計の最小値を求めそれらを比較したがそれだと不正解になることに気が付くのが遅かった。
例)
1.A1=4 B1=4
2.A2=8 B2=5

この時一人目に仕事を任せると4+4で8分となってしまう。そのうえ最小値で求めても4分と4分で答えが4分になるが実際A1とB2で5分が一番早く仕事が終わる。
2<N<1000の条件があったため総当たりで考えても計算量は全く問題なかったことに気が付くのが遅かった。

C問題

N個の整数が与えられる。各要素同士の2乗の和を求める。
やることは分かったのでやってみたらTLEになった。

C.py
#TLEになったコード
N=int(input())
num=input().split(" ")
all=0
for a in range(N):
    for b in range(a,N):
        if a!=b:
            all=all+(int(num[a])-int(num[b])**2
print(all)

与えられる数値に|A|<200という制約があったためそれを利用するらしい。
N<3×10^5なのでO(N^2)だと10^10を超え実行速度に問題が出るパターンがある。
しかし-200<A<200なら各要素の2乗のパターンは多く見積もって400×400で160000しかない。
(a-b)^2で求めようとしていたものを考え方を変え、S^2となるSの量を求める。
(雑な例だがS=1が2個,S=3が3個なら1*2+3*2=11となる)

C問題、かなり考えていますが今の理解はここまでなのでいったん保留にします。
他の過去のC問題などもこなしてから再考しようと思います。

身についたこと

・二つ以上の要素でN>10^5の可能性がある時計算量が多くなるためほかの面を利用する。
 今回のC問題の場合は要素数Nではなく要素の範囲-200<i<200を用いてfor文を回す。(C問題)
・全検索で10^10までの回数なら全検索もあり。(B問題)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エクセルファイル(.xlsx)からヒストグラムを作成

環境

macOS Cataina
10.15.7

python 3.6.2

ソースコード

2xy_hist.py
import matplotlib.pyplot as plt
import xlrd
import numpy
import matplotlib.ticker as tick # 目盛り操作に必要なライブラリを読み込み 
import sys

plt.rcParams["font.size"] = 14

if __name__ == '__main__':
    book = xlrd.open_workbook('2xy.xlsx')  # ブック名
    sheet1 = book.sheet_by_name('Sheet1') 


    X = []
    Y = []
    Z = []

##################シート読み込み#############                                               
    # セルを cell(行番号, 列番号) で指定(0から数える)                                         \
    for row in range(0, sheet1.nrows):
        valuex = sheet1.cell(row, 0).value
        if  valuex  != '':
            X.append(valuex)
    X = numpy.array(X)

    for row in range(0, sheet1.nrows):
        valuey = sheet1.cell(row, 1).value
        if  valuey  != '':
            Y.append(valuey)
    Y = numpy.array(Y)


#3講座目がある場合
#    for row in range(0, sheet1.nrows):
#        valuez = sheet1.cell(row, 2).value
#        if  valuez  != '':
#            Z.append(valuez)
#    Z = numpy.array(Z)

fig2, axes = plt.subplots(2, 2)

print(X)
print(Y)
print(Z)

#sys.exit()
#################xのヒストグラム############
#plt.hist(X, bins=20,color="0.3", range=(0,100))  
axes[0,0].hist(X, bins=20,color="0.3", range=(0,100))
axes[0,0].grid(linestyle="--",which="minor")
axes[0,0].grid(which='major',color='black',linestyle='--')
#axes[0,0].set_title('2x')
axes[0,0].set_xlabel('score')
axes[0,0].set_ylabel('frequency')  
axes[0,0].set_xlim(0,100)
axes[0,0].set_ylim(0,10)
axes[0,0].xaxis.set_minor_locator(tick.MultipleLocator(5)) 
axes[0,0].yaxis.set_minor_locator(tick.MultipleLocator(1))
muX = numpy.mean(X)
meX = numpy.median(X)
sigmaX = numpy.std(X) 
varianceX = numpy.var(X)
# 平均値の線
axes[0,0].axvline(muX, linewidth=2, color='k',label="average",linestyle='solid')
#中央値                                                                                                                    
axes[0,0].axvline(meX, linewidth=2, color='k',label="median",linestyle='dashdot')                                        
axes[0,0].legend(loc='upper right')
axes[0,0].text(0,10.1, r'''
2x,μ=%.2f, me=%.2f, σ=%.2f''' %(muX, meX, sigmaX))   


#plt.show()

#################yのヒストグラム############
#plt.subplot(2,2,3)
#plt.hist(Y, bins=20,color="0.3", range=(0,100))
axes[0,1].hist(Y, bins=20,color="0.3", range=(0,100))
axes[0,1].grid(linestyle="--",which="minor")
axes[0,1].grid(which='major',color='black',linestyle='--')
#axes[0,1].set_title('2y')
#axes[0,1].set_xlabel('score')
#axes[0,1].set_ylabel('frequency')
axes[0,1].set_xlim(0,100)
axes[0,1].set_ylim(0,10)
axes[0,1].xaxis.set_minor_locator(tick.MultipleLocator(5))
axes[0,1].yaxis.set_minor_locator(tick.MultipleLocator(1))
muY = numpy.mean(Y)
meY = numpy.median(Y)
sigmaY = numpy.std(Y)
varianceY = numpy.var(Y)
# 平均値の線  
axes[0,1].axvline(muY, linewidth=2, color='k',label="average",linestyle='solid')
#中央値
axes[0,1].axvline(meY, linewidth=2, color='k',label="median",linestyle='dashdot')
axes[0,1].legend(loc='upper right')
axes[0,1].text(0,10.1, r'''
2y,μ=%.2f, me=%.2f, σ=%.2f''' %(muY, meY, sigmaY))


#3講座目がある場合
##################zのヒストグラム############
#axes[1,0].hist(Z, bins=20,color="0.3", range=(0,100))
##plt.hist(Z, bins=20,color="0.3", range=(0,100))                                           
##sys.exit()                                                                                 
#axes[1,0].grid(linestyle="--",which="minor")
#axes[1,0].grid(which='major',color='black',linestyle='--')
##axes[1,0].set_title('2z')
#axes[1,0].set_xlabel('score')
#axes[1,0].set_ylabel('frequency')
#axes[1,0].set_xlim(0,100)
#axes[1,0].set_ylim(0,10)
#axes[1,0].xaxis.set_minor_locator(tick.MultipleLocator(5))
#axes[1,0].yaxis.set_minor_locator(tick.MultipleLocator(1))
#muZ = numpy.mean(Z)
#meZ = numpy.median(Z)
#sigmaZ = numpy.std(Z)
#varianceZ = numpy.var(Z)
# 平均値の線                                                                                                                                                               
#axes[1,0].axvline(muZ, linewidth=2, color='k',label="average",linestyle='solid')
#中央値                                                                                                                                                                    
#axes[1,0].axvline(meZ, linewidth=2, color='k',label="median",linestyle='dashdot')
#axes[1,0].legend(loc='upper right')
#axes[1,0].text(0,10.1, r'''                                                                                                                                                
#2z,μ=%.2f, me=%.2f, σ=%.2f''' %(muZ, meZ, sigmaZ))

axes[1,0].axis('off')

#################xyzのヒストグラム############                                                                                                          
Z = numpy.append(Z, X)
Z = numpy.append(Z, Y)
print(Z) # [1, 2, 3, 4, 5] 
axes[1,1].hist(Z, bins=20,color="0.3", range=(0,100))
axes[1,1].grid(linestyle="--",which="minor")
axes[1,1].grid(which='major',color='black',linestyle='--')
#axes[1,1].set_title('2xy')
axes[1,1].set_xlabel('score')
axes[1,1].set_ylabel('frequency')
axes[1,1].set_xlim(0,100)
#axes[1,1].xticks(range(5, 10))
axes[1,1].set_ylim(0,20)
axes[1,1].xaxis.set_minor_locator(tick.MultipleLocator(5))
axes[1,1].yaxis.set_minor_locator(tick.MultipleLocator(1))
muZ = numpy.mean(Z)
meZ = numpy.median(Z)
sigmaZ = numpy.std(Z)
varianceZ = numpy.var(Z)
# 平均値の線
axes[1,1].axvline(muZ, linewidth=2, color='k',label="average",linestyle='solid')
#中央値
axes[1,1].axvline(meZ, linewidth=2, color='k',label="median",linestyle='dashdot')
axes[1,1].legend(loc='upper right')
axes[1,1].text(0,20.1, r'''
2xy,μ=%.2f, me=%.2f, σ=%.2f''' %(muZ, meZ, sigmaZ))
plt.show()

完成しました
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python development and production environment with debugging in Docker

Developing your application in an isolated environment, like a Docker container, makes it easy to run on different platforms. Developer on different Operating Systems like MacOS, Window or Linux can install the development environment easy and fast with one command to run. No "Works on my machine" problems anymore, you can focus on write your application instead of setup your environment.

We setup a development environment for Python projects using Docker with separated development and production environment. As an example, we create a Flask web application. On the development build we install a debugger and setup remote debug into the Docker container. The production build installs a dedicated web server running our application.
We use the Docker Multistage Build feature to separate a development and production build but using the same Dockerfile.
To debug the application we use Visual Studio Code with Microsoft Python Extension.

Let's get started.

The Project Folder

First we create a Python Project with the following structure.

MyFlaskApp/
├ app.py
├ docker-compose.yaml
├ Dockerfile
└ requirements.txt

The project folder contains the file app.py with Flask application code, the Dockerfile to build the docker images, docker-compose.yaml is used to setup our development environment and requirements.txt contains a list with python modules needed for our Flask application.
Simply create empty files first, we fillup the content in the following paragraphs.

mkdir MyFlaskApp
cd MyFlaskApp
touch app.py
touch docker-compose.yaml
touch Dockerfile
touch requirements.txt

The Application Code

In the app.py file, we just copy & paste the demo HelloWorld application from the Flask documentation for simple demonstration.

app.py
from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello, World!'


if __name__ == "__main__":
    app.run(
        debug=True,
        host='0.0.0.0',
        port=5000
    )

The following block

@app.route('/')
def hello_world():
    return 'Hello, World!'

assign the route / to return the static string "Hello World!".
The last block

if __name__ == "__main__":
    app.run(
        debug=True,
        host='0.0.0.0',
        port=5000
    )

starts the Flask application when the script got executed from the command line (e.g. python app.py or python -m app)

The Dockerfile

Copy & Paste the following lines into the Dockerfile.

Dockerfile
FROM python:3.7-alpine AS base

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

ENV FLASK_ENV="docker"
ENV FLASK_APP=app.py
EXPOSE 5000

# Development Stage
FROM base AS develop
RUN pip install debugpy
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1

# Production Stage
FROM base AS production
RUN pip install --no-cache-dir gunicorn
COPY . .
CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:5000", "app:app"]

We use a Docker feature called Multistage Builds which allows us to build multiple stages (Docker images) with one Dockerfile. Here we build 3 stages:
- base: install required dependencies
- development: install additional development dependencies (e.g. a debugger)
- production: copy the source files into the image and installs a proxy server for production build

The Base Stage

We declare our base stage in the first line inside the Dockerfile.

FROM python:3.7-alpine AS base

The FROM keyword specifies on environment we want to build our application. Since we are creating a python application, we choose a pre build python 3.8 Docker image. The Operating System depends on what we want to develop. We choose Alpine, a lightweight Ubuntu distribution.
With the suffix AS base we declare a stage with the name base. The name is free to choose.
The following commands are related to the base stage until another FROM commands appears.
WORKDIR /app creates a folder for the application in the image and change into that folder.
In in the following two lines

COPY src/requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

we copy the python module dependencies list into the folder app/ and install the dependencies.
With the following three lines we define some environment variables specific for flask.

ENV FLASK_ENV="docker"
ENV FLASK_APP=app.py
EXPOSE 5000

In the line ENV FLASK_ENV="docker" we define an environment for which we can define a .env setting file with settings (e.g. database login information) which flask is looking up
automatically on startup. We don't use the .env file in this tutorial, but keep the structure for additional settings in the future. The second line ENV FLASK_APP=app.py tells flask, which file to run on startup. With the last line EXPOSE 5000 we set the port of the flask application to 5000 which we already set in the app.py file.

The Development Stage

In the development stage we reuse the base stage but install the debugger additionally. We define the development stage with the following line.

FROM base as develop

The following commands are related to the develop stage until another FROM commands appears.

As python debugger we install debugpy.

RUN pip install debugpy

Then we do some python setup, specific for our development environment.

# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1

The first line ENV PYTHONDONTWRITEBYTECODE 1 disable the python byte-code generation, an optimization step which should be enabled in the production environment but in the development with frequent code changes results in increased memory on hard disc. If you feel, that your python runs too slow during development, you can enable this setting. The second line ENV PYTHONUNBUFFERED 1 disable the buffer for logs and send messages directly to the docker container runtime.
We don't copy any source files into the docker image, the source code is linked from our local directory with the docker container directly on startup. The setup is done in the docker-compose.yaml file, which we fill up in later.

The Production Stage

In the production stage we don't want a debugger installed and shipped with our production release. So we start again from the base stage.

FROM base as production

We install gunicorn as HTTP server for accessing the application in production.

RUN pip install --no-cache-dir gunicorn

Then we copy our source files into the image, because they don't change anymore for this specific production build version.
bash
COPY . .

In the last line we run the gunicorn HTTP server on startup, who starts our app.py application and listen to port 5000 which we already set in the base stage.

CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:5000", "app:app"]

The Docker Compose File

We use Docker Compose to setup our development environment. In the docker-compose.yaml paste the following.

docker-compose.yaml
version: "3.9"

services:
  flask-app:
    image: my_flask_app-develop
    container_name: my_flask_app-develop
    build:
      context: .
      target: develop
    ports:
      - 5000:5000
      - 5678:5678
    volumes:
      - .:/app
    environment:
      - FLASK_DEBUG=1
    entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app",  "--wait-for-client", "--multiprocess", "-m", "flask", "run", "--host", "0.0.0.0", "--port", "5000" ]
    networks:
      - my_flask_app-develop

networks:
  my_flask_app-develop:
    name: my_flask_app-develop

The following paragraphs explain each section the setup

The Services Section

We define all services to use in the services part of the file. We only setup our Flask application here, but could add additional services like a database service in a separate docker image.

With the following lines we define our Flask application service.

services:
  flask-app:
    image: my_flask_app-develop
    container_name: my_flask_app-develop

image tells docker the tag to assign when the image is build. This helps us to find the image later in the image list with the command docker images.
With container_name we give the container a name on startup. If we don't set a name here, randomly assign a name to the spawned container.

Under the build section we tell docker what to build.

    build:
      context: .
      target: develop

The docker-compose.yaml file is located in our application directory and context points to that directory to look for the Dockerfile with the build instructions. With target we say which stage to build. In our case the development stage.

The ports section defines the ports to use.

    ports:
      - 5000:5000
      - 5678:5678

Here we map the local ports 5000 and 5678 to the same ports in the container. Port 5000 is for our Flask application to connect, allowing us to open a browser and view the application under http://localhost:5000. Port 5678 is used by the debugger to listen for debug requests.

With volumes we assign our local application folder with the container.

    volumes:
      - .:/app

This allows us to change the source code without the need to rebuild our docker image after every change.

The environment section allows us to define environment variables.

    environment:
      - FLASK_DEBUG=1

Here we enable the debug mode in Flask, which shows us detailed error messages in the browser instead of an error-page.

entrypoint is the most important section and defines what to run on startup.

    entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app",  "--wait-for-client", "--multiprocess", "-m", "flask", "run", "--host", "0.0.0.0", "--port", "5000" ]

We run the debugger first listening on port 5678. Then we assign our Flask application to the debugger and tell the debugger to wait for a client to connect on port 5678. The multiprocess flag allows us to access our running application beside debugging on port 5000 as well. The last part starts our Flask application on port 5000.

The Networks Section

The networks part let us define networks and assign them to services allowing us to control the communication between different services. We define only one network for our Flask application here. For detailed information and how to establish a connection between Docker containers see the Networking in Docker Compose Documentation.

services:
  flask-app:
    # ...
    networks:
      - my_flask_app-develop

networks:
  my_flask_app-develop:
    name: my_flask_app-develop

The Requirements File

The file requirements.txt lists all python modules we need in order to run our flask application. This time the only module we need is flask, so copy and paste the following into the requirements.txt file.

requirements.txt
Flask

We don't set any specific version of flask here, it is up to you if you want to set a fixed version instead (e.g. Flask==1.1.2).

Start the Development Environment

To run our development environment, we only need to run the following command inside our application directory.

Terminal
docker-compose up -d

This builds the develop stage docker image and spawn the docker container of the application. To stop the application container run the following command.

Terminal
docker-compose down

If you want to run docker compose in the foreground, omit the -d flag. You can stop the application then with the CTRL+C key shortcut.

Start Debugging

We use Visual Studio Code to debug our application.

If you want to use PyCharm instead of Visual Studio Code, you need a PyCharm Professional Edition license in order to remote debug into a Docker container. The free PyCharm Community Edition do not support remote debugging.

First we got into our application folder and start Visual Studio Code in that folder.

Terminal
cd MyFlaskApp
code .

If you get an error message code: command not found, then open Visual Studio Code by clicking on the program icon and start the command palette with Command+Shift+P on a Mac (Ctrl+Shift+P on Windows or Linux) and type "Shell Command", then select "Shell Command: Install 'code' command in PATH".

This should open Visual Studio Code with the contents of our application folder.

Install the Python Extension

Click on the Extensions icon on the left side. In the search field write "python" and select and install the Microsoft Python Extension in order to debug.
python_extension.png

Setup Remote Debugging

The debug configuration is saved in the file launch.json inside the folder .vscode/.
The steps to setup your remote debugging differs in order to if a launch.json file already exist in your project folder or if you have to create a new file.

If you are using Visual Studio Code Version 1.54.1 there is a bug not showing Python as debug configuration as explained in the following steps. A workaround is to generate a launch.json file with arbitrary configuration (e.g. choose NodeJS) and then overwrite the contents with the launch.json provided in Add the Debug Configuration to an existing launch.json file

Generate the Debug Configuration File

If you don't have launch.json file, you can generate one with the following steps.
Click on the Debug icon on the left side, then create a launch.json file.

debug_setup1.png
A launch.json file do not exist

If you successfully installed the Python Extension, then you should be able to select Python from the debug configuration list.

debug_setup2.png
Create a launch.json file

Then select Remote Attach.

debug_setup3.png

Then you need to select the remote debugger to connect. We use the defaults host name localhost and port 5678.

debug_setup4.png
debug_setup5.png

Add the Debug Configuration to an existing launch.json file

I you have already a launch.json file in the project directory, you can open the file and add the following configuration,

launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "python",
            "request": "attach",
            "connect": {
                "host": "localhost",
                "port": 5678
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "."
                }
            ]
        }
    ]
}

or you can also generate the configuration with the following steps.
Click on the Debug icon on the left side, then you should see a debug configuration combo-box instead. Click the down arrow and select Add Configuration....

debug_add_configuration.png
A launch.json already exist

If you successfully installed the Python Extension, then you should be able to select Python from the debug configuration list.

debug_add_configuration2.png
Add configuration to existing launch.json

Then select Remote Attach.

debug_setup3.png

Then you need to select the remote debugger to connect. We use the defaults host name localhost and port 5678.

debug_setup4.png
debug_setup5.png

Set a breakpoint and attach

Open app.py and set a breakpoint at line 7. Then attach to the debugger by clicking the green play button on the left side. Open the application in the browser by going to the url http://localhost:5000. The application should stop at the break point.
debug.png

You can attach and detach as often as you want.

Build the Production Stage

Building the production stage we run the following command inside our application folder.

Terminal
docker build --target production -t my_flask_app .

With the --target parameter we tell docker which stage to build and with the tag parameter -t my_flask_app we give the image an appropriate name. Important is the . at the end telling Docker where to find our Dockerfile, that is the current directory we are running this command.

You can assign any tag you want to the built image, but take care not to use the same take for development and production build.

List the built Images

You can see the built image in the image list with the docker images command.

Terminal
docker images

REPOSITORY                          TAG          IMAGE ID       CREATED        SIZE
my_flask_app                        latest       a72b11d35c59   2 days ago     52.2MB
my_flask_app-develop                latest       5916db28c0ff   2 days ago     80.5MB

You can also see the difference in image size with debugger (80.5MB) and without debugger (52.2MB).

Run the Production build

To spawn a container running the production image use the following command.

Terminal
docker run -d --rm -p 5001:5000 --name my_flask_app my_flask_app:latest

We map the local port 5001 to the container port 5000 here, allowing us to run the production stage and the development stage at the same time.

Stage URL
development http://localhost:5000
production http://localhost:5001

You can list running container with the following command.

Terminal
docker ps

CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS         PORTS                    NAMES
a8bc12d89d9f   my_flask_app:latest   "gunicorn --reload -…"   7 seconds ago   Up 6 seconds   0.0.0.0:5001->5000/tcp   my_flask_app

To stop the container simply use the command

Terminal
docker stop my_flask_app

If you want to run the command in the foreground, omit the -d flag. You can stop the application then with the CTRL+C key shortcut.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Django][RestFramework]Object of type "AA" is not JSON serializable エラー

生のSQL文を実行したいと考え模索していたが、今回のエラーが解決できたので備忘録として残す。

エラー内容

Object of type "AA" is not JSON serializable
とエラーが出た 
AAはJson形式ではないという内容

解決

views.py
from .models import BaseAPI
from .serializer import BaseAPISerializer
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response


class BaseAPIViewSet_GET(APIView):
    def get(self, request, pk, format=None):
        sql = "SQL文"
        queryset = BaseAPI.objects.raw(sql,pk)
        return Response(queryset.query) #ここが解決ポイント
urls.py
path('user/<int:pk>/test', BaseAPIViewSet_GET.as_view(), name='GET'),

ちなみにSQL文について
変数を使用した複数行に渡ってSQL文を書きたいと考えていた時があったが、

SELECT * FROM api WHERE name1=(SELECT name2 FROM api WHERE id=%s)
views.py
queryset = BaseAPI.objects.raw(sql,[pk]) #ここで実行

SELECTの中にSELECTを書くことが可能なため、一行のSQL文で自在に操作が可能

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習アルゴリズムメモ アンサンブル学習編

アンサンブル学習とは

アンサンブル学習(ensemble learning)とは、名の通り、弱学習器を多数作成し、多数決を取ることによって汎用的なモデルを作成することである。主に決定木を弱学習器としたランダムフォレスト、勾配ブースティングなどがあげられる(弱学習器が決定木である必要はない)。

アルゴリズム

ランダムフォレストは分類および回帰に用いられるアルゴリズムである。
同じ決定木を作成しては意味がない(皆が同じ予測をしてしまう)のでブートストラップサンプリングにより、訓練データを複製する。
ブートストラップサンプリングとは、データから交換ありでデータ数分選び出す手法である。

import numpy as np

def bootstrap(array):
    index_list = [np.random.randint(len(array)) for x in range(len(array))]
    return x[index_list]

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

for i in range(5):
    print(bootstrap(x))
[ 9 10  4 10  1  1  6 10  8  3]
[ 5 10  3  8  5  7  9 10  7  1]
[ 7  6  8 10  5  8  5  7  1  5]
[10  9  6 10  5  6  4  3  2  3]
[ 9  5  4 10  9  4  6  1  9  6]

このようにしてサンプリングされたデータより多数の決定木を作成する。
パラメータにより作成する際にさらに特徴量を減らすこともできる。
多数得られた決定木の中で最も確率が高いラベルが予測値となる。
回帰の場合はデータの平均値が予測値となる。

scikit-learnでの使い方

【回帰】

from sklearn.ensemble import RandomForestRegressor
rf_reg = RandomForestRegressor()
rf_reg.fit(X_train, y_train)
rf_reg.score(X_test, y_test)

【分類】

from sklearn.ensemble import RandomForestClassifier
rf_clf = RandomForestClassifier()
rf_clf.fit(X_train, y_train)
rf_clf.score(X_test, y_test)

パラメータ

scikit-learnでのパラメータ名 概要 初期値 特性
n_estimators 学習器の個数 100 学習器の個数を指定。大きくすると時間がかかるが、精度はよくなる。
max_features 最大特徴量 "auto" 個々の学習器が見る特徴量の個数を指定。分類の場合、sqrt(n_features)、回帰の場合、n_featuresとなる。
max_depth 木の深さ None 木の深さを指定。Noneの場合、純粋になるまで深くなる。

メリット

学習器の多数決を取るので、決定木よりも精度がよくなることが多い。
同時に木を作ることができるので、メモリの許す限り高速で学習が行える。

デメリット

決定木とは違い予測の根拠が分かりづらい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 Mac にnumpy, matplotlibなどが入らない問題の解消法

はじめに M1 Macbook Proが登場して意気揚々として購入したものの,普段多用しているnumpyやmatplotlibをpipでインストールしようとしたら入らなかったので,その解決法(暫定)を記しておきます.(02/19/2020) 時間が経てばライブラリ側からこの問題は解消してくれるはずなので,その時に,今まで自分が何をいじったのかが思い出せるように,ここに記しておきたいと思います. numpy build fail in M1 Big sur 11.1 解消法 A. condaをインストールしてその上に入れる 今回こちらの方法は使いませんが一応紹介します. かくいう僕も以前まではAnacondaをよく使用していましたが,ライブラリがcondaで入らずに泣く泣くpipを使うたびに下の記事が頭に浮かんで心を痛めますし,何よりGitHubでソースコードを公開する場合はrequirements.txtをつけて pip install を促す場合が多いわけですから,Pythonのパッケージ管理はPython公式が推奨するpipに限定する方がいいと思います(この辺りのベストプラクティスは人それぞれだと思いますが) condaとpip:混ぜるな危険 ただ,condaの使用を許すのであれば以下の方法が一番簡単であるかと思います.MiniforgeというArm版に対応したcondaのインストーラーを使ってcondaをインストールし,その上にnumpyやmatplotlibをインストールします.詳しくは以下のサイトに譲ります. M1 macOS で python + numpy/scipy/pandas/matplotlib/jupyterlab 環境構築のメモ(2020/12/24 時点) Miniforge - Install B. tensorflow-macosを入れる tensorflow-macosというMacに最適化されたTensorFlowをインストールすれば,numpyは自動的にインストールすることもできるようです.ただ,これは本来の目的とは異なりますし,如何せんmatplotlibがインストールできない問題は解決しません. Mac-optimized TensorFlow and TensorFlow Addons (GitHub) How to install TensorFlow and NumPy on Apple Silicon (M1) (YouTube) C. Python3.9をインストールする ← これが本命 冒頭で説明したスレッドの回答に以下のようにあります. First of all numpy support is not there for m1 arm based Macs using python 3.8 version. The method to workaround with this is to update your python version to 3.9. This is the latest version of python and can be a bit unstable. If you want to work with tensorflow then do not update to python 3.9 version as they don't support it and support will come up with version 2.5 (estimated). So, if you don't want to use tensorflow and want to use numpy by using pip install then go ahead install python3.9 and make it your default python interpreter. I will recommend you to use Homebrew for installation of python 3.9 as it as easy as running a simple command. 現在のところ,Python3.9用のnumpyやmatplotlibはarmアーキテクチャをサポートしてそうなので,Python3.9をインストールしてその上に載せる形で実現しようと思います. デフォルトで入っているPython $ python --version # /usr/bin/python Python 2.7.16 $ python3 --version # /usr/bin/python3 Python 3.8.2 更地の環境は汚したくないので,pyenvを利用してバージョン管理をします(Pythonのバージョン管理とパッケージ管理のベストプラクティスは何なんでしょうね.この記事に説得力があるので個人的には pyenv+venv を採用しています.) Homebrewのインストール pyenvはHomebrew経由で入れますが,買ったばかりのMacにはHomebrewが入っていないのでインストールします. 去年の年末までは,Homebrewがまだ対応しきっておらず,arm64とx86_64でバイナリが分かれてしまうことから,下の記事にあるようにシェルのアーキテクチャを切り替えて使用する必要がありました. Apple SiliconにおけるHomebrewのベストプラクティス みんなの M1 Mac における Homebrew のベストプラクティス は間違っている しかし,2021年2月現在では公式ページにあるように既にHomebrewのインストーラーはM1 Macをサポートしてくれたので,下記のコマンド一発で入ります(ちなみに今やMacのデフォルトシェルはzshですが,このコマンドの'bin/bash'は気にしなくていいです). $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ちなみに,コマンドラインの最後にHomebrewのパスが通っていないと忠告が出ますが,ご丁寧に~/.zprofileに追記すべき文まで書いてくれているので,この指示に従ってパスを通します("YOUR HOME DIRECTORY PATH"はそれぞれ異なります) command_output Warning: /opt/homebrew/bin is not in your PATH. - Add Homebrew to your PATH in (YOUR HOME DIRECTORY PATH)/.zprofiles: echo 'eval $(/opt/homebrew/bin/brew shellenv)' >> (YOUR HOME DIRECTORY PATH)/.zprofile eval $(/opt/homebrew/bin/brew shellenv) $ echo 'eval $(/opt/homebrew/bin/brew shellenv)' >> ~/.zprofile $ source ~/.zprofile $ brew --version これでHomebrewが使えるようになりました. pyenvのインストール pyenvの仕組みやインストール方法は公式ページに載っているので,この通りにインストールします. 3行目はpyenvの自動起動のコマンドです.もし今後何かのソフトウェアと競合する場合は ~/.zshrc からこの文をコメントアウトすればいいでしょう. $ brew update $ brew install pyenv $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.zshrc $ exec "$SHELL" # シェルの再起動 $ pyenv --version Python3.9のインストール さあ,いよいよPython3.9をインストールします. その前に以下のパッケージのインストールが推奨されているので,従っておきます. # optional, but recommended: $ brew install openssl readline sqlite3 xz zlib pyenv install --listでインストールできるPythonのバージョンを確認できるので,インストールできるPython3.9系を確認しておきます(minicondaもインストールできるんですね,面白い) $ pyenv install --list | grep 3.9 3.9.0 3.9-dev 3.9.1 miniconda-3.9.1 miniconda3-3.9.1 $ pyenv install 3.9.1 # 3.9.1をインストール $ pyenv rehash # 新たなバージョンをインストールしたあとはこれをしないとshimsコマンドが再構成されない 適当なフォルダ内で試してみます. $ pyenv local 3.9.1 $ python version 3.9.1 (set by (CURRENT DIR)/.python-version $ python -m venv .venv $ source .venv/bin/activate $ pip instal numpy matplotlib pandas scikit-learn が,matplotlibだけが入らない... 結局matplotlibはPython3.9でもサポートされていないじゃないか... 根本の問題は numpy そうこうしている内に,numpyはM1 Macでもサポートされたようです.よって,Python3.8でもPython3.9でもpipでインストールできます. matplotlib Apple M1 — Matplotlib, Python, and Jupyter Lab FYI : silicon M1 : installation of matplotlib requires Pillow which requires libjpeg which requires brew とあるように,Homebrewでlibjpegというパッケージをインストールする必要があったようです.こうすると,Python3.9系でもPython3.8系でもmatplotlibが入るようになります. $ brew install libjpeg pyenv導入しなくてもよかったですね.ただいずれにせよHomebrewは必要ですし,pyenvの+venvはベストプラクティスな気がするので,上記のステップも無駄ではないでしょう. pandas, scikit-learn Apple M1, Python, Pandas, and Homebrew pandasやscikit-learnが入らないのは,インストーラがarm64というアーキテクチャ上では動かないからだそうです.よって上の記事にあるようにiTermを開く前に"Open Using Rosetta"にチェックを入れてから起動するようにし(もちろん一度iTermはquitしてから再起動する),pipでインストールすると入るようになります.(M1 Macbook Pro 2020では,arm64とx86_86を切り替えて使えるようになっています.) ちなみに現在のシェルがどのアーキテクチャを使っているかはuname -mというコマンドで確認できます. $ uname -m # Open Using Rosetta にチェックを入れずに起動した場合 arm64 $ uname -m # Open Using Rosetta にチェックを入れて起動した場合 x86_64 しかし,x86_64上でインストールしたpandasやscikit-learnは,もちろんx86_64上で実行しなくてはなりません.アーキテクチャをx86_64にした状態でインストールした後,arm64に戻してシェルを起動すると,その状態では使えないということです. (参考) ARMとx86/x64(Intel, AMD)のCPU、アーキテクチャの違い、シェア、性能比較、アーキテクチャ、エンディアン 追記(4/14/2021) この回答によると,pandasは以下のようにソースコードからインストールすると入るらしいです.(若干のWarningが出ましたが基本的な機能は入りました.) $ pip install numpy cython $ git clone https://github.com/pandas-dev/pandas.git $ cd pandas $ python3 setup.py install numpy, matplotlib, pandas, scikit-learnを全て同時に使いたいときは pandas, scikit-learnはx86_64上でしかインストールできませんし,実行できません.対してnumpy, matplotlibはarm64上でもx86_64上でも動きます.よってこれらを同時に使いたい場合は,インストール時も実行時もアーキテクチャをx86_64に統一しましょう. 現在のところはプロジェクトやディレクトリ単位で,どちらのアーキテクチャ上で行うかを逐一自分で覚えておきながら実行するということになりそうです. まとめ とりあえずは上記の解決方法で,それぞれのライブラリをimportすることはできそうです.numpyのインストーラは仕様変更してくれたみたいですし,そのうちにあらゆるライブラリがM1チップの上でシームレスに動くようになってくれるでしょう.その時を早く待ちたいものです. 余談: 問題3.8系がpyenvで入らない pyenvで色々なバージョンのpythonを入れようとしたとき,3.9.1は入ったのだが,3.8系が入らないという問題があったが,この方法を用いたら入った $ CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" pyenv install --patch 3.8.6 <<(curl -sSL https://raw.githubusercontent.com/Homebrew/formula-patches/113aa84/python/3.8.3.patch\?full_index\=1)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自己紹介】oki_kosukeのページについて【投稿一覧】

自己紹介

簡単な経歴となぜQiitaで記事を書いているのかについてご紹介いたします。

経歴

  • 30代前半
  • 大学院卒(修士)
  • 化学専攻(研究室では有機化合物を用いたデバイスを研究)
  • メーカー勤務
  • エンジニア
  • 使用言語はpython

Qiitaで記事を書くモチベーション

【実験科学者がデータサイエンティストを目指す。 データ科学でものづくりの未来を変えたい!】
私は大学自体から実験系の科学者であり、入社後もプロセス開発業務を中心に従事しておりました。"Cyber"か"Physical"かでいうと完全に"Physical"側の人間でした。ただ、ある時参加したセミナーで「機械学習を使ってプロセス開発を効率的に進める」という内容の講演を聞き、機械学習に興味を持つようになりました。情報収集や勉強を重ねていくうちの機械学習の持つポテンシャルを感じるようになり、実験科学者としての強みやものづくりに携わってきた経験を活かしながら、"Cyber"側のスキルも身につけ、データ科学で未来のものづくりをより良くしていきたいと強く思うようになりました。その思いを実現するために勉強した内容をアウトプットしていこうと思います。

投稿一覧

私が書いた記事の目次です。内容ごとに以下に記載しています。

機械学習関連

データの前処理

データの可視化

主成分分析(PCA)

モデルの構築

モデルの適用範囲

マテリアルズインフォマティクス関連

RDkit

VESTA(結晶構造の可視化)

その他

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポータルサイトからお知らせをLINEに通知する(メモ)

はじめに

スクレイピングを勉強していて、何かアウトプットできないかと思い、大学のお知らせを通知するコードを練習がてら書きました。ただのメモです。全体的にこちらを参考にしました。

ログインコード

このコードで最初のログインをします。

webdriver.get('サイトURL')

    #ユーザー名入力
    webdriver.find_element_by_xpath('//*[@id="userId"]').send_keys("ユーザー名")
    #パスワード入力
    webdriver.find_element_by_xpath('//*[@id="password"]').send_keys("パスワード")

    time.sleep(3)
  
    #クリックボタン
    webdriver.find_element_by_xpath('//*[@id="loginButton"]').click()

スクレイピング

内容をスクレイピングするコードです。

contents=[]
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date1 = details.find_element_by_css_selector('div.date').text
        title1 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date2 = details.find_element_by_css_selector('div.date').text
        title2 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date3 = details.find_element_by_css_selector('div.date').text
        title3 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date4 = details.find_element_by_css_selector('div.date').text
        title4 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date5 = details.find_element_by_css_selector('div.date').text
        title5 = details.find_element_by_css_selector('div.text').text
    contents.append([date1,title1,date2,title2,date3,title3,date4,title4,date5,title5])

    return contents  

contentsにリストとして格納します。for文でまとめたかったのですが、サイトの構造や技術不足でうまくできませんでした。

LINEへの通知

LINE Notifyを使い通知させます。

 method = "POST"
    headers = {"Authorization": "Bearer %s" % LINE_TOKEN}
    payload = {"message": contents}
    try:
        payload = urllib.parse.urlencode(payload).encode("utf-8")
        req = urllib.request.Request(
            url=LINE_NOTIFY_URL, data=payload, method=method, headers=headers)
        urllib.request.urlopen(req)
    except Exception as e:
        print ("Exception Error: ", e)
        sys.exit(1)

全体のコード

import sys

import time
from selenium.webdriver import Chrome, ChromeOptions, Remote

#トークンを指定します
LINE_TOKEN = "LINE_TOKEN"
LINE_NOTIFY_URL = 'LINE_NOTIFY_URL'


def main():

    options = ChromeOptions()
    webdriver = Chrome(options=options)

    logging(webdriver)

    time.sleep(3)

    contents = scrape_contents_list(webdriver)

    send_to_line(contents)




def logging(webdriver):

    webdriver.get('サイトURL')

    webdriver.find_element_by_xpath('//*[@id="userId"]').send_keys("ユーザー名")
    webdriver.find_element_by_xpath('//*[@id="password"]').send_keys("パスワード")

    time.sleep(3)

    webdriver.find_element_by_xpath('//*[@id="loginButton"]').click()


def scrape_contents_list(webdriver):

    contents=[]
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date1 = details.find_element_by_css_selector('div.date').text
        title1 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date2 = details.find_element_by_css_selector('div.date').text
        title2 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date3 = details.find_element_by_css_selector('div.date').text
        title3 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date4 = details.find_element_by_css_selector('div.date').text
        title4 = details.find_element_by_css_selector('div.text').text
    for details in webdriver.find_elements_by_css_selector('セレクター'):
        date5 = details.find_element_by_css_selector('div.date').text
        title5 = details.find_element_by_css_selector('div.text').text
    contents.append([date1,title1,date2,title2,date3,title3,date4,title4,date5,title5])

    return contents   



def send_to_line(contents):
    method = "POST"
    headers = {"Authorization": "Bearer %s" % LINE_TOKEN}
    payload = {"message": contents}
    try:
        payload = urllib.parse.urlencode(payload).encode("utf-8")
        req = urllib.request.Request(
            url=LINE_NOTIFY_URL, data=payload, method=method, headers=headers)
        urllib.request.urlopen(req)
    except Exception as e:
        print ("Exception Error: ", e)
        sys.exit(1)




if __name__ == '__main__':
    main()    

おわりに

通知はしっかり来ました。しかしリスト型で処理されるので、見栄えは良くありませんでした。
今後はsystemdのタイマーなども使い定期的に実行されるようなプログラムをつくりたいと思います。

参考記事

時間割をLINEで自動通知する。(selenium+LINE Notify)

参考文献

Pythonクローリング&スクレイピング[増補改訂版]
―データ収集・解析のための実践開発ガイドー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

E - スプリンクラー, E - SNS のログ

E - スプリンクラー

O(N+M+Q)
無向グラフの問題です。
隣接行列を使っています。

python
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    N, M, Q = list(map(int, input().split()))
    graph = []

    for y in range(0, N):
        row = []
        for x in range(0, N):
            row.append(False)
        graph.append(row)

    for _ in range(0, M):
        u, v = list(map(int, input().split()))
        u = u - 1
        v = v - 1
        graph[u][v] = True
        graph[v][u] = True

    C = list(map(int, input().split()))

    for _ in range(0, Q):
        q = list(map(int, input().split()))

        if q[0] == 1:
            x = q[1] - 1 
            print(C[x])

            for i in range(0, N):
                if graph[x][i]:
                    C[i] = C[x]

        if q[0] == 2:
            x = q[1] - 1 
            y = q[2]
            print(C[x])
            C[x] = y

# エントリポイント
if __name__ == '__main__':
    main()

メモリの制約で隣接行列を使用できない場合、隣接リストを使用します。

python
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    N, M, Q = list(map(int, input().split()))
    graph = []

    for y in range(0, N):
        row = []
        graph.append(row)

    for _ in range(0, M):
        u, v = list(map(int, input().split()))
        u = u - 1
        v = v - 1
        graph[u].append(v)
        graph[v].append(u)

    C = list(map(int, input().split()))

    for _ in range(0, Q):
        q = list(map(int, input().split()))

        if q[0] == 1:
            x = q[1] - 1 
            print(C[x])

            for i in graph[x]:
                C[i] = C[x]

        if q[0] == 2:
            x = q[1] - 1 
            y = q[2]
            print(C[x])
            C[x] = y

# エントリポイント
if __name__ == '__main__':
    main()

E - SNS のログ

O(N)
有向グラフの問題です。

python
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    N, Q = list(map(int, input().split()))
    graph = []

    for y in range(0, N):
        row = []
        for x in range(0, N):
            row.append(False)
        graph.append(row)

    for _ in range(0, Q):
        s = list(map(int, input().split()))

        if s[0] == 1:
            graph[s[1]-1][s[2]-1] = True
        elif s[0] == 2:
            for i in range(0, N):
                if graph[i][s[1]-1]:
                    graph[s[1]-1][i] = True
        elif s[0] == 3:
            to_follow = []
            for i in range(0, N):
                if graph[s[1]-1][i]:
                    for j in range(0, N):
                        if graph[i][j] and s[1]-1 != j:
                            to_follow.append(j)
            for i in to_follow:
                graph[s[1]-1][i] = True

    for i in range(0, N):
        row = ""
        for j in range(0, N):
            if graph[i][j]:
                row += "Y"
            else:
                row += "N"
        print(row)

# エントリポイント
if __name__ == '__main__':
    main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vision クライアントライブラリを使わずに Python3 で Vision API を使う

はじめに

この記事は Vision API を Vision クライアントライブラリ を使わずに Python で JavaScript の Fetch API のように使用するための内容を記載します。

結論

  • 使用する画像は base64 モジュールでエンコード
  • JSON リクエストを作成し、json モジュールから json.dumps() を用いて JSON 文字列に変換する
  • 最後に requests モジュールから requests.post() を使用して Vision API リクエスト(POST)を作成すれば OK

下にサンプルコードを置いておきます。

コード

import requests
import base64
import json
import pprint

GOOGLE_API_KEY = "YOUR API KEY"
GOOGLE_CLOUD_VISION_API_URL = f"https://vision.googleapis.com/v1/images:annotate?key={GOOGLE_API_KEY}"
IMG_PATH = "YOUR PHOTO PATH"
base64Image = base64.b64encode(open(IMG_PATH, "rb").read()).decode('utf-8')

headers = { "Content-Type" : "application/json" }
request_data = json.dumps({
    "requests": [
        {
            "image": {
                "content": base64Image
            },
            "features": [
                {
                    "type": "OBJECT_LOCALIZATION"
                }
            ]
        }
    ]
})

# Vision API にリクエストを行う
response = requests.post(GOOGLE_CLOUD_VISION_API_URL, headers=headers, data=request_data)

# データを表示
pprint.pprint(response.json())

以前行ったスープカレー屋さんの画像を POST した結果を貼っておきます。
カレー屋さんのホームページ - 矢巾店 - スープカレーハウスしっぽ
上のコードで Vision API を通した結果の画像

おわりに

手っ取り早く API を使用したい人はこちらの方法のほうが楽なのではないでしょうか。
参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS IoT】エッジデバイス(ラズパイ、Jetson nano)をAWS IoT Coreでプロビジョニングしてみた

AWS IoTを使ううえで一番大事なエッジ機器とIoT Coreを繋ぐ(プロビジョニング)部分をやってみたので解説します。

はじめに

やりたいことの概略図はこんな感じ。image.png

大まかなプロセス

・ AWSコンソールでデバイス用の証明書と鍵を生成
・ エッジデバイスと通信。エッジデバイスでAWS IoTと通信するためのPython SDKをインストール。
・ エッジデバイスに証明書と鍵を渡す
・ エッジデバイスで今回の肝となるAWS IoTとMQTT通信するためのPythonファイルをいじる
・ Pythonファイルを実行する。証明書と鍵を使ってエッジデバイスからAWS IoT Coreにメッセージをパブリッシュ

このように認証情報をエッジデバイスに渡してそれを使ってエッジデバイスとIoT Coreをとりあえず簡単に通信させるところまでをプロビジョニングと呼びます。

※この記事で説明していない前提条件

・エッジデバイスへのOSのインストール(ラズパイだとRaspbian OS、Jetson nanoだとUbuntuを入れる人が多いです)

・OSの初期設定(SSH接続を設定から許可する必要があるかもしれません)

・エッジデバイスの内部IPアドレスを調べる(今回はCLIのみのheadlessは想定していません。普通にデスクトップありのOSでモニターを使ってやる想定です。またJetson nanoは1万円以上もするのになぜかWifiモジュールがついてないので普通にLANケーブルを刺す必要があります...500円でWifi/BluetoothがついているESP8266を見習って欲しい)

1 AWSコンソールでデバイス用の証明書と鍵を生成

お使いのPC(自分はMacBookで作業しています)でAWSコンソールを開き、「サービス」から「IoT Core」を選択すると以下の画面に行く

左にあるメニューから「モノ」を選択し「作成」をクリックimage.png

名前を適当に決めて(自分は今回はJetson nanoをプロビジョニングしたいので「minagawa_Jetson_nano」としました)、「次へ」を選択。
※「タイプ」や「タグ」はたくさんモノを登録していく際に管理しやすくするため。基本はなしで大丈夫。グループも最初は設定なしで行きます。image.png

証明書は一番上にある「1-Click証明書作成(推奨)」の「証明書を作成」をクリックimage.png

すると以下のような画面になります。画面に示してあるように
・このモノの証明書(自分の場合Jetson nano用の証明書)
・プライベートキー
・AWS IoTのルートCA(サーバー用の証明書)

の3つが必要なので、ダウンロードします(パブリックキーは使わないのでダウンロードしない)。image.png
また、3つ目のルートCAのダウンロードをクリックすると別タブで以下の画面に飛ばされてセキュリティの度合いが違う4つのタイプから証明書を選べます。量子コンピュータが現実のものとなっていない今はそこまでここの違いは気にしなくて良いらしい、と言うことで一番上の「RSA 2048 bit key:Amazon Root CA 1」をダウンロードします。右クリックでリンクを保存するをクリック、名前は「AmazonRootCA1.pem」と言った感じになっているのでそのまま保存。image.png

必要な証明書2つと秘密鍵がダウンロードできました。image.png

これら3つは後でエッジデバイスに渡すので大事に保管しておきます。

AWSコンソールに戻り「有効化」をクリックし、「ポリシーをアタッチ」をクリック。image.png

こんな画面に行きます。
image.pngAWSは権限を与えない限り基本何もできないギスギスした世界なので、新しいモノにはポリシーという形で基本的人権を与えてあげます。Defaultがそれに当たります。チェックを入れて「モノの登録」をクリック。image.png

※S3に直接データを送信したい場合などは新たにポリシーを作って付与する必要があります(ここがAWSの面倒なところ)。実験段階では、「神権限」を作っておいてモノに付与するのも手です(その場合「ポリシーの作成」から「アクション」と「リソースARN」に「*」、効果の「許可」にチェックを入れてポリシーを作成します。名前は例えば「God_Policy」とかいかがでしょう)。defaultだとIoT Coreとの通信しか許可されていません。権限が足りなかったら後から付け足すこともできるので(ポリシーは2つ以上付与できます)、自分はとりあえずこのdefaultで行きます。

下の画像のようにメッセージが出たら無事STEP1証明書と鍵の発行は終わりです。
image.png

※ちなみにもう少し詳しい説明としては、モノにAWSのサービス(例えばS3)へのアクセス権限を当たるポリシーはたった今発行した証明書に紐づけられます。画像のようにラズパイ君が持ってきた証明書に対しAWSが「この証明書は基本的人権が紐づけられてるな」とか「これは神権限が紐づけられてますね、どうぞお通りください」という風に対応する感じです。
image.png

2 エッジデバイスとSSH接続してAWS IoT DeviceSDK Pythonをインストール(&ビルド)する

Jetson nano向けのUbuntuのデスクトップはご覧の通りめちゃくちゃかっこいいですが、今回はMacBookからSSH接続して操作するので使いません。もちろんSSH接続せずエッジデバイスのターミナルで直接コマンドを打っていくのでも大丈夫です。(※WindowsだとSSH接続にPutty等を使います。Jetson nano用のUbuntuディスクイメージはこちらからインストールできます->https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit#write )image.png

Jetson nanoのターミナルで

hostname -I

と打つとLAN内のIPアドレスが表示されます。
ターミナル上で

ssh USER_NAME@IP_ADDRESS

と打つとパスワードを求められるので入力。(ラズパイのユーザー名とパスワードはデフォルトでそれぞれ「pi」「raspberry」です)
※「USER_NAME」と「IP_ADDRESS」にはそれぞれユーザー名とIPアドレスを入れてください。ちなみに自分の場合は

ssh hidehiro@192.168.1.2


と入れました。

無事つながりました。
image.png

3 AWS IoT Device SDK Pythonをエッジデバイスにインストールする

SSH接続ができたのでPython SDKをインストールしていきます。

git clone https://github.com/aws/aws-iot-device-sdk-python.git

でAWS IoT Device SDK for Pythonをインストールします。自分はhome/hidehiro/配下にインストールしました。

ls

でaws-iot-device-sdk-pythonというフォルダができていることを確認します。

cd aws-iot-device-sdk-python/samples/basicPubSub
ls

とすると、basicPubSub.pyがあるのがわかります。これが今回使用する、AWS IoT CoreとMQTT通信するためのPythonファイルです。

その後、

cd aws-iot-sdk-python
sudo python setup.py install

でSDKをビルドします。これでAWS IoT Device SDK Pythonの初期設定は終わりです。

4 エッジデバイスに証明書2つと鍵を渡す

FTP(File Transfer Protocol)のひとつであるFileZillaが便利なので使います。
Mac: https://filezilla-project.org/download.php?platform=osx
Windows: https://filezilla-project.org/download.php?platform=win64
FileZillaを使わない場合はMac,Linuxの場合はftp, scp, sftpコマンドなどでもいけます。

画面はこんな感じ。「Host」にはエッジデバイスのIPアドレス、「Port」には「22」(HTTPSポート)と入れて「Quickconnect」をクリック。image.png
こうなったら成功。
image.png

証明書2つと鍵はaws-iot-device-sdk-python/samples/basicPubSub/配下に入れます。ベストプラクティスではないかもしれませんが、今回は実験なのでとりあえず。

5 Pythonファイルの編集

実際にIoT Coreと通信するためのPythonファイルを編集していきます。先程ダウンロードしたAWS IoT Device SDK for Pythonのフォルダの中にサンプルとなるPythonファイルがたくさん入っています(こういうサンプルファイルをスケルトンとも言います)。

Pythonファイルの編集はSSH上でnano等を使って編集しても良いし、PC上で編集したファイルをエッジデバイス上に(FileZilla等で)書き換えても大丈夫です。最終的にbasicPubSub.pyを動かしていくわけですが、ターミナル上で

python basicPubSub.py -e [AWS IoTのカスタムエンドポイント] -r [ルートCAのファイルパス] -c [デバイス証明書のファイルパス] -k [秘密鍵のファイルパス] 

という風に打つ必要があります。証明書2つと秘密鍵はBasicPubSub.pyと同じファイル配下にあるので、そのままファイル名を入力すれば大丈夫です。AWS IoTのカスタムエンドポイントはどこで確かめるかというと、AWSコンソールのIoT Coreのページに行って、
image.png
左にある「設定」をクリックします(これが超わかりにくい場所にあります。AWS IoTエンドポイントは「設定」にあります。もう一度繰り返します。AWS IoTのエンドポイントは「設定」の場所にあります。悪名高いAWSコンソールの中でもなかなかの凶悪さです)

a2v4hij6s98s19-ats.iot.us-east-1.amazonaws.com

みたいな感じで、「英数字.iot.あなたの設定したAWS IoTリージョン.amazonaws.com」という感じになっています。これをコピーして適当なところに保存しておきます。

次にいよいよPythonファイルを編集していきます。
PubSub.pyを開いて30行目くらいにそれぞれ入力する箇所があるので、4つとも入力していきます。image.png

※このPythonファイル内に、MQTTのトピックの設定とか、パブリッシュする頻度、PublishするJSONファイルの編集なども含まれていますが、今回は長くなりすぎるので省きます。

6 Pythonファイルの実行

いよいよPythonファイルを実行してAWS IoT Coreにメッセージをパブリッシュしていきましょう。今回はパブリッシュするJSONファイルをいじっていないのでHello AWSみたいな味気ないメッセージですが、我慢してください(笑)

cdコマンドでBasicPubSub.pyがあるところまで行きます。その後、先程説明した

python basicPubSub.py -e [AWS IoTのカスタムエンドポイント] -r [ルートCAのファイルパス] -c [デバイス証明書のファイルパス] -k [秘密鍵のファイルパス] 

を打ちます(自分の場合は「python basicPubSub.py -e a2iw7bft7mqw59-ats.iot.us-east-1.amazonaws.com -r AmazonRootCA1.pem -c 9fd87ec2c7-certificate.pem.crt -k bfd901fa1f-private.pem.key」と言った感じになりました)。

成功すると、以下のような文字列が毎秒更新されていきます。
image.png

7 AWS IoT Coreでメッセージを確認

それに成功したら、AWSコンソールからAWS IoT Coreに行って、「テスト」から「MQTTテストクライアント」に行きます。トピックはbasicPubSub.py内でデフォルトで設定してある「sdk/test/Python」と入力し、「Subscribe」をクリックします。
image.png

サブスクライブできるとこんな感じ。
image.png

そして先程basicPubSub.pyの実行が成功していると、以下のように毎秒エッジデバイスからメッセージが届いているはずです。やった!image.png

終わりに

これだけでもだいぶ長くて大変だったかもしれませんが、AWS IoTマスターの旅は始まったばかりです。ポケモンで言うとマサラタウンから出発して最初の草むらでコラッタと戦っているくらい。次回はトキワの森に行ってRule Engineを使ってIoTらしいことをやっていきたいと思います。

※間違いなどあれば指摘していただけると幸いです。不明点も以下へ。できるだけ対応します。
Email:hidehiro.minagawa@stylez.co.jp

※ちなみにこの記事を書くのには主にUdemyのStephen Borsayの「Exploring IoT」を参考にしました。
https://www.udemy.com/course/exploring-aws-iot/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Microsoft Graph API でクラウドサービスのデータを取得する

Microsoft Graph API でクラウドサービスのデータを取得する

はじめに

Mac環境の記事ですが、Windows環境も同じ手順になります。環境依存の部分は読み替えてお試しください。

目的

Microsoft Graph (REST API) を使用して各サービスのデータを取得します。この記事では ユーザーなしでアクセスを取得SharePointOneDrive の方法をご紹介します。

  • Microsoft 365 サービス: Delve, Excel, Microsoft Bookings, Microsoft Teams, OneDrive, OneNote, Outlook/Exchange, Planner, SharePoint, Workplace Analytics
  • Enterprise Mobility + セキュリティサービス: Advanced Threat Analytics, Advanced Threat Protection, Azure Active Directory, Identity Manager, Intune
  • Windows 10 サービス: アクティビティ, デバイス, 通知, ユニバーサル印刷 (プレビュー)
  • Dynamics 365 Business Central

この記事を最後まで読むと、次のことができるようになります。

No. 概要 キーワード
1 認証トークン の種類 ユーザーなしでアクセスを取得
2 Microsoft Graph の初期設定 アプリの登録
3 Microsoft Graph の使い方 SharePoint, OneDrive

実行環境

環境 Ver.
macOS Catalina 10.15.6
Python 3.7.3
requests 2.25.1

ソースコード

実際に実装内容やソースコードを追いながら読むとより理解が深まるかと思います。是非ご活用ください。

GitHub

関連する記事

認証トークン の種類

この記事では ユーザーなしでアクセスを取得 の方法をご紹介します。

ユーザーの代わりにアクセスを取得 (リンク)

  • 対話形式でアクセス許可を与える (ログイン認証)
  • 利用者側でアプリのアクセスを許可する
  • GUIアプリ等で利用

ユーザーなしでアクセスを取得 (リンク)

  • あらかじめ管理者がアクセス許可を与える
  • 管理コンソールでアプリのアクセスを許可する
  • バッチ処理等で利用

Microsoft Graph の初期設定

アプリの登録

  1. Microsoft Azure Portal サインイン
  2. サブメニュー > Azure Active Directory > アプリの登録 > 新規登録
  3. {名前}入力 > 登録

アクセス権限の設定 (SharePoint と OneDrive)

  1. API のアクセス許可 > アクセス許可の追加 > Microsoft Graph > アプリケーションの許可 > Sites.Read.All > アクセス許可の追加
  2. API のアクセス許可 > アクセス許可の追加 > Microsoft Graph > アプリケーションの許可 > Files.Read.All > アクセス許可の追加
  3. API のアクセス許可 > {組織名}に管理者の同意を与えます > はい

クライアント シークレットの設定

  1. 新しいクライアント シークレット > 追加 > 値をコピーして保管 (client_secret)

クライアントID と テナントID の確認

  1. 概要
  2. アプリケーション (クライアント) ID (client_id)
  3. ディレクトリ (テナント) ID (tennant_id)

Microsoft Graph の使い方

API の初期設定

{}はご自身の環境に置き換えてください。

app/__init__.py
client_id = '{client_id}'
scope = 'https://graph.microsoft.com/.default'
client_secret = '{client_secret}'
grant_type = 'client_credentials'
tennant_id = '{tennant_id}'
graph_url = 'https://graph.microsoft.com/v1.0'
# graph_url = 'https://graph.microsoft.com/beta'

アクセストークン の取得

app/get_access_token.py
"""curl -d 'client_id={client_id}' \
-d 'scope=https://graph.microsoft.com/.default' \
-d 'client_secret={client_secret}' \
-d 'grant_type=client_credentials' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-X POST 'https://login.microsoftonline.com/{tennant_id}/oauth2/v2.0/token'
"""
import json

import requests

from app import client_id, client_secret, grant_type, scope, tennant_id


def get_access_token():
    """get_access_token
    """
    url = 'https://login.microsoftonline.com/{tennant_id}/oauth2/v2.0/token'.format(**{
        'tennant_id': tennant_id
    })

    data = {
        'client_id': client_id,
        'scope': scope,
        'client_secret': client_secret,
        'grant_type': grant_type
    }

    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    res = requests.post(url=url, data=data, headers=headers)

    return json.loads(res.text).get('access_token')


if __name__ == '__main__':
    print(get_access_token())

データ の取得 (共通)

app/graph_api.py
import codecs
import json

import requests

from app import graph_url
from app.get_access_token import get_access_token


def graph_api(service):
    """graph_api
    """
    url = '{graph_url}{service}'.format(**{
        'graph_url': graph_url,
        'service': service
    })

    headers = {
        "Authorization": get_access_token()
    }

    res = requests.get(url=url, headers=headers)

    print(res.status_code)
    print(codecs.decode(json.dumps(json.loads(res.text), indent=4), 'unicode-escape'))


if __name__ == '__main__':
    graph_api(service='/sites/{hostname}:/{server-relative-path}')

SharePoint の取得

app/get_site_item.py
"""[リスト内のアイテムを列挙する](https://docs.microsoft.com/ja-jp/graph/api/listitem-list?view=graph-rest-1.0&tabs=http)
"""
from app.graph_api import graph_api

"""サイトID取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites/{hostname}:/{server-relative-path}'
"""
graph_api(service='/sites/{hostname}:/{server-relative-path}')

"""サイトリスト取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites/{siteId}/lists'
"""
graph_api(service='/sites/{siteId}/lists')

"""サイトアイテム取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites/{siteId}/lists/{list-id}/items?expand=fields'
"""
graph_api(service='/sites/{siteId}/lists/{list-id}/items?expand=fields')

"""サイトアイテム取得(select)
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites/{siteId}/lists/{list-id}/items?expand=fields(select={key1},{key2})'
"""
graph_api(service='/sites/{siteId}/lists/{list-id}/items?expand=fields(select={key1},{key2})')

OneDrive の取得

app/get_drive_data.py
"""[DriveItemのコンテンツをダウンロードする](https://docs.microsoft.com/ja-jp/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http)
"""
from app.graph_api import graph_api

"""サイト検索
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites?search={string}'
"""
graph_api(service='/sites?search={string}')

"""サイトのドライブ一覧取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/sites/{siteId}/drives'
"""
graph_api(service='/sites/{siteId}/drives')

"""ドライブのルート一覧取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/drives/{drive-id}/root/children'
"""
graph_api(service='/drives/{drive-id}/root/children')

"""ルートの子一覧取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/drives/{drive-id}/items/{item-id}/children'
"""
graph_api(service='/drives/{drive-id}/items/{item-id}/children')

"""コンテンツ取得
curl -H 'Authorization: Bearer {access_token}' \
-X GET 'https://graph.microsoft.com/v1.0/drives/{drive-id}/items/{item-id}/content'
"""
graph_api(service='/drives/{drive-id}/items/{item-id}/content')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンドVue.js バックエンドFlaskの開発で躓いたこと色々

GoogleのOAuthをする場合

元々はFlaskのみで実装していたプロジェクトだったのだが、途中からフロントエンドをvue.jsに引き継いだため、OAuth部分をFlask側のコードで実行しようとしていた。
これだとCORSエラーが出てしまう。
CORSエラーへの対応について調べてみると、Flask,Vue.jsともにCORS対応方法についての文献がでてくる。
しかしこれをやっても超えられない。何故か。

色々と探してみたのだが、そもそもそういう使い方は出来ないっぽい。探しすぎてソースを見失ってしまったが、つまりそういうことだったんだと思う。
素直にVue.js側でvue-google-oauth2を使った実装を行う事によってVue.js→Google→Vue.jsという通信によってtokenが取得できるのでそれを使う。

認証されないGoogleAPIキー

これはそのままこれ
https://qiita.com/kenken1981/items/9d738687c5cfb453be19

どうも、Google認証の「OAuth 2.0 クライアント ID」は、一度あるポートで使うと、その後で違うポートで利用することはできないらしい。

ということで、別途フロントエンド用のキーを作って、vue.js側のポートを指定することで解決。

認証されないGoogleAPIキー2

承認済みの JavaScript 生成元
については、127.0.0.1 は使えない。
https://qiita.com/kenken1981/items/b6cb3e536668a3cef520

Google認証は、「127.0.0.1」というIPアドレスは認めておらず、「actual urls」というらしい?いわゆるaaa.jpやbbb.comといった形式でないとエラーを返す模様。

Vue.js側で環境変数が読み込まれない

Vue.js側は yarn build することでdistフォルダに静的ファイルを吐き出す形で利用しているのだが、どうもVue.js側で環境変数を読み込んでくれない。
.envに書いてあろうが読み込まれない。何故か。

buildする時に、その設定が必要だった。
詳細についてはここにある通り。
https://qiita.com/go6887/items/2e254d31b5a4af42f813

vue.js が
/frontend
内に入っていて、/frontoend でビルドコマンドを打つ形にしてありましたが
/frontend/.env.development
というファイルを作って、中に変数を記述。

package.json には
"build": "cross-env NODE_ENV=development vue-cli-service build --mode development --dest ../dist",

みたいな形に書いておく事によって環境変数が読み込まれた。

変更履歴

2021/3/8 初稿up

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyTorch Tensorをprintで要約表示させないようにする

はじめに

PyTorchのTensorの中身をprintすると、いい感じに要約表示してくれるが、全部表示させたいときもある。
numpyで全部表示させる方法はググるとすぐでてくるが、PyTorchのTensorについてはすぐに見つからなかったのでメモっておく

方法

以下のようにset_printoptionsのidgeitemsを指定する。

torch.set_printoptions(edgeitems=1000)

これは最初と最後にようやく表示される配列の要素の数の設定であるが、これを大き目の値に設定しておけば、配列の要素をずらっと表示させることが可能だ。

set_printoptionsメソッドは他にも色々設定できるようなので、参考文献を見てほしい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django プロジェクト環境の構築【Django つぶやきアプリ開発 #1】

0. はじめに

今回から数回に分けてDjangoによるアプリ開発を行う。Pythonの基礎構文が理解していることを前提としている。簡易的なつぶやきアプリを作成していく。
Anacondaではなくpipでパッケージ管理を行っているので注意してほしい。

【環境】
Python: 3.9.1
Django: 3.1.6
VScode(powershell)

1. 仮想環境の構築

Djangoはvenvによる仮想環境で開発を進める。venvはPythonをインストールした際にデフォルトで備わっている機能だ。

python -m venv django

上記コマンドを叩くとカレントディレクトリにdjangoというディレクトリが作成される。作成したディレクトリに移動し、djangoをインストールする。

cd django
Scripts/Activate.ps1
python -m pip install django

Scriptsというディレクトリ内にActivate.ps1というファイルが入っている。これは仮想環境を立ち上げる(仮想環境内に入る)ためのファイルである。これを実行するとターミナル内のカレントディレクトリ表示の左側に(django)と表示される。この状態が仮想環境にを実行していることを表している。今後ターミナル操作を行う際は仮想環境に入っている必要がある。
後は通常通りpipでDjangoをインストールする。

2. プロジェクトの作成とサーバの立ち上げ

次にプロジェクトを作成していく。アプリケーションに必要なファイルやライブラリをまとめたものをプロジェクトと呼ぶ。

django-admin startproject tsubuyaki

上記を仮想環境に入ったターミナルで叩くとtsubuyakiというディレクトリが作成される。'django-admin startproject "プロジェクト名"`とすることでプロジェクトが作成できる。

cd tsubuyaki
python manage.py runserver

上記コマンドでサーバーを立ち上げることができる。manage.pyというファイルにプロジェクトを作成、実行したりする際に必要な機能が備わっている。runserverもそのうちの一つで、サーバを立ち上げる機能になっている。
powershellにhttp://127.0.0.1:8000/ と表示されたらサーバが立ち上がっている。ctrlキーを押しながら左クリックすることでページを表示できる。ロケットが打ちあがっている絵が表示されたら問題なく立ち上がっている。

3. まとめ

今回は仮想環境の構築とプロジェクトの作成を行った。次回から実際にアプリ開発を行っていく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】jinja2を使ってtemplateを読み込みたい

はじめに

<バージョン>
python: 3.7.9

 以下のような、jinja2テンプレートとymlを組み合わせるにはどうすればいいでしょうか?
参考記事(1つ目)のように、ymlを引数としてpythonに渡すコードは見つかったのですが、
pythonコードの中で完結しているものがなかったので作ってみました。

test.j2
var1: {{ var1 }}
var2: {{ var2 }}
test.yml
---
var1: test1
var2: test2

1. サンプルコード

 コードにあるように「with open」を使えば、ymlを引数としてpythonに渡す必要が
無くなるようです。

template.py
#!/usr/bin/env python3
# coding: utf-8
import sys, yaml, jinja2

# define variable
base_dir = '/home/ec2-user/test'
j2_dir   = base_dir + '/j2'
yml_dir  = base_dir + '/yml'

# import j2 template
_loader      = jinja2.FileSystemLoader(j2_dir, encoding='utf-8')
_environment = jinja2.Environment(loader=_loader)
_template    = _environment.get_template("test.j2")

# import yaml and render
with open(yml_dir + '/' + 'test.yml') as import_yml:
  sys.stdout.write(_template.render(yaml.load(import_yml)))

2. ディレクトリ構成

 ファイルは以下の構成にしてください。

.
├── template.py
├── j2
│   └── test.j2
└── yml
    └── test.yml

3. 実行結果

 想定通り、jinja2を使ってymlとtemplateを組み合わせることが出来ました。

出力
[ec2-user@ip-<ip-addr> test]$ python template.py
var1: test1
var2: test2

参考資料

PythonでYAMLを読み込んでJinja2で整形する
【PyYAML VS ruamel.yaml】PythonからYAMLファイルを触ってみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エッジデバイス(Raspberry Pi, Jetson nano)をAWS IoT Coreでプロビジョニングしてみた

AWS IoTを使ううえで一番大事なエッジ機器とIoT Coreを繋ぐ(プロビジョニング)部分をやってみたので解説します。

やりたいことの概略図はこんな感じ。image.png
大まかなプロセスはこちら
1. AWSコンソールでデバイス用の証明書と鍵を生成
2. エッジデバイス(ラズパイやJetson nano。今回はJetson nanoを使いますがラズパイに置き換えても普通にできます)と通信
3. その証明書と鍵を使ってラズパイからAWS IoT Coreにアクセス

1,2,3のように認証情報をエッジデバイスに渡してそれを使ってエッジデバイスとIoT Coreをとりあえず簡単に通信させるところまでをプロビジョニングと呼びます。

  1. AWSコンソールでデバイス用の証明書と鍵を生成 お使いのPC(自分はMacBookで作業しています)でAWSコンソールを開き、「サービス」から「IoT Core」を選択すると以下の画面に行く

左にあるメニューから「モノ」を選択し「作成」をクリックimage.png

名前を適当に決めて(自分は今回はJetson nanoをプロビジョニングしたいので「minagawa_Jetson_nano」としました)、「次へ」を選択。
※「タイプ」や「タグ」はたくさんモノを登録していく際に管理しやすくするため。基本はなしで大丈夫。グループも最初は設定なしで行きます。image.png

証明書は一番上にある「1-Click証明書作成(推奨)」の「証明書を作成」をクリックimage.png

すると以下のような画面になります。画面に示してあるように
・このモノの証明書(自分の場合Jetson nano用の証明書)
・プライベートキー
・AWS IoTのルートCA(サーバー用の証明書)

の3つが必要なので、ダウンロードします(パブリックキーは使わないのでダウンロードしない)。image.png
また、3つ目のルートCAのダウンロードをクリックすると別タブで以下の画面に飛ばされてセキュリティの度合いが違う4つのタイプから証明書を選べます。量子コンピュータが現実のものとなっていない今はそこまでここの違いは気にしなくて良いらしい、と言うことで一番上の「RSA 2048 bit key:Amazon Root CA 1」をダウンロードします。image.png

クリックするとこんな感じ(この証明書は当然使えないので破棄しました)。これをテキストエディタにコピペして(面倒くさいです)、「RootCA」などと名前をつけて保存します。image.png

(※Macのテキストエディタで普通に保存すると.rtfで保存されますが、.rtfだと証明書として認識されないなので注意!.pemや.crtにしなくても証明書として認識されますが、.rtfはダメです。テキストエディタの「フォーマット」から「プレーンテキストにする」をクリックして.txt形式で保存する必要があります。孔明の罠に注意)
image.png

必要な証明書2つと秘密鍵がダウンロードできました。image.png
これら3つは後でエッジデバイスに渡すので大事に保管しておきます。
※これらプライベートキーと証明書2つはここでしか保存する機会がないのでもしここで保存し忘れたりファイルを無くしてしまったりするとまた1の最初からやり直しになるので注意が必要です。

AWSコンソールに戻り「有効化」をクリックし、「ポリシーをアタッチ」をクリック。image.png

こんな画面に行きます。
image.pngAWSは権限を与えない限り基本何もできないギスギスした世界なので、新しいモノにはポリシーという形で基本的人権を与えてあげます。Defaultがそれに当たります。チェックを入れて「モノの登録」をクリック。image.png

※S3に直接データを送信したい場合などは新たにポリシーを作って付与する必要があります(ここがAWSの面倒なところ)。実験段階では画像のように、「神権限」(名前は適当につけました)を作っておいてモノに付与するのも手です(その場合画面のように「ポリシーの作成」から「アクション」と「リソースARN」に「*」、効果の「許可」にチェックを入れてポリシーを作成します)。defaultだとIoT Coreとの通信しか許可されていません。権限が足りなかったら後から付け足すこともできるので(ポリシーは2つ以上付与できます)、自分はとりあえずこのdefaultで行きます。

下の画像のようにメッセージが出たら無事STEP1.証明書と鍵の発行は終わりです。
image.png

※ちなみにもう少し詳しい説明としては、モノにAWSのサービス(例えばS3)へのアクセス権限を当たるポリシーはたった今発行した証明書に紐づけられます。画像のようにラズパイ君が持ってきた証明書に対しAWSが「この証明書は基本的人権が紐づけられてるな」とか「これは神権限が紐づけられてますね、どうぞお通りください」という風に対応する感じです。
image.png

2.いったんエッジデバイスとSSH接続する
Jetson nano向けのUbuntuのデスクトップはご覧の通りめちゃくちゃかっこいいですが、今回はMacBookからSSH接続して操作するので使いません。(※WindowsだとPutty等を使います。Jetson nano用のUbuntuディスクイメージはこちらからインストールできます->https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit#write )image.png

Jetson nanoのターミナルで

hostname -I

と打つとLAN内のIPアドレスが表示されます。
ターミナル上で

ssh USER_NAME@IP_ADDRESS

と打つとパスワードを求められるので入力。
※「USER_NAME」と「IP_ADDRESS」にはそれぞれユーザー名とIPアドレスを入れてください。ちなみに自分の場合は「ssh hidehiro@192.168.1.2」と入れました

無事つながりました。
image.png

3.エッジデバイスに証明書2つと鍵を渡す
FTP(File Transfer Protocol)のひとつであるFileZillaが便利なので使います。
Mac: https://filezilla-project.org/download.php?platform=osx
Windows: https://filezilla-project.org/download.php?platform=win64
FileZillaを使わない場合はMac,Linuxの場合はftp, scp, sftpコマンドなどでもいけます。

画面はこんな感じ。「Host」にはエッジデバイスのIPアドレス、「Port」には「22」と入れて「Quickconnect」をクリック。image.png
こうなったら成功。
image.png

証明書2つと鍵をエッジデバイスの適当なフォルダにドラッグ&ドロップでコピーします()

ーPythonファイルの編集

実際にIoT Coreと通信するためのPythonファイルを編集していきます。先程ダウンロードしたAWS IoT Device SDK for Pythonのフォルダの中にサンプルとなるPythonファイルがたくさん入っています(こういうサンプルファイルをスケルトンとも言います)。

Pythonファイルの編集はSSH上でnano等を使って編集しても良いし、PC上で編集したファイルをエッジデバイス上に(FileZilla等で)書き換えても大丈夫ですが、最後Pythonファイルを動かすときに

basicPubSub.py

ここでPubSubのPythonファイルをいじっていくわけですが、必要な情報は4つです。
・AWS IoT Coreのエンドポイント
・デバイス証明書のファイルパス
・サーバー証明書のファイルパス
・秘密鍵のファイルパス

一つ目のAWS IoT Coreのエンドポイントはどこで確かめるかというと、AWSコンソールのIoT Coreのページに行って、
image.png
左にある「設定」をクリックします(これが超わかりにくい場所にあります。AWS IoTエンドポイントは「設定」にあります。もう一度繰り返します。AWS IoTのエンドポイントは「設定」の場所にあります。悪名高いAWSコンソールの中でもなかなかの凶悪さです)

a2v4hij6s98s19-ats.iot.us-east-1.amazonaws.com

みたいな感じで、「英数字.iot.あなたの設定したAWS IoTリージョン.amazonaws.com」という感じになっています。これをコピーします。
(※最後にPythonファイルを動かすときにこの4つの情報を再度入力することになるので、テキストファイル等にメモを残しておくことをお勧めします)

PubSub.pyを開いて30行目くらいにそれぞれ入力する箇所があるので、4つとも入力していきます。image.png
※証明書や秘密鍵のファイルパスは
/home/USER-NAME/Documents/RootCA.rtf
と言った感じで絶対参照で書くのをお勧めします。

2.エッジデバイスとPCを繋ぐ

次にAWS CLIをエッジデバイスにインストールしていきます。SSH接続したエッジデバイス上で、cdコマンドでAWSフォルダを作成したいところまで移動して、以下のコマンドを実行します(ちなみにラズパイ、Jetson nanoのどちらもLinux ARMです)。

sudo apt-get install curl
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

実行したら、
aws --version
と打ってみて「aws-cli/2.1.29 Python/3.8.8 Linux/4.9.201-tegra exe/aarch64.ubuntu.18 prompt/off」のように表示されれば成功です。zipファイルはもういらないので、

rm awscliv2.zip

で消してしまいます。

cd aws
で作成したAWSフォルダに入って(入らなくても大丈夫です)、

git clone https://github.com/aws/aws-iot-device-sdk-python.git

でAWS IoT Device SDK for Pythonをインストールします。
ls
でaws-iot-device-sdk-pythonというフォルダができていることを確認します。

その後、
cd aws-iot-sdk-python
sudo python setup.py install

でSDKをビルドします。

ls
をするといくつかサンプルファイルがあります。そのうちbasicPubSub.pyが今回使うPythonファイルです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonを使って中小印刷会社の営業が面倒な手配数量選択を自動化してみた話

筆者のスペック

  • 地方の工学部機械工学科にてPython・C++を触る
  • ROSを介して自律ロボットの制御をやってました
  • なんやかんやあって印刷会社の営業(内勤多め)をやってます。

自動化前の状況

私が現在担当している品目は、主に"軟包材"と呼ばれるアルミ袋です。
(ポテチの袋やシャンプーの詰め替えパックをイメージしてくれれば嬉しいです)

この品目は製造にかかる時間が最短でも2ヶ月かかるため、
前もってヌケモレなく手配しておくことが最重要課題です。

また、原材料が2000m刻みの規格のロール状であるため、
任意の数量を得ることが難しいです。
(想像以上に出来すぎてしまう場合がある)

下に上り数量の表の例を記載します。
これも実際は最大で±それぞれ10%の出来高数変動が存在します。
(オペレータの腕がいいとセッティング時のロスが少ない)

[袋] 8000m 6000m 4000m 2000m
製品A 81000 58500 40000 14000
製品B 76000 57000 38000 15000
製品C 35000 25500 17000 6000

そして、納期の2~3か月前にはお客様より先行手配がエクセル添付で届きます。
品名を確認し、上り数量一覧表(紙)の該当行に定規を置き、
右端の2000mから順に数量を満たす手配メーターを決定します。
上り数量が要求数にぎりぎりな場合は電卓で*0.9をし安全を確認します。

この作業が多い時で50品目ほど繰り返されます。
数的にはまぁ手作業でもできなくはないのですが、
表の行や左右の見間違えが即、数欠けや過納品につながりますので
細心の注意が要求されます。
(まぁ何度もやってると品目聞いただけでメーターごとの上りがおおよそ把握できるんで自分で間違いに気付けるんですが…)

そこで私は自動化を決意しました。

プログラム構成

上り数量一覧表をエクセルにcsv形式で保存。コマンドにて自動化プログラムを実行し、入力情報を食わせ、出力を得る。

  • 入力情報
    • 注文番号
    • 必要数
  • 出力情報
    • 注文番号
    • 仕入先
    • 袋形状
    • 版番号
    • 品名
    • 必要数
    • 上り数量
    • 手配メータ―数

※エクセルオンリーでもできそうですがVBAよりPythonのほうがとっつきやすく、またこういうファイル間横断プログラムをマクロでやるのはあんまエレガントじゃないよなぁ(笑)って思いこんな回りくどいことしてます。もっと効率いい方法ありましたら是非ご教授願います。

コード

#################################################
###コマンドから製品コードと数量を取得、最適メーター数を出力#####
##################################################
import numpy as np
import pandas as pd
#pd.set_option('display.unicode.east_asian_width', True)
miss_coefficient = 0.9
pd.set_option('display.max_rows', 1000)
pd.set_option('display.max_columns', 1000)
df1 = pd.read_csv('inputdate/order.csv',header=None)#入力データ読込部
df2 = pd.read_csv('basedate/number.csv',header=0,encoding="shift-jis")#データベース参照部
#仕入先,袋形状,コード,直近版番号,品名,2000m,4000m,6000m,8000m,10000m,12000m,14000m,16000m,18000m,20000m
df2 = df2.fillna(0)
#print(df2)#データべースチェック用
list_df = pd.DataFrame(columns=['仕入先','注文番号','袋形状','直近版番号','品名','注文数(手配数)','上り数量','手配m数'])

for i in range(len(df1)):
    code  = df1.loc[i, 0]#製品コード##int
    order = df1.loc[i, 1].astype(float)#数量←locで指定するとnumpyになる##float
    df3 = df2[df2['コード'] == str(code)].reset_index()#df3は製品コードに対応する行#行番号を0にして読込
    #print(df3)
    if df3.empty:
        print('注文番号',end="")
        print(code,end="")
        print('に合致する番号がデータベース上にありません!')
        continue
    #print(type(order))#<class 'numpy.float64'>
    #print(type(df3))###<class 'pandas.core.frame.DataFrame'>
    for j in ['2000m','4000m','6000m','8000m','10000m','12000m','14000m','16000m','18000m','20000m']:
        num = df3.loc[0, j]
        if j=='2000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='4000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='6000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='8000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='10000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='12000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='14000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j################手配メーター数
            break
        if j=='16000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='18000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
        if j=='20000m' and num*miss_coefficient > order:
            arrange_number = num.astype(int)#上り数量
            arrange_meter = j###############手配メーター数
            break
    #再度dateframeに組み込み
    tmp_se = pd.Series([
        (df3['仕入先'].to_string(name = False,index = False)).ljust(6),
        (df3['コード'].to_string(name = False,index = False)).ljust(4),
        (df3['袋形状'].to_string(name = False,index = False)).ljust(4),
        (df3['直近版番号'].to_string(name = False,index = False)).ljust(10),
        (df3['品名'].to_string(name = False,index = False)).ljust(25),
        str(order.astype(int)).center(10),#int
        str(arrange_number).center(10),
        str(arrange_meter).center(10)], index=list_df.columns )
    #実はSeriesは1次元の配列(ベクトル)なので縦とか横とか言う概念がないので、縦でも横でも同じことをさしているのだそうです
    list_df = list_df.append(tmp_se, ignore_index=True )
    #ここも要注意ポイントで、.append は戻り値で新しいインスタンスを返します。 なので、代入しなおしてください。
#print(list_df)#蓄積したリストを最後に表示
#得られたリストのソート
list_df = list_df.sort_values(['仕入先', '袋形状'], ascending=[False, False])
print(list_df)
list_df.to_csv("outputdate/result.csv", encoding="shift_jis")

今後の課題

手配数量を一発で決定するという課題は解決された。

しかしこの出力結果でそのまま発注できるわけではなく、
さらにエクセル方眼紙製の発注書にコピぺしFAXしなければなりません。
できれば発注書まで一気に自動作成できるようになれば、かなり労力が削減でき、
その時間を別のお客様のもとで過ごす、という選択肢も可能になるやもしれません。

参考URL

Python pandas で動的に行を追加するTips(プログラマ向け)
https://qiita.com/567000/items/d8a29bb7404f68d90dd4

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで学ぶ制御工学 第13弾:周波数応答

#Pythonで学ぶ制御工学< 周波数応答 >

はじめに

基本的な制御工学をPythonで実装し,復習も兼ねて制御工学への理解をより深めることが目的である.
その第13弾として「周波数応答」を扱う.

周波数応答

周波数応答の概要についてから,以下の図に説明を示す.
image.png
image.png

ここで,実際に図を示す.(※ソースコードは後に示す)
frequency.png
確かに位相はずれており,周波数を高くするほど出力信号の振幅が小さくなっていることが分かる.

image.png

ここまでをまとめると,次のような感じである.
「制御対象の特徴を知るためにインパルス入力によるインパルス応答を観測すればよいが,インパルス入力は難しいため,代わりに複数の周波数の余弦波(正弦波)の集まり,すなわち様々な周波数成分を含んだ信号を入力として,周波数に対する応答(周波数応答)を観測すればよいということに置き換えられる.そこで,ゲインや位相という話が出てくる.」

1次遅れ系

1次遅れ系での周波数応答について,以下の図にて簡単な説明を示す.
image.png
ここで,時定数$T$を変化させたときの1次遅れ系のボード線図を示す.(※ソースコードは後に示す)
bode1.png
確かに周波数が時定数の逆数となる時に位相が-45度付近にあり,そのときの周波数あたりでゲイン線図では,およそ-3dBにあることが分かる.

2次遅れ系

2次遅れ系での周波数応答について,以下の図にて簡単な説明を示す.
image.png
ここで,減衰係数$\zeta$を変化させたときの2次遅れ系のボード線図と,固有角振動数$\omega_n$を変化させたときの2次遅れ系のボード線図を示す.(※ソースコードは後に示す)
bode2_zeta.png
bode2_omega.png
確かに減衰係数$\zeta$が0.4と小さい時にゲインが0を上回っている.また,固有角振動数$\omega_n$と周波数が一致するところで,位相が-90度付近にあり,そのときの周波数あたりで,ゲインが下がり始めていることが確認できる.

実装

上で示した図を生成したソースコードを以下に示す.

ソースコード①:周波数の違いによる応答
frequency.py
"""
2021/03/07
@Yuya Shimizu

正弦波を入力したときの応答を調べる
"""
from control import tf
from control.matlab import lsim
import matplotlib.pyplot as plt 
import numpy as np
from for_plot import plot_set       #自分で定義した関数をインポート
#定義した関数について (https://qiita.com/Yuya-Shimizu/items/f811317d733ee3f45623)

fig, ax = plt.subplots(2, 2)

#2次遅れ系
zeta = 0.7
omega_n = 5
K = 1
P = tf([0, K*omega_n**2], [1, 2*zeta*omega_n, omega_n**2])

freq = [2, 5, 10, 20]   #周波数を4つ用意
Td = np.arange(0, 5, 0.01)  #シミュレーション時間

for i in range(2):
    for j in range(2):
        u = np.sin(freq[i+j+i*1]*Td)    #正弦波入力
        y, t, x0 = lsim(P, u, Td, 0)    #シミュレーション

        #描画
        ax[i, j].plot(t, u, ls='--', label='u')
        ax[i, j].plot(t, y, label='y')
        ax[i, j].set_title(f"freqency {freq[i+j+i*1]} [Hz]")
        plot_set(ax[i, j], 't', 'u, y')

ax[0, 0].legend()   #1つ目の図だけ凡例表示

plt.show()
ソースコード②:ボード線図(1次遅れ系)
bode1.py
"""
2021/03/07
@Yuya Shimizu

ボード線図(1次遅れ系)
"""
from control import tf, bode
from control.matlab import lsim, logspace
import matplotlib.pyplot as plt
import numpy as np
from for_plot import linestyle_generator, bodeplot_set      #自分で定義した関数をインポート
#定義した関数について (https://qiita.com/Yuya-Shimizu/items/f811317d733ee3f45623)

K = 1
T = [1, 0.5, 0.1]   #時定数

LS = linestyle_generator()
fig, ax = plt.subplots(2, 1)

for i in range(len(T)):
    P = tf([0, K], [T[i], 1])   #1次遅れ系

    #ゲイン,位相,周波数の取得 ※Plot=Falseとすることで,値だけ取得し図は出力しない
    gain, phase, w = bode(P, logspace(-2, 2), Plot=False)

    #ボード線図をプロット
    plt_args = {'ls':next(LS), 'label': f"T={T[i]}"}
    ax[0].semilogx(w, 20*np.log10(gain), **plt_args)    #.semilogxでx軸に対数表現を置いた片側対数グラフにプロット
    ax[1].semilogx(w, phase*180/np.pi, **plt_args)      #度数表現

ax[0].set_title("First-order delay system")
bodeplot_set(ax, 3, 3)
plt.show()
ソースコード③:ボード線図(2次遅れ系)
bode2.py
"""
2021/03/07
@Yuya Shimizu

ボード線図(2次遅れ系)
"""
from control import tf, bode
from control.matlab import lsim, logspace
import matplotlib.pyplot as plt
import numpy as np
from for_plot import linestyle_generator, bodeplot_set     #自分で定義した関数をインポート
#定義した関数について (https://qiita.com/Yuya-Shimizu/items/f811317d733ee3f45623)


##減衰係数の変化によるボード線図の違い
K = 1
zeta = [1, 0.7, 0.4]   #減衰係数
omega_n = 5

LS = linestyle_generator()
fig, ax = plt.subplots(2, 1)

for i in range(len(zeta)):
    P = tf([0, K*omega_n**2], [1, 2*zeta[i]*omega_n, omega_n**2])   #2次遅れ系

    #ゲイン,位相,周波数の取得 ※Plot=Falseとすることで,値だけ取得し図は出力しない
    gain, phase, w = bode(P, logspace(-2, 2), Plot=False)

    #ボード線図をプロット
    plt_args = {'ls':next(LS), 'label': f"$\zeta$={zeta[i]}"}
    ax[0].semilogx(w, 20*np.log10(gain), **plt_args)    #.semilogxでx軸に対数表現を置いた片側対数グラフにプロット
    ax[1].semilogx(w, phase*180/np.pi, **plt_args)      #度数表現

ax[0].set_title("Second-order delay system: changed $\zeta$")
bodeplot_set(ax, 3, 3)
plt.show()


##固有角振動数の変化によるボード線図の違い
K = 1
zeta = 0.7
omega_n = [1, 5, 10]    #固有角振動数

LS = linestyle_generator()
fig, ax = plt.subplots(2, 1)

for i in range(len(omega_n)):
    P = tf([0, K*omega_n[i]**2], [1, 2*zeta*omega_n[i], omega_n[i]**2])   #2次遅れ系

    #ゲイン,位相,周波数の取得 ※Plot=Falseとすることで,値だけ取得し図は出力しない
    gain, phase, w = bode(P, logspace(-2, 2), Plot=False)

    #ボード線図をプロット
    plt_args = {'ls':next(LS), 'label': f"$\omega_n$={omega_n[i]}"}
    ax[0].semilogx(w, 20*np.log10(gain), **plt_args)    #.semilogxでx軸に対数表現を置いた片側対数グラフにプロット
    ax[1].semilogx(w, phase*180/np.pi, **plt_args)      #度数表現

ax[0].set_title("Second-order delay system: changed $\omega_n$")
bodeplot_set(ax, 3, 3)
plt.show()

感想

改めて,図だけでなく式にも触れて周波数応答について学び,-3dBや-45 degとなるときの条件がなぜそうなのかなど,理論的に理解することができた.それを知ったうえで図を見るとボード線図は非常に役立つ図だと感じられる.この理論から知るか知らないかでは大きな差があるだろうと思った.自分自身も完ぺきではないが,これからもより理解を深められるように努めたい.

参考文献

Pyhtonによる制御工学入門  南 祐樹 著  オーム社

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3 Socket通信でエラーがでるときのメモ

2020.03.08

Python3
Socket通信

本の通りにclient.pyとserver.pyを書いてソケット通信を試みようとした
ところエラーが出て通信できなかった時のメモ。

●サーバ環境
 さくらVPS 3GB 100GB SSH
 Ubuntu 20.04LTS
 Python3.8.5
 server.py を設置して稼働する

●クライアント環境 (自宅)
 Windows10 Home
 Visual Studio Code
 Python3.9.1(Windows版)
 Rlogin
 client.py を設置して実行する(上記server.py稼働中に実行する)

Ubuntuでのポート開放方法がわからない方は
参考:ufwコマンドの使い方
などをご参照ください。

●ポート番号が悪さをしている可能性
古い本の中にはソケット通信のポート番号として、ウェルノウンポート番号や登録済み
ポート番号を指定しているものが多い(8000番とか、8001番とか)が、これを動的・
プライベートポート番号に変更
するだけでプログラムが正常に稼働し、通信できた。

通信エラーがでる人はプログラムで利用するポート番号を確認してほしい。

client.py
# 2021.03.08
# ソケット通信テストプログラム
# ポートは動的・プライベートポート番号(49152-65535)を利用しないと
# つながらない!本などで8000番,8001番などを利用しているものが
# 多いので注意する。

import socket

def socketConnectClient():
    PORT=49200 #動的・プライベートポート番号(49152-65535)を利用すること
    SERVER='honyarara.com' #接続先のドメインかIPアドレス
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((SERVER, PORT))  # サーバを指定して接続
        # s.connect((SERVER,8002)) # 仮にポート開けててもソケット通信不可!
        s.sendall(b'Hello!')       # サーバにメッセージを送る
        data = s.recv(1024)        # サーバからの文字列を取得する
        print(repr(data))

if __name__ =='__main__':
    socketConnectClient()
server.py
# 2021.03.08
# socket サーバを作成
# Ctrl+Cで中断終了してください

import socket

SERVER=''  #サーバー側は空欄でよい
PORT=49200 #動的・プライベートポート番号(49152-65535)を利用すること
def socketConnectServer():
    print("ポート番号:{}でソケット通信を開始します。".format(PORT))
    # AF = IPv4 という意味
    # TCP/IP の場合は、SOCK_STREAM を使う
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # IPアドレスとポートを指定
        s.bind((SERVER,PORT))
        s.listen(10)
        while True:
            conn, addr = s.accept()
            with conn:
                while True:
                    # データを受け取る
                    data = conn.recv(1024)
                    if not data:
                        break
                    print('data : {}, addr: {}'.format(data, addr))
                    conn.sendall(b'Received: ' + data)

if __name__ == '__main__':
    socketConnectServer()
    print("プログラムを終了しました。")

実行結果
server.png
client.png

参考:WikiPedia:TCPやUDPにおけるポート番号の一覧
ウェルノウンポート番号 (0–1023)
登録済みポート番号 (1024–49151)
動的・プライベート ポート番号 (49152–65535)

ぷれじ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

anyenvとpyenvでPythonをインストールする

最終更新日

2020年10月14日

検証を行ったmacOSのバージョンは下記です。

  • macOS Catalina 10.15.7

インストールするもののバージョンは下記です。

  • anyenv 1.1.2
  • pyenv 1.2.21
  • Python 3.9.0

この記事が古くなった場合、下記の手順は最新のインストール手順とは異なっている可能性があります。

そもそも、pyenvを使うか否か?

以前の僕はpyenvを使う派でした。しかし、今はなるべく使わない派になっています。なぜかというと、pyenvはトラブル解決の難易度が高いからです。

「pyenv 動かない」「pyenv doesn't work」でググると、ブログやStack Overflowが数多くヒットします。つまり、それだけトラブルが多いことを意味するのでしょう。

更に、記事によって解決方法がかなり異なります。つまり、不具合の原因となる要素が多いのでしょう。

問題を自分で解決する自信がある場合は、pyenvを使ってもいいと思います。しかし、少なくともPython初心者には、pyenvはおすすめしません。

このことを念頭において、以下の記事を読んでください。

事前にインストールが必要なもの

  • Homebrew
  • Xcode Command Line Tools
    • Homebrewインストール時に一緒にインストールされるので、特に作業は必要なし

anyenv

インストール

(1) ターミナルで brew install anyenv を実行してください。

$ brew install anyenv
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
==> Updated Casks
skype

==> Downloading https://homebrew.bintray.com/bottles/anyenv-1.1.2.catalina.bottl
Already downloaded: /Users/tada/Library/Caches/Homebrew/downloads/7d50622dafb1cbbc172af5158125cd4e6d137d7c05615a6e90739ffda9204241--anyenv-1.1.2.catalina.bottle.tar.gz
==> Pouring anyenv-1.1.2.catalina.bottle.tar.gz
?  /usr/local/Cellar/anyenv/1.1.2: 23 files, 29.9KB

(2) anyenv init を実行してください。

$ anyenv init
# Load anyenv automatically by adding
# the following to ~/.zshrc:

eval "$(anyenv init -)"

(3) ~/.zshrcに下記の記述を追記してください。

eval "$(anyenv init -)"

~/.zshrcを直接編集してもいいですし、 echo 'eval "$(anyenv init -)"' >> ~/.zshrc を実行してもOKです。

(4) source ~/.zshrc を実行、またはターミナルを再起動してください。

$ source ~/.zshrc

これは任意ですが、 anyenv update のインストールもおすすめします。詳細はGitHubを確認してください。

確認

(1) anyenv install -l を実行して、下記のように表示されれば成功です。

$ anyenv install -l
  Renv
  crenv
  denv
  erlenv
  exenv
  goenv
  hsenv
  jenv
  jlenv
  luaenv
  nodenv
  phpenv
  plenv
  pyenv
  rbenv
  sbtenv
  scalaenv
  swiftenv
  tfenv

pyenv

インストール

(1) anyenv install pyenv を実行してください。

$ anyenv install pyenv
/var/folders/fy/48r10wdn7mx2629zngd9zsnh0000gn/T/pyenv.20201014093919.65856 ~
Cloning https://github.com/pyenv/pyenv.git master to pyenv...
Cloning into 'pyenv'...
remote: Enumerating objects: 19, done.
remote: Counting objects: 100% (19/19), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 18370 (delta 3), reused 10 (delta 2), pack-reused 18351
Receiving objects: 100% (18370/18370), 3.70 MiB | 2.36 MiB/s, done.
Resolving deltas: 100% (12507/12507), done.
~

Install pyenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.

(2) ターミナルを再起動してください。

確認

(1) pyenv install -l を実行してください。下記のように表示されれば成功です。

$ pyenv install -l
Available versions:
  2.1.3
  (中略)
  3.9.0
  (中略)
  stackless-3.7.5

Python

インストール

(1) pyenv install 3.9.0 を実行してください。

$ pyenv install 3.9.0
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.9.0.tar.xz...
-> https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tar.xz
Installing Python-3.9.0...
python-build: use readline from homebrew
python-build: use zlib from xcode sdk
Installed Python-3.9.0 to /Users/tada/.anyenv/envs/pyenv/versions/3.9.0

(2) pyenv versions を実行してください。Python 3.9.0がインストールされたことが確認できます。

$ pyenv versions
* system (set by /Users/tada/.anyenv/envs/pyenv/version)
  3.9.0

(3) pyenv global 3.9.0 を実行してください。

$ pyenv global 3.9.0

(4) ターミナルを再起動してください。

確認

(1) python --version を実行してください。 Python 3.9.0 と表示されれば成功です。

$ python --version
Python 3.9.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む