- 投稿日:2019-10-02T23:49:13+09:00
Tkinterを使うのであればPySimpleGUIを使ってみたらという話
ページを読んでできるもの
- Pythonで以下の入力内容のGUIが簡単に作れます
- 基本的な入力(テキストボックス、チェックボックス)
- テキストボックスにはデフォルトで入力が、チェックボックスで入力できます
- 入力された内容をポップアップで表示する
起動直後の画面
実行ボタンを押した結果
Tkinterについて
Tkinterは標準のPythonのGUIライブラリです。個人的に考えるメリット、デメリットは以下の通りです。
メリット
- 標準だからインストール不要
- サンプルはそこそこ多い
デメリット
- ウィジェット(UIパーツ)を配置する際、種類、オプション、レイアウトと順を追って設定しないといけない
- ウィジェットの配置が増えると、レイアウトがコードを見てもよくわからない
Tkinterの例
実際にTkinterについて実際にコードを載せます
PythonでGUIアプリを作る方法【Tkinter】
の記事の最後のコードと実行結果をこちらに転載します実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードは以下になります
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_value = input_box.get() messagebox.showinfo("クリックイベント",input_value + "が入力されました。") #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x240") #入力欄の作成 input_box = tkinter.Entry(width=40) input_box.place(x=10, y=100) #ラベルの作成 input_label = tkinter.Label(text="ラベル") input_label.place(x=10, y=70) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=130) #ウインドウの描画 root.mainloop()ウィジェット(UIのパーツ)が縦一列に並んだ場合は比較的簡単です。
問題は横一列に「ラベル」+「テキストボックス」というようなよくあるレイアウトで複数のウィジェットを配置した場合です実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードはいかになります。
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_name_value = input_name.get() input_address_value = input_address.get() input_phone_value = input_phone.get() show_message = "名前:" + input_name_value + 'が入力されました。\n' show_message += "住所:" + input_address_value + 'が入力されました。\n' show_message += "電話番号:" + input_phone_value + "が入力されました。" print(show_message) # 入力内容をポップアップ画面で表示 messagebox.showinfo("入力内容" ,show_message) #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x120") # 名前 input_name_label = tkinter.Label(text="名前") input_name_label.grid(row=1, column=1, padx=10,) # 入力欄の作成 input_name = tkinter.Entry(width=40) input_name.grid(row=1, column=2) # 住所 input_address_label = tkinter.Label(text="住所") input_address_label.grid(row=2, column=1, padx=10,) # 住所入力欄の作成 input_address = tkinter.Entry(width=40) input_address.grid(row=2, column=2) # 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,) # 電話番号入力欄の作成 input_phone = tkinter.Entry(width=40) input_phone.grid(row=3, column=2) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=80) #ウインドウの描画 root.mainloop()問題なのは以下のところです。
# 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,)Tkinterはウィジェットを配置する際はまず自分が何(ラベル、ボタン、テキストボックスetc)を設定します。
そしてそのあとに配置する場所を指定します。Tkinterでグリッドレイアウトを用いる際は、grid関数にrow,columnを使ってレイアウトを調整します。今回は縦×横が3×2のレイアウトなのでまだいいですが、例えば5×10などウィジェットが増えていくと、コードをぱっと見ただけではレイアウトがよくわからなくなります。
PySimpleGUI
PySimpleGUIは2018年から開発が始まったライブラリーです。
- github
- 公式ドキュメント
tkinter、Qt、WxPython、Remiののラッパーで公式ではもとのライブラリーで書く場合を比べて、コード量が2分の1から10分の1程度でかけると書いてあります。
私自身も実際に書いてみてTkinterで書くよりもはるかに短いコード量でかけると思いました。最大の特徴は、リストを使用して、UIのレイアウトを配置することです。
実際に使用して見ましょう。インストール方法はpipで簡単にインストールできます
pip install pysimplegui or pip3 install pysimplegui簡単な使い方は以下のとおりです。公式の以下のサンプルをもとに作成しています。
- 1 Shot - Simple Data Entry - Return Values - Auto Numbered
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event == None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
実行結果
実行ボタンを押した場合
いかがでしょうか?
レイアウトは以下で定義しています。2次元のリストでレイアウトを定義しています。layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')]そして取得した値は以下で取得できます
if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。"詳しくは公式ドキュメントを読むことをおすすめしますが、
Tkinterでは54行だったものがPySimpleGUIですと28行でかけます。
なにより、Tkinterで書いたものよりもレイアウトや値の取得方法が格段にわかりやすいです。checkboxを使ってみる
先程のコードにデフォルトでチェックが入っているチェックボックスを追加してみます。
- 公式のcheckboxのリファレンス
コードはいかになります
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Checkbox('同意する', default=True)], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event is None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。\n" show_message += "同意のチェックは:" + str(values[3]) + "です" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
起動直後の画面
実行ボタンを押した結果
チェックボックスの値はBool方でture,falseで取得できます。
Tkinterでチェックボックスでデフォルトの値を設定しようとおもうとちょっとめんどうだったものが簡単に設定できます。Tkinterのチェックボックスの設定方法については以下の記事が参考になります。
- PythonのTkinterを使ってみる(最初からChecked,Uncheckedの値をもたせる)
PySimpleGUIは他にもTkinterを生で使うよりも、簡単にウィジェットが使用できます。また公式のサンプルが豊富です。matplotlibやopenCvを使用した例などが豊富にあります。
exe化について
exe化はPyInstllerを使用して簡単にできます
以下は公式の以下の説明からの引用です
- Creating a Windows .EXE File
単一ファイルの作り方は以下になります
pip install PyInstaller pyinstaller -wF my_program.py
-wF
をつけることで単一ファイルができますまた以下のエラーが出た場合は、
--hidden-import tkinter
をオプションに加えます。ValueError: script '.......\src\tkinter' not foundMacだと以下でできるそうです(公式では確かめていないそうです)
pyinstaller --onefile --add-binary = '/ System / Library / Frameworks / Tk.framework / Tk': 'tk' --add-binary = '/ System / Library / Frameworks / Tcl.framework / Tcl': ' tcl 'your_program.pyこの情報はRedditにあり、ソースはhttps://github.com/pyinstaller/pyinstaller/issues/1350 から引用しています。
ライセンスについて
PySimpleGUIはライセンスはLGPL3.0になります。
MITライセンスではないですが、再配布しない限り社内や個人で利用するにあたっての使用には問題がないはずですので通常使用する分には問題がないはずです。今後の予定
時間があれば公式のサンプルプログラムからいくつかの紹介とTkinterで作られたGUIをPySimpleGUIで書き直した場合のサンプルを書こうかと考えています。
- 投稿日:2019-10-02T23:49:13+09:00
Tkinterを使うのであればpySympleGUIを使ってみたらという話
Tkinterを使うのであればpySympleGUIを使ってみたらという話
ページを読んでできるもの
- Pythonで以下の入力内容のGUIが簡単に作れます
- 基本的な入力(テキストボックス、チェックボックス)
- テキストボックスにはデフォルトで入力が、チェックボックスで入力できます
- 入力された内容をポップアップで表示する
起動直後の画面
実行ボタンを押した結果
Tkinterについて
Tkinterは標準のPythonのGUIライブラリです。個人的に考えるメリット、デメリットは以下の通りです。
メリット
- 標準だからインストール不要
- サンプルはそこそこ多い
デメリット
- ウィジェット(UIパーツ)を配置する際、種類、オプション、レイアウトと順を追って設定しないといけない
- ウィジェットの配置が増えると、レイアウトがコードを見てもよくわからない
Tkinterの例
実際にTkinterについて実際にコードを載せます
PythonでGUIアプリを作る方法【Tkinter】
の記事の最後のコードと実行結果をこちらに転載します実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードは以下になります
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_value = input_box.get() messagebox.showinfo("クリックイベント",input_value + "が入力されました。") #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x240") #入力欄の作成 input_box = tkinter.Entry(width=40) input_box.place(x=10, y=100) #ラベルの作成 input_label = tkinter.Label(text="ラベル") input_label.place(x=10, y=70) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=130) #ウインドウの描画 root.mainloop()ウィジェット(UIのパーツ)が縦一列に並んだ場合は比較的簡単です。
問題は横一列に「ラベル」+「テキストボックス」というようなよくあるレイアウトで複数のウィジェットを配置した場合です実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードはいかになります。
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_name_value = input_name.get() input_address_value = input_address.get() input_phone_value = input_phone.get() show_message = "名前:" + input_name_value + 'が入力されました。\n' show_message += "住所:" + input_address_value + 'が入力されました。\n' show_message += "電話番号:" + input_phone_value + "が入力されました。" print(show_message) # 入力内容をポップアップ画面で表示 messagebox.showinfo("入力内容" ,show_message) #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x120") # 名前 input_name_label = tkinter.Label(text="名前") input_name_label.grid(row=1, column=1, padx=10,) # 入力欄の作成 input_name = tkinter.Entry(width=40) input_name.grid(row=1, column=2) # 住所 input_address_label = tkinter.Label(text="住所") input_address_label.grid(row=2, column=1, padx=10,) # 住所入力欄の作成 input_address = tkinter.Entry(width=40) input_address.grid(row=2, column=2) # 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,) # 電話番号入力欄の作成 input_phone = tkinter.Entry(width=40) input_phone.grid(row=3, column=2) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=80) #ウインドウの描画 root.mainloop()問題なのは以下のところです。
# 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,)Tkinterはウィジェットを配置する際はまず自分が何(ラベル、ボタン、テキストボックスetc)を設定します。
そしてそのあとに配置する場所を指定します。Tkinterでグリッドレイアウトを用いる際は、grid関数にrow,columnを使ってレイアウトを調整します。今回は縦×横が3×2のレイアウトなのでまだいいですが、例えば5×10などウィジェットが増えていくと、コードをぱっと見ただけではレイアウトがよくわからなくなります。
pySympleGUI
pySympleGUIは2018年から開発が始まったライブラリーです。
- github
- 公式ドキュメント
tkinter、Qt、WxPython、Remiののラッパーで公式ではもとのライブラリーで書く場合を比べて、コード量が2分の1から10分の1程度でかけると書いてあります。
私自身も実際に書いてみてTkinterで書くよりもはるかに短いコード量でかけると思いました。最大の特徴は、リストを使用して、UIのレイアウトを配置することです。
実際に使用して見ましょう。インストール方法はpipで簡単にインストールできます
pip install pysimplegui or pip3 install pysimplegui簡単な使い方は以下のとおりです。公式の以下のサンプルをもとに作成しています。
- 1 Shot - Simple Data Entry - Return Values - Auto Numbered
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event == None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
実行結果
実行ボタンを押した場合
いかがでしょうか?
レイアウトは以下で定義しています。2次元のリストでレイアウトを定義しています。layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')]そして取得した値は以下で取得できます
if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。"詳しくは公式ドキュメントを読むことをおすすめしますが、
Tkinterでは54行だったものがpySympleGUIですと28行でかけます。
なにより、Tkinterで書いたものよりもレイアウトや値の取得方法が格段にわかりやすいです。checkboxを使ってみる
先程のコードにデフォルトでチェックが入っているチェックボックスを追加してみます。
- 公式のcheckboxのリファレンス
コードはいかになります
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Checkbox('同意する', default=True)], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event is None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。\n" show_message += "同意のチェックは:" + str(values[3]) + "です" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
起動直後の画面
実行ボタンを押した結果
チェックボックスの値はBool方でture,falseで取得できます。
Tkinterでチェックボックスでデフォルトの値を設定しようとおもうとちょっとめんどうだったものが簡単に設定できます。Tkinterのチェックボックスの設定方法については以下の記事が参考になります。
- PythonのTkinterを使ってみる(最初からChecked,Uncheckedの値をもたせる)
PySympleGUIは他にもTkinterを生で使うよりも、簡単にウィジェットが使用できます。また公式のサンプルが豊富です。matplotlibやopenCvを使用した例などが豊富にあります。
exe化について
exe化はPyInstllerを使用して簡単にできます
以下は公式の以下の説明からの引用です
- Creating a Windows .EXE File
単一ファイルの作り方は以下になります
pip install PyInstaller pyinstaller -wF my_program.py
-wF
をつけることで単一ファイルができますまた以下のエラーが出た場合は、
--hidden-import tkinter
をオプションに加えます。ValueError: script '.......\src\tkinter' not foundMacだと以下でできるそうです(公式では確かめていないそうです)
pyinstaller --onefile --add-binary = '/ System / Library / Frameworks / Tk.framework / Tk': 'tk' --add-binary = '/ System / Library / Frameworks / Tcl.framework / Tcl': ' tcl 'your_program.pyこの情報はRedditにあり、ソースはhttps://github.com/pyinstaller/pyinstaller/issues/1350 から引用しています。
ライセンスについて
PySympleGUIはライセンスはLGPL3.0になります。
MITライセンスではないですが、再配布しない限り社内や個人で利用するにあたっての使用には問題がないはずですので通常使用する分には問題がないはずです。今後の予定
時間があれば公式のサンプルプログラムからいくつかの紹介とTkinterで作られたGUIをPySympleGUIで書き直した場合のサンプルを書こうかと考えています。
- 投稿日:2019-10-02T23:49:13+09:00
Tkinterを使うのであればPySympleGUIを使ってみたらという話
Tkinterを使うのであればPySympleGUIを使ってみたらという話
ページを読んでできるもの
- Pythonで以下の入力内容のGUIが簡単に作れます
- 基本的な入力(テキストボックス、チェックボックス)
- テキストボックスにはデフォルトで入力が、チェックボックスで入力できます
- 入力された内容をポップアップで表示する
起動直後の画面
実行ボタンを押した結果
Tkinterについて
Tkinterは標準のPythonのGUIライブラリです。個人的に考えるメリット、デメリットは以下の通りです。
メリット
- 標準だからインストール不要
- サンプルはそこそこ多い
デメリット
- ウィジェット(UIパーツ)を配置する際、種類、オプション、レイアウトと順を追って設定しないといけない
- ウィジェットの配置が増えると、レイアウトがコードを見てもよくわからない
Tkinterの例
実際にTkinterについて実際にコードを載せます
PythonでGUIアプリを作る方法【Tkinter】
の記事の最後のコードと実行結果をこちらに転載します実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードは以下になります
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_value = input_box.get() messagebox.showinfo("クリックイベント",input_value + "が入力されました。") #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x240") #入力欄の作成 input_box = tkinter.Entry(width=40) input_box.place(x=10, y=100) #ラベルの作成 input_label = tkinter.Label(text="ラベル") input_label.place(x=10, y=70) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=130) #ウインドウの描画 root.mainloop()ウィジェット(UIのパーツ)が縦一列に並んだ場合は比較的簡単です。
問題は横一列に「ラベル」+「テキストボックス」というようなよくあるレイアウトで複数のウィジェットを配置した場合です実行結果
テキストボックスに入力後に「実行ボタン」を押した結果
コードはいかになります。
import tkinter from tkinter import messagebox #ボタンがクリックされたら実行 def button_click(): input_name_value = input_name.get() input_address_value = input_address.get() input_phone_value = input_phone.get() show_message = "名前:" + input_name_value + 'が入力されました。\n' show_message += "住所:" + input_address_value + 'が入力されました。\n' show_message += "電話番号:" + input_phone_value + "が入力されました。" print(show_message) # 入力内容をポップアップ画面で表示 messagebox.showinfo("入力内容" ,show_message) #ウインドウの作成 root = tkinter.Tk() root.title("Python GUI") root.geometry("360x120") # 名前 input_name_label = tkinter.Label(text="名前") input_name_label.grid(row=1, column=1, padx=10,) # 入力欄の作成 input_name = tkinter.Entry(width=40) input_name.grid(row=1, column=2) # 住所 input_address_label = tkinter.Label(text="住所") input_address_label.grid(row=2, column=1, padx=10,) # 住所入力欄の作成 input_address = tkinter.Entry(width=40) input_address.grid(row=2, column=2) # 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,) # 電話番号入力欄の作成 input_phone = tkinter.Entry(width=40) input_phone.grid(row=3, column=2) #ボタンの作成 button = tkinter.Button(text="実行ボタン",command=button_click) button.place(x=10, y=80) #ウインドウの描画 root.mainloop()問題なのは以下のところです。
# 電話番号 input_phone_label = tkinter.Label(text="名前") input_phone_label.grid(row=3, column=1, padx=10,)Tkinterはウィジェットを配置する際はまず自分が何(ラベル、ボタン、テキストボックスetc)を設定します。
そしてそのあとに配置する場所を指定します。Tkinterでグリッドレイアウトを用いる際は、grid関数にrow,columnを使ってレイアウトを調整します。今回は縦×横が3×2のレイアウトなのでまだいいですが、例えば5×10などウィジェットが増えていくと、コードをぱっと見ただけではレイアウトがよくわからなくなります。
PySympleGUI
PySympleGUIは2018年から開発が始まったライブラリーです。
- github
- 公式ドキュメント
tkinter、Qt、WxPython、Remiののラッパーで公式ではもとのライブラリーで書く場合を比べて、コード量が2分の1から10分の1程度でかけると書いてあります。
私自身も実際に書いてみてTkinterで書くよりもはるかに短いコード量でかけると思いました。最大の特徴は、リストを使用して、UIのレイアウトを配置することです。
実際に使用して見ましょう。インストール方法はpipで簡単にインストールできます
pip install pysimplegui or pip3 install pysimplegui簡単な使い方は以下のとおりです。公式の以下のサンプルをもとに作成しています。
- 1 Shot - Simple Data Entry - Return Values - Auto Numbered
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event == None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
実行結果
実行ボタンを押した場合
いかがでしょうか?
レイアウトは以下で定義しています。2次元のリストでレイアウトを定義しています。layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Submit(button_text='実行ボタン')]そして取得した値は以下で取得できます
if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。"詳しくは公式ドキュメントを読むことをおすすめしますが、
Tkinterでは54行だったものがPySympleGUIですと28行でかけます。
なにより、Tkinterで書いたものよりもレイアウトや値の取得方法が格段にわかりやすいです。checkboxを使ってみる
先程のコードにデフォルトでチェックが入っているチェックボックスを追加してみます。
- 公式のcheckboxのリファレンス
コードはいかになります
import PySimpleGUI as sg layout = [ [sg.Text('Python GUI')], [sg.Text('名前', size=(15, 1)), sg.InputText('○○〇×××')], [sg.Text('住所', size=(15, 1)), sg.InputText('△△△△村')], [sg.Text('電話番号', size=(15, 1)), sg.InputText('xxx-xxx-xxx')], [sg.Checkbox('同意する', default=True)], [sg.Submit(button_text='実行ボタン')] ] window = sg.Window('Simple data entry window').Layout(layout) while True: event, values = window.Read() # if event == None or event == 'Exit': if event is None: print('exit') break if event == '実行ボタン': show_message = "名前:" + values[0] + 'が入力されました。\n' show_message += "住所:" + values[1] + 'が入力されました。\n' show_message += "電話番号:" + values[2] + "が入力されました。\n" show_message += "同意のチェックは:" + str(values[3]) + "です" print(show_message) # ポップアップ sg.Popup(show_message)実行結果はいかになります。
起動直後の画面
実行ボタンを押した結果
チェックボックスの値はBool方でture,falseで取得できます。
Tkinterでチェックボックスでデフォルトの値を設定しようとおもうとちょっとめんどうだったものが簡単に設定できます。Tkinterのチェックボックスの設定方法については以下の記事が参考になります。
- PythonのTkinterを使ってみる(最初からChecked,Uncheckedの値をもたせる)
PySympleGUIは他にもTkinterを生で使うよりも、簡単にウィジェットが使用できます。また公式のサンプルが豊富です。matplotlibやopenCvを使用した例などが豊富にあります。
exe化について
exe化はPyInstllerを使用して簡単にできます
以下は公式の以下の説明からの引用です
- Creating a Windows .EXE File
単一ファイルの作り方は以下になります
pip install PyInstaller pyinstaller -wF my_program.py
-wF
をつけることで単一ファイルができますまた以下のエラーが出た場合は、
--hidden-import tkinter
をオプションに加えます。ValueError: script '.......\src\tkinter' not foundMacだと以下でできるそうです(公式では確かめていないそうです)
pyinstaller --onefile --add-binary = '/ System / Library / Frameworks / Tk.framework / Tk': 'tk' --add-binary = '/ System / Library / Frameworks / Tcl.framework / Tcl': ' tcl 'your_program.pyこの情報はRedditにあり、ソースはhttps://github.com/pyinstaller/pyinstaller/issues/1350 から引用しています。
ライセンスについて
PySympleGUIはライセンスはLGPL3.0になります。
MITライセンスではないですが、再配布しない限り社内や個人で利用するにあたっての使用には問題がないはずですので通常使用する分には問題がないはずです。今後の予定
時間があれば公式のサンプルプログラムからいくつかの紹介とTkinterで作られたGUIをPySympleGUIで書き直した場合のサンプルを書こうかと考えています。
- 投稿日:2019-10-02T23:13:00+09:00
sklearn.cross_varidationが使えない件
sklearn.cross_varidationがimportできない
sklearn.cross_varidationからimportしようと思ったが下記のようなエラーが出る.
SyntaxError: invalid syntax
調べてみたあっさり原因が見つかった.
cross_varidationの廃止
原因は,cross_varidationの廃止だったそうな.じゃあなに使えばいいのっていうとsklearn.model_selectionというのを代わりに使えばいいらしい.
具体的には...
from sklearn.model_selection import cross_val_score
こんな感じ
大体のクラスや関数は置き換えなのでcross_validationを書き換えるだけでいいっぽい
ソースはここ
ImportError: No module named 'sklearn.cross_validation'の対処
- 投稿日:2019-10-02T22:38:04+09:00
tkinterのタイトル横のアイコンをコードに埋め込む方法
この記事では、Windows10の環境で、Python 3.7.4を使っています。
pythonのデスクトップアイコンの変更方法
pythonのデスクトップアイコンは通常pyinstallerを使うと、
pyinstallerpyinstaller ソース.py --icon=アイコンファイル.icoで変更することができます。
ですが、tkinterでウインドウを作成したときに、
が変更できない。
ここも変更したいと思う。iconbitmapを使う方法
icon.icoを作成してある場合、
ico.pyimport tkinter as tk root = tk.Tk() tk.iconbitmap(default='icon.ico') root.mainloop()とすると、同じフォルダにあるicon.icoが表示されます。
ただし、pyinstallerで--onefileを指定してコンパイルしても同一ファイルにコンパイルされずに、icon.icoファイルを同一フォルダに準備しないといけません。これでは、少しかっこ悪いです。アイコンをコードに埋め込む
アイコンをbase64でエンコードして埋め込む方法があります。まず画像ファイル(a.gif)を用意します。(gifフィアルがいいようです。)
これをbase64でエンコードするために、Windows PowerSellを起動し、certutilcertutil -encode a.gif text.txt
とすると、同じフォルダにtext.txtというファイルができます。中身は、
certutil-----BEGIN CERTIFICATE----- R0lGODlhEAAQAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr /wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCq mQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMA MzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV /zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPV mTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYr M2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA /2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/ mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlV M5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq /5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswA mcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyA M8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV /8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8r mf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+q M/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP// /wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAQABAAAAh7APcJHEiwoMGDCAXWU8aQ mDKCDRlmYsgQorKF0JQR26exYSaBkz46pHgRWr2BysQIZLhwJUeN+9A85Diyo0Zo +8TcQJmxIs6VAD4qdLhv4UmBYmIUJFlxHzEAGwlunDkwxs6lTqPuywRgJlVlPwfq LBq26MOeMRNSTRgQADs= -----END CERTIFICATE-----という中身のファイルができます。これの文字の羅列の部分をとってきて、コードに埋め込みます。
base64.pyimport tkinter as tk root = tk.Tk() data = '''R0lGODlhEAAQAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr /wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCq mQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMA MzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV /zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPV mTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYr M2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA /2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/ mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlV M5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq /5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswA mcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyA M8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV /8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8r mf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+q M/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP// /wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAQABAAAAh7APcJHEiwoMGDCAXWU8aQ mDKCDRlmYsgQorKF0JQR26exYSaBkz46pHgRWr2BysQIZLhwJUeN+9A85Diyo0Zo +8TcQJmxIs6VAD4qdLhv4UmBYmIUJFlxHzEAGwlunDkwxs6lTqPuywRgJlVlPwfq LBq26MOeMRNSTRgQADs= ''' img = tk.PhotoImage(data=data) label = tk.Label(image=img) label.pack() root.mainloop()とすると、
↑のようなコード埋め込み画像を表示することができます。タイトル横のアイコンをコードに埋め込む方法
ここまで来れば、あと少しです。今埋め込んだ画像をタイトル横に表示してあげればいいのです。ここで
title.pyroot.tk.call('wm', 'iconphoto', root._w, tk.PhotoImage(data=data))をつかって、アイコンを表示します。
title.pyimport tkinter as tk root = tk.Tk() data = '''R0lGODlhEAAQAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr /wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCq mQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMA MzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV /zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPV mTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYr M2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA /2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/ mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlV M5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq /5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswA mcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyA M8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV /8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8r mf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+q M/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP// /wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAQABAAAAh7APcJHEiwoMGDCAXWU8aQ mDKCDRlmYsgQorKF0JQR26exYSaBkz46pHgRWr2BysQIZLhwJUeN+9A85Diyo0Zo +8TcQJmxIs6VAD4qdLhv4UmBYmIUJFlxHzEAGwlunDkwxs6lTqPuywRgJlVlPwfq LBq26MOeMRNSTRgQADs= ''' root.tk.call('wm', 'iconphoto', root._w, tk.PhotoImage(data=data)) root.mainloop()という風になります。
これでpyinstallerでインストールしても、icon.icoを置く必要もなく一つのファイルで起動することができます。
- 投稿日:2019-10-02T22:35:45+09:00
RaspberryPiで監視カメラ 暗い部屋の撮影編
はじめに
前回、RaspberryPiで監視カメラを作成したのですが、暗い部屋でベビーモニタとして利用するには工夫が必要という状況になっていました。
暗い部屋で使うために
- USBライトの制御
- カメラの設定変更
について調査してみたので、まとめておきます。
USBライトの制御
ハードウェア
Can☆Doで買ったUSBライトを使用します。
RaspberryPiに差してもlsusbで認識されないので、クラスとして認識するような仕組みはなくUSBコネクタから電力供給しているだけの動作のようです。
たぶん、どのメーカのUSBライトも同じ仕様だと思います。
Linux側からUSBポートの電源供給を制御する方法を探してみます。hub-ctrl
ぐぐったら、hub-ctrlというソフトを使うことでUSBの制御ができるとのこと。
手順通りにgccでビルドしてインストールできました。
gcc -o hub-ctrl hub-ctrl.c -lusb以下のコマンドで点灯、消灯ができました。
sudo hub-ctrl -h 0 -P 2 -p 0 sudo hub-ctrl -h 0 -P 2 -p 1権限設定等
pythonからhub-ctrlを起動する場合にsudoのパスワード入力が問題になるので回避策を探します。
最初はsudoしなくてもUSB操作ができるグループを追加すればいけると思いましたが、そういうグループは無いんですね。
グループを追加はあきらめてsudoのパスワード要求をしないように設定を変更します。sudo visudo/usr/local/bin/hub-ctrl にコピーしたのでこれにNOPASSWDを指定した行を追加します
monitor_user ALL=NOPASSWD: /usr/local/bin/hub-ctrlこれでsudoでのパスワード要求されなくなりますので、pythonからの呼び出しに支障はなくなりました。
クライアント側
HTML
点灯制御するトリガーのボタンを用意します。
usb.html<html> <head> <script src="./usb.js"></script> </head> <body onload="on_load();"> <input type="button" value="usb" onclick="on_button_light();"> <div> <canvas id="canvas_image" width="640" height="480"></canvas> </div> </body> </html>javascript
ボタンのハンドラでUSBライトの点灯制御を要求するコマンドを送信します。
usb.jsvar image_socket = null; var usb_socket = null; var mode_usb = true; function on_load() { // 画像通信用WebSocket接続 img_url = "ws://" + location.hostname + ":60002" image_socket = new WebSocket(img_url); image_socket.binaryType = 'arraybuffer'; image_socket.onmessage = on_image_message; usb_url = "ws://" + location.hostname + ":60004" usb_socket = new WebSocket(usb_url); usb_socket.binaryType = 'arraybuffer'; } function on_button_light() { mode_usb = mode_usb ? false : true; var command_data = { mode: mode_usb }; usb_socket.send(JSON.stringify(command_data)); } function on_image_message(recv_data) { // 受信したデータをbase64文字列に変換 var recv_image_data = new Uint8Array(recv_data.data); var base64_data = "" for (var i=0; i < recv_image_data.length; i++) { base64_data += String.fromCharCode(recv_image_data[i]); } // 画像をcanvasに描画 var canvas_image = document.getElementById('canvas_image'); var ctx = canvas_image.getContext('2d'); var image = new Image(); image.onload = function() { ctx.drawImage(image, 0, 0); } image.src = 'data:image/jpeg;base64,' + window.btoa(base64_data); }サーバー側
USBの電源制御するWebSocketのサーバーを用意します。
USB制御サーバー
クライアント側で送信したON/OFFの要求を受けてhub-ctrlを呼び出します。
UsbServer.pyimport asyncio import websockets import json import subprocess class UsbServer: def __init__(self, loop, address, port): self.loop = loop self.address = address self.port = port async def _handler(self, websocket, path): while True: try: recv_data = await websocket.recv() dic_data = json.loads(recv_data) except: print('usb recv Error.') break if dic_data['mode']: command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '0'] else: command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '1'] print(command) try: process = subprocess.Popen(command, stdout=subprocess.PIPE) output = process.stdout.read().decode('utf-8') print(output) except: print('usb proc Error.') def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() if __name__ == '__main__': loop = asyncio.get_event_loop() wss = UsbServer(loop, '0.0.0.0', 60004) wss.run()USBの親HUBごと電源をOFFにするのでUSB AudioもOFFになるのは問題ですが、電源制御対応の子USB HUBを挟めば対応できるそうです。(手持ちのHUBでは動作確認できませんでした)
カメラの設定変更
USBライトの明かりだけはうまく撮影できなかったので、カメラ側の設定もいじることにします。
PiCameraで変更できるプロパティで明るさを制御します。
明るく撮影する設定
前回作成したImageサーバーにフレームレート、シャッタースピード、露出補正の設定を追加します。
公式の説明を読んでいろいろ設定してみましたが、バージョンで動作が違うので正しい設定がよくわかりませんでした。ネットの情報もばらばらで正解がよくわからないです。試行錯誤してうまくいった設定値にしています。
ImageServer.pyimport asyncio import websockets import io from picamera import PiCamera class ImageServer: def __init__(self, loop, address , port): self.loop = loop self.address = address self.port = port self.camera = PiCamera() self.camera.framerate = 1 self.camera.exposure_compensation = 10 self.camera.shutter_speed = 1000 * 8000 async def _handler(self, websocket, path): with io.BytesIO() as stream: for _ in self.camera.capture_continuous(stream, format='jpeg', use_video_port=True, resize=(640,480)): stream.seek(0) try: await websocket.send(stream.read()) except: print('image send Error.') break stream.seek(0) stream.truncate() def run(self): self._server = websockets.serve(self._handler, self.address, self.port) self.loop.run_until_complete(self._server) self.loop.run_forever() if __name__ == '__main__': loop = asyncio.get_event_loop() ws_is = ImageServer(loop, '0.0.0.0', 60002) ws_is.run()動作確認
シャッタースピードが遅いのでライトのON/OFFしてから少し待たないと更新されないですが、それなりに撮影できています。
おわりに
USBの電源制御とシャッタースピードの制御でなんとか実用レベルになりました。
- 投稿日:2019-10-02T21:49:58+09:00
【Python Pandas メモ】
始めに
Python DataFrameに読み込んだ日付型の項目で絞り込みをする方法に手間取ったので、その時のメモ。
ポイント1
日付型データは pd.to_datetime関数を使って列ごと型変換する。
from ibmdbpy import IdaDataBase, IdaDataFrame idadb = ... # (省略) df_result = IdaDataFrame(idadb, 'TBL_RESULT').as_dataframe() df_result['CREATE_DATE'] = pd.to_datetime(df_result['CREATE_DATE'])ポイント2
検索時は検索条件側も datetime.date関数で変換した値を使う。
import datetime w1= df_plan.query("EMPLOYEE_CD=='1234' and CREATE_DATE==datetime.date(2019,1,10)")
- 投稿日:2019-10-02T21:49:58+09:00
【Python Pandas メモ】DataFrameで日付型項目で検索する方法
始めに
Python DataFrameに読み込んだ日付型の項目で絞り込みをする方法に手間取ったので、その時のメモ。
ポイント1
日付型データは pd.to_datetime関数を使って列ごと型変換する。
from ibmdbpy import IdaDataBase, IdaDataFrame idadb = ... # (省略) df_plan = IdaDataFrame(idadb, 'TBL_RESULT').as_dataframe() df_plan['CREATE_DATE'] = pd.to_datetime(df_plan['CREATE_DATE'])ポイント2
検索時は検索条件側も datetime.date関数で変換した値を使う。
import datetime w1= df_plan.query("EMPLOYEE_CD=='1234' and CREATE_DATE==datetime.date(2019,1,10)")
- 投稿日:2019-10-02T21:48:48+09:00
シャニマスの全R~SSR画像を保存するスクリプト
アイドルマスター シャイニーカラーズ、絵が良い。そんなシャイニーカラーズ(以下、シャニマス)の画像を全部保存したくありませんか?
ただ、シャニマスのキャラって今(2019/10/01現在)252種類あるわけで...一つ一つページ探して保存するのはめんどくさいんですよね...
そこで!!スクレイピングを使って全部勝手に保存してもらおう!というわけです。
注意!!
https://topcourt-law.com/internet_security/scraping-illegal
上を読んでもらった方が早いんですけど著作権法などの絡みがあるっていうのとサーバーに負担をかけてしまう行為なので慎重に行ってください。
使用環境
Windows 10 Education
Python 3.7.3ソースコード
shiny_scraping.pyimport requests from bs4 import BeautifulSoup as bs import os import urllib.request import urllib.error j = 0 for num in range(1, 253): URL = 'https://imas-shinycolors.boom-app.wiki/entry/card-' + str(num) # URL入力 images = [] # 画像リストの配列 soup = bs(requests.get(URL).content,'lxml') # bsでURL内を解析 for link in soup.find_all("img"): # imgタグを取得しlinkに格納 s = link.get("src") if s.endswith("w=960") or s.endswith("w=600"): images.append(link.get("src")) for img in images: j += 1 try: with urllib.request.urlopen(img) as w: data = w.read() with open('C:/Users/[ユーザー名]/Pictures/ShinyColors/' + str(j) + '.jpg', mode='wb') as m: m.write(data) except urllib.error.URLError as e: print(e) #保存できなかった時のため print("No." + str(num) + " is ok") # 確認解説
前半部分
for num in range(1, 253): URL = 'https://imas-shinycolors.boom-app.wiki/entry/card-' + str(num) # URL入力 images = [] # 画像リストの配列 soup = bs(requests.get(URL).content,'lxml') # bsでURL内を解析 for link in soup.find_all("img"): # imgタグを取得しlinkに格納 s = link.get("src") if s.endswith("w=960") or s.endswith("w=600"): images.append(link.get("src"))1行目...今回はシャニマス攻略wikiを使って抜き出していこうと思います。252種類あるのでrangeを1~253にして(Pythonを使ったことある人は分かると思いますが、範囲指定は必要な数+1します)、
2行目...URLに番号を含めたものを指定します。
3行目...画像urlを格納するリストを初期化します
4行目...2行目で定義したURLの中身を取得します。この時の内容はlxml形式で取得されます。基本的にスクレイピングは、ページを構成しているhtmlから必要な情報をタグやid、クラスなどを指定することで取得する感じになります。例えば、【ほわっとスマイル】櫻木真乃の場合だと、
URL: https://imas-shinycolors.boom-app.wiki/entry/card-1
の中は以下の通りになっています。
card-1<!DOCTYPE html> <html lang="ja" xmlns:og="http://ogp.me/ns#"> <head> <meta charset="UTF-8"> (略) <div class="breadcrumbs"> <ul> <li><a href="/"><i class="fa fa-home" aria-hidden="true"></i> シャニマス攻略Wiki</a></li> <li><a href="https://imas-shinycolors.boom-app.wiki/entry/card-list">全カード一覧</a></li> <li>SSR【ほわっとスマイル】櫻木真乃のスキルとステータス</li> (略) <div class="imgList1"> <div class="ss"><div class="image-zoom" data-url="https://image.boom-app.wiki/wiki/5a824ae1b1b4b803847278d1/35a59c77b6e647b3c56a66662fa46329.jpg?w=600"> <img src="https://image.boom-app.wiki/wiki/5a824ae1b1b4b803847278d1/35a59c77b6e647b3c56a66662fa46329.jpg?w=600" alt=""> </div> </div> </div> (略) <h3>フェスアイドルイラスト</h3> <div class="imgList1"> <div class="ss"><div class="image-zoom" data-url="https://image.boom-app.wiki/wiki/5a824ae1b1b4b803847278d1/card/fes/001.jpg?w=960"> <img src="https://image.boom-app.wiki/wiki/5a824ae1b1b4b803847278d1/card/fes/001.jpg?w=960" alt="【ほわっとスマイル】櫻木真乃"> </div> </div> </div> <a name="toc2" id="toc2"></a> <h2>SSR【ほわっとスマイル】櫻木真乃の演出まとめ</h2> (略) </body> </html>今回取得したい画像(通常絵、フェスアピール時絵)は、いずれも
.jpg
の後に?w=600 or 960
がついているのがわかると思います、これによって画像の幅を指定しているのですが、今回はこれを逆手にとってこの2種類の画像のみを取得するようにします。5行目から...4行目で取得したもののなかから
img
タグの文のみを指定し、その中でも上記の通り、?w=960
か?w=600
で終わっているものを指定し、取得します。(これがないと、結構色々な画像が入ってしまうので選別が大変なことになります。)取得したいもの(リンク形式になっている)をimagesリストに格納します。後半部分
for img in images: j += 1 try: with urllib.request.urlopen(img) as w: data = w.read() with open('C:/Users/[ユーザー名]/Pictures/ShinyColors/' + str(j) + '.jpg', mode='wb') as m: m.write(data) except urllib.error.URLError as e: print(e) #保存できなかった時のため print("No." + str(num) + " is ok") # 確認1~2行目...1imagesリストのなかから1枚ずつ取り出します。今回は保存するときの画像名を「(番号).jpg」にしたかったので上でjという引数を用意し、1枚画像を保存するごとに1ずつ加えています。
3~7行目...1枚ずつリンクの画像を読み込んで取得したものをopenの第一引数で指定したフォルダに保存しています。
8~9行目...取得できなかったらどういうエラーが起こったかをターミナルに出力します
10行目...コンソールに進捗を書き出しています。実行結果
実行すると1つずつこんな感じで実行されます。だいたい1つ実行するのに0.5~1秒かかるので3分ほどで終わると思います。この通り、見事に保存されていることがわかります。是非法の範囲で使って見てください~
- 投稿日:2019-10-02T19:43:46+09:00
ssl.get_server_certificateで証明書を取得できない場合の対処
概要
Python でSSL証明書の情報を確認したい場合、以下のようなコードが紹介されていることが多い。
import ssl import OpenSSL # required pyopenssl cert = ssl.get_server_certificate(('www.google.com', 443)) x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)試してみると、多くの場合はこの方法で正常に情報が取得できたが、正常に情報が取得できないケースがあったため、調査を実施した。
実際のところ invoke (Fabric 2) でローカルの openssl コマンド叩けばうまくいったけれど、何故 Python でやるとうまくいかなかったのかが疑問だったので。
原因
Server Name Indication(SNI) の指定がないため。
openssl s_client
コマンドで言えば-servername
が未指定のため。IPアドレス直打ちの場合などにアクセスさせないためや、1つのサーバー上で複数のドメイン管理をしている場合などにSNI設定なしでアクセスすると別設定の証明書を返してきたり、そもそも返さなかったりする。
わざと
openssl
コマンドで-servername example.com
など全く別のサーバーネームを与えた場合に同様の事象が再現した。対処
原因がSNIと分かれば同じような質問があった。 結果としては、
ssl.get_server_certificate
は使わず、その中身と同じようなことを自分で書いてやる必要がある。import socket import ssl import OpenSSL def get_server_certificate(hostname): context = ssl.create_default_context() with socket.create_connection((hostname, 443)) as sock: with context.wrap_socket(sock, server_hostname=hostname) as sslsock: der_cert = sslsock.getpeercert(True) return ssl.DER_cert_to_PEM_cert(der_cert) cert = get_server_certificate('www.google.com') x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
- 投稿日:2019-10-02T19:29:58+09:00
Atcoderコンテスト用部品まとめ
この記事の目的
・Atcoderでのコンテスト本番中にあたふた慌てないように整理すること
・問題を解いてる途中に参照しやすい状態にすること
・アウトプット作業に慣れること
・Markdown記法になれること
・ページ内リンクがやりたかった(githubではうまくいかなかった)【随時更新予定】
目次 1. 入力編 2. リスト編 3. 数値編 4. 書き方編 5. 出力編 1. 入力編
1.0 入力用
import sys input = sys.stdin.readlineこれで
input()
だけより早くなるらしい1.2 単入力
n=int(input())1.3 複数入力
n,m=(int(x) for x in input().split())1.4 リスト入力
l = [int(i) for i in input().split()]2. リスト編
2.1 0リスト,空リスト
l=[0]*n l=[]2.2 リスト要素追加
l.append(n)2.3 リスト重複なし
l=set(l)2.4 リスト大きい順ソート
l.sort(reverse=True)3. 数値編
3.1 素因数分解リスト列挙
def factorize(n): b = 2 fct = [] while b * b <= n: while n % b == 0: n //= b fct.append(b) b = b + 1 if n > 1: fct.append(n) return fct3.2 偶数True奇数False
def evenjudge(obj): if obj %2==0: return True else: return False4. 書き方編
4.1 2重ループ
import itertools for i,j in itertools.product(range(n), range(n)): pass4.2 リスト内組み合わせ
import itertools itertools.combinations(list,n)5. 出力編
5.1 スペース間隔出力
print(s,end=' ')大体ループの中に入れてる
github
- 投稿日:2019-10-02T19:29:58+09:00
AtCoderコンテスト用部品まとめ
この記事の目的
・Atcoderでのコンテスト本番中にあたふた慌てないように整理すること
・問題を解いてる途中に参照しやすい状態にすること
・アウトプット作業に慣れること
・Markdown記法になれること
・ページ内リンクがやりたかった(githubの.mdではうまくいかなかった)
→(2019/10/02時点)あれ、うまくリンクとんでない、、、【随時更新予定】
目次 1. 入力編 2. リスト編 3. 数値編 4. 書き方編 5. 出力編 1. 入力編
1.0 入力用
import sys input = sys.stdin.readlineこれで
input()
だけより早くなるらしい1.2 単入力
n=int(input())1.3 複数入力
n,m=(int(x) for x in input().split())1.4 リスト入力
l = [int(i) for i in input().split()]2. リスト編
2.1 空リスト,要素が0だけのリスト
l=[] l=[0]*n2.2 リストに要素追加
l.append(n)2.3 重複なしのリスト
l=set(l)2.4 リストを大きい順(降順)にソート
l.sort(reverse=True)3. 数値編
3.1 素因数分解リスト列挙
def factorize(n): b = 2 fct = [] while b * b <= n: while n % b == 0: n //= b fct.append(b) b = b + 1 if n > 1: fct.append(n) return fct3.2 偶奇判断:偶数True奇数False
def evenjudge(obj): if obj %2==0: return True else: return False4. 書き方編
4.1 2重ループを1行に
import itertools for i,j in itertools.product(range(n), range(n)): pass計算量は変わらないからAtcoder的にはいらない?
4.2 リスト内組み合わせ
import itertools itertools.combinations(list,n)計算量多めだから注意
5. 出力編
5.1 スペース間隔出力
print(s,end=' ')大体ループの中に入れてる
github
- 投稿日:2019-10-02T19:29:58+09:00
AtCoderコンテスト用まとめ【python】
この記事の目的
・Atcoderでのコンテスト本番中にあたふた慌てないように整理すること
・問題を解いてる途中に参照しやすい状態にすること
・アウトプット作業に慣れること
・Markdown記法に慣れること
・ページ内リンクがやりたかった(githubの.mdではうまくいかなかった)
→(2019/10/02時点)あれ、うまくリンクとんでない、、、【随時更新予定】
目次 1. 入力編 2. リスト編 3. 数値編 4. 書き方編 5. 出力編 1. 入力編
1.0 入力用
import sys input = sys.stdin.readlineこれで
input()
だけより早くなるらしい1.2 単入力
n=int(input())1.3 複数入力
n,m=(int(x) for x in input().split())1.4 リスト入力
l = [int(i) for i in input().split()]2. リスト編
2.1 空リスト,要素が0だけのリスト
l=[] l=[0]*n2.2 リストに要素追加
l.append(n)2.3 重複なしのリスト
l=set(l)2.4 リストを大きい順(降順)にソート
l.sort(reverse=True)3. 数値編
3.1 素因数分解リスト列挙
def factorize(n): b = 2 fct = [] while b * b <= n: while n % b == 0: n //= b fct.append(b) b = b + 1 if n > 1: fct.append(n) return fct3.2 偶奇判断:偶数True奇数False
def evenjudge(obj): if obj %2==0: return True else: return False4. 書き方編
4.1 2重ループを1行に
import itertools for i,j in itertools.product(range(n), range(n)): pass計算量は変わらないからAtcoder的にはいらない?
4.2 リスト内組み合わせ
import itertools itertools.combinations(list,n)計算量多めだから注意
5. 出力編
5.1 スペース間隔出力
print(s,end=' ')大体ループの中に入れてる
ToDo
itertools編とか増やしていきたい
github
- 投稿日:2019-10-02T19:29:58+09:00
AtCoderコンテスト用まとめ【Python】
AtCoder
この記事の目的
- Atcoderでのコンテスト本番中にあたふた慌てないように整理すること
- 問題を解いてる途中に参照しやすい状態にすること
- アウトプット作業に慣れること
- Markdown記法に慣れること
- ページ内リンクがやりたかった(githubの.mdではうまくいかなかった)
→(2019/10/02時点)あれ、うまくリンクとんでない、、、どうして、、、(勉強中)【随時更新予定】
目次 0. フォーマット編 1. 入力編 2. リスト編 3. 数値編 4. 書き方編 5. 出力編 0. フォーマット編
contest_base.pyimport sys import itertools input = sys.stdin.readline #単入力 n=int(input()) #複数入力 n,m=(int(x) for x in input().split()) #リスト入力 l = [int(i) for i in input().split()]基本的にこれをファイルコピーして使いまわしてます
1. 入力編
1.0 入力用
import sys input = sys.stdin.readlineこれで
input()
だけより早くなるらしい1.1 単入力
n=int(input())1.2 複数入力
n,m=(int(x) for x in input().split())1.3 リスト入力
l = [int(i) for i in input().split()]j1
j2
.
.
.
jN
のようにタテに並んでいるときはj=[] for _ in range(n): j.append(input())2. リスト編
2.1 空リスト,要素が0だけのリスト
l=[] l=[0]*n2.2 リストに要素追加
l.append(n)2.3 重複なしのリスト
l=set(l)2.4 リストを大きい順(降順)にソート
l.sort(reverse=True)2.5 優先度付きキュー
例として、【「リストaの最大値を1/2してリストaに戻す」をm回繰り返す】
import heapq a = list(map(lambda x: int(x)*(-1), input().split())) heapq.heapify(a) for _ in range(m): temp = heapq.heappop(a) heapq.heappush(a, (-1)*(-temp//2))
優先度付きキューを使わない場合:
a = [int(i) for i in input().split()] a.sort(reverse=True) for _ in range(m): a[0]=a[0]//2 a.sort(reverse=True)この場合いちいち全要素をソートしなおすので計算量が膨大になってTLEとなる
3. 数値編
3.1 素因数分解リスト列挙
def factorize(n): b = 2 fct = [] while b * b <= n: while n % b == 0: n //= b fct.append(b) b = b + 1 if n > 1: fct.append(n) return fct3.2 偶奇判断:偶数True奇数False
n%2==03.3 最大公約数,最小公倍数
nとmの最大公約数ansを求める
import math ans=math.gcd(n,m)最小公倍数はgcdを用いて
def lcm(n,m): return (n*m)//math.gcd(n,m)3.4 約数列挙,公約数列挙
def make_divisors(n): divisors = [] for i in range(1, int(n**0.5)+1): if n % i == 0: divisors.append(i) if i != n // i: divisors.append(n//i) divisors.sort() return divisors公約数列挙は
make_divisors
を用いて、ansdiv=set(make_divisors(n))&set(make_divisors(m))4. 書き方編
4.1 2重ループを1行に
import itertools for i,j in itertools.product(range(n), range(n)): pass計算量は変わらないから競プロ的にはいらない?
4.2 リスト内組み合わせ
import itertools itertools.combinations(list,n)計算量多めだから注意
5. 出力編
5.1 スペース区切り出力
print(s,end=' ')大体ループの中に入れてる
ToDo
- もっとバリエーション増やす
- itertools編とか増やしていきたい
github
- 投稿日:2019-10-02T17:46:58+09:00
同Directory配下のClassのimportの注意点
- 投稿日:2019-10-02T17:42:49+09:00
Pythonでbool型のオプション引数を与えたらハマった話
やりたいこと
実行時のオプション引数でTrue/Falseを設定したい。
$ py test.py --hogeFlag False Falseみたいな感じでオプション引数からbool値を取って内部で処理を分けたいと思ってました。
コード
最初はこんな感じのコードでオプション引数を取得していました。
bool以外のオプション引数を使ったスクリプトを組んだことがあったのでその応用でいけるやろと思ってました。def get_args(): parser = argparse.ArgumentParser() parser.add_argument("--hogeFlag", type=bool, default=True) return parser.parse_args()結果
py test.py --hogeFlag False TrueFalseを指定したつもりがTrueになってますね・・・
原因
bool()
でbool値に変換されているのが原因だった。
type=bool
と指定しているとPythonは、設定された引数をbool()
に入れてbool型にしてくれるようです。
type=int
とかにするとint()
を通してint型にしてくれていたおかげで文字列が入るとエラーが起こっていたんですね。
確かにどの言語でも、コマンドライン引数も最初はstring型として取得されてましたね。今回のケースでは下記のコマンドで動かしていました。
$ py test.py --hogeFlag False
type=bool
なので、この場合はbool('False')
という処理を動かしてbool値に変換しようとします。
このbool('False')
はどういう結果になるのか。サクッとPythonWinで動かしてみます>>> print(bool('False')) TrueTrueになってるやん・・・
理由
pythonの真偽値でFalseと判定されるのは次のものらしいです(公式ドキュメントより抜粋)
偽であると定義されている定数: None と False
数値型におけるゼロ: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
空のシーケンスまたはコレクション: '', (), [], {}, set(), range(0)コマンドライン引数やオプション引数は、取得時にはstr型として扱われます。
なので3つ目の空のシーケンスまたはコレクションの部分に判定されます。
''
のような空文字だけがFalseと判定され、それ以外はTrueと判定されるようです。
bool('False')
がTrueと判定される理由がわかりました。対応
add_argument関数の引数には
type
の他にaction
というのもありました。
今回のケースではこちらを使用します。 参考コード
parser.add_argument("--hogeFlag", action='store_true')
action='store_true'
というのが新しいところですね。
動きとしてはこうなります$ py test.py --hogeFlag True $ py test.py False
--hogeFlag
を指定した時だけTrue
で、指定しない時はFalse
になりました!余談ですが真逆の
store_false
というのも設定することができます。動きはstore_true
と反対なだけなのでここでは省きます。まとめ
オプション引数でbool値を与えたいときは
type=bool
ではなくaction='store_true(またはfalse)'
で設定する!
- 投稿日:2019-10-02T17:42:49+09:00
Pythonのargparseでbool型のオプション引数を与えたらハマった話
やりたいこと
実行時のオプション引数でTrue/Falseを設定したい。
$ py test.py --hogeFlag False Falseみたいな感じでオプション引数からbool値を取って内部で処理を分けたいと思ってました。
コード
最初はこんな感じのコードでオプション引数を取得していました。
bool以外のオプション引数を使ったスクリプトを組んだことがあったのでその応用でいけるやろと思ってました。def get_args(): parser = argparse.ArgumentParser() parser.add_argument("--hogeFlag", type=bool, default=True) return parser.parse_args()結果
py test.py --hogeFlag False TrueFalseを指定したつもりがTrueになってますね・・・
原因
bool()
でbool値に変換されているのが原因だった。
type=bool
と指定しているとPythonは、設定された引数をbool()
に入れてbool型にしてくれるようです。
type=int
とかにするとint()
を通してint型にしてくれていたおかげで文字列が入るとエラーが起こっていたんですね。
確かにどの言語でも、コマンドライン引数も最初はstring型として取得されてましたね。今回のケースでは下記のコマンドで動かしていました。
$ py test.py --hogeFlag False
type=bool
なので、この場合はbool('False')
という処理を動かしてbool値に変換しようとします。
このbool('False')
はどういう結果になるのか。サクッとPythonWinで動かしてみます>>> print(bool('False')) TrueTrueになってるやん・・・
理由
pythonの真偽値でFalseと判定されるのは次のものらしいです(公式ドキュメントより抜粋)
偽であると定義されている定数: None と False
数値型におけるゼロ: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
空のシーケンスまたはコレクション: '', (), [], {}, set(), range(0)コマンドライン引数やオプション引数は、取得時にはstr型として扱われます。
なので3つ目の空のシーケンスまたはコレクションの部分に判定されます。
''
のような空文字だけがFalseと判定され、それ以外はTrueと判定されるようです。
bool('False')
がTrueと判定される理由がわかりました。対応
add_argument関数の引数には
type
の他にaction
というのもありました。
今回のケースではこちらを使用します。 参考コード
parser.add_argument("--hogeFlag", action='store_true')
action='store_true'
というのが新しいところですね。
動きとしてはこうなります$ py test.py --hogeFlag True $ py test.py False
--hogeFlag
を指定した時だけTrue
で、指定しない時はFalse
になりました!余談ですが真逆の
store_false
というのも設定することができます。動きはstore_true
と反対なだけなのでここでは省きます。まとめ
オプション引数でbool値を与えたいときは
type=bool
ではなくaction='store_true(またはfalse)'
で設定する!
- 投稿日:2019-10-02T17:02:54+09:00
Jinja2でincludeしたファイルに変数を渡す方法
はじめに
ちょっとした社内・部内向けのツールを作るのに、Python + bottle + jinja2 + SQLiteを使ってる。jinja2でincludeしたファイルに変数、パラメータを渡す方法がわからなかったのであれこれ調べたのでメモ。
やりかた
sample.py{% with 変数名 = 渡す変数名 %} {% include 'インクルードするファイル' %} {% endwith %}というように、{% with %} ~ {% endwith %} で囲んで、withのパラメータに渡したい変数を書く。
例
template.html{% with name = name %} {% include 'include.html' %} {% endwith %}include.html名前:{{ name }}複数の変数を渡す場合
template.html{% with name = name, address = address %} {% include 'include.html' %} {% endwith %}
- 投稿日:2019-10-02T16:57:18+09:00
「にじさんじ」で学ぶNeo4j
解説動画リンク
この記事を解説したものです。
解説動画を作成後リンクを貼ります。はじめに
研究でNeo4jを扱う必要が出てきました。
これは問題です。Neo4jの使い方を僕は知らないのです。
しかし、インターネットの記事は「Kevin」を「Chris」と繋いでどうこうしていて、これはやりたくありません。
そこで、自分の好きなコンテンツである、にじさんじの関係性をNeo4jを使って作りながら学んでいこうと思います。
卯月、愛してるぞ。
この記事で扱う内容
この記事で扱う内容は以下のとおりです。
- Neo4jのグラフ・エッジの概念
- 基本操作
- PythonDriver
- Pythonによるスクレイピング
- スクレイピング + Neo4jPythonDriverによるにじさんじネットワークの構築
- 以上の記事内容を解説・実践する動画(Youtubeへのリンク)
使用OS・バージョン
使用OSはMacです。
また、Neo4j Desktopのバージョンは「Neo4j Desktop v1.2.1」
Neo4j自体のバージョンは3.5.9を使用しています。GraphDB・Neo4jとは
GraphDB・Neo4jの特徴はグラフデータベースNeo4jハンズオン(インストールからプログラミングまで)で非常に分かりやすく解説されています。
Neo4jのインストール
Neo4jのインストールはNeo4j 入門 #1 (インストールおよび起動)のGUIのインストールを参考にしてください。
非常にわかりやすく画像もいっぱいで嬉しいです。グラフ・エッジの概念
Now Writing...
- 投稿日:2019-10-02T16:46:33+09:00
pymatgenで結晶構造情報フォーマットcifを扱う
Cifについて
Cif:Crystallographic Information Fileは
結晶学情報共通データ・フォーマット:結晶構造についての情報を詰め込んだ、国際結晶学連盟が推奨しているフォーマットです。
詳細はここ表面分析研究会 無料記事vol.21にかなり詳しい記載があります。例えば、NaCl(塩化ナトリウム)のCifはこんな感じです。
# generated using pymatgen data_NaCl _symmetry_space_group_name_H-M 'P 1' _cell_length_a 3.50218997 _cell_length_b 3.50218997 _cell_length_c 3.50218997 _cell_angle_alpha 90.00000000 _cell_angle_beta 90.00000000 _cell_angle_gamma 90.00000000 _symmetry_Int_Tables_number 1 _chemical_formula_structural NaCl _chemical_formula_sum 'Na1 Cl1' _cell_volume 42.95553177 _cell_formula_units_Z 1 loop_ _symmetry_equiv_pos_site_id _symmetry_equiv_pos_as_xyz 1 'x, y, z' loop_ _atom_site_type_symbol _atom_site_label _atom_site_symmetry_multiplicity _atom_site_fract_x _atom_site_fract_y _atom_site_fract_z _atom_site_occupancy Na Na0 1 0.000000 0.000000 0.000000 1 Cl Cl1 1 0.500000 0.500000 0.500000 1このままではただの文字列で分かりにくいので、pymatgenで読み込み数値データを利用しやすくします。
まずはMaterial Projectから上記の塩化ナトリウムのCifを取得します。
#material projectから塩化ナトリウムのデータを取得する import pandas as pd from pymatgen.ext.matproj import MPRester API_KEY = 'Your API' # Materials Project の API キー with MPRester(API_KEY) as m: # m.get_data(chemsys_formula_id)は Material Projectから指定化合物のデータを取得 #chemsys_formula_id は、元素の組み合わせ(e.g.,Li-Fe-O)、化合物名(e.g.,Fe2O3)、 # またはMaterial Projectの材料id (e.g., mp-1234)のどれかを入れる. NaCl_df = pd.DataFrame(m.get_data('NaCl',data_type='vasp'))NaClのデータは2個あるらしい。
NaCl_df['cif'] >>> 0 # generated using pymatgen\ndata_NaCl\n_symmet... 1 # generated using pymatgen\ndata_NaCl\n_symmet... Name: cif, dtype: object両方のNaClをpymatgenのStructureオブジェクトで取得します。
import pymatgen.io.cif as pycif from pymatgen.io.cif import CifParser #cifを取り出す NaCl_cif_1 = CifParser.from_string(NaCl_df['cif'][0]) NaCl_cif_2 = CifParser.from_string(NaCl_df['cif'][1]) #CifParser.get_structure メソッドは cifのデータを入力するとStructureの部分をリスト型に整形して返す NaCl_1 =CifParser.get_structures(NaCl_cif_1) NaCl_2 =CifParser.get_structures(NaCl_cif_2) NaCl_1,NaCl_2Cifの名データがだいぶ見やすい形で出力されました。
([Structure Summary Lattice abc : 4.02463512 4.02463511817503 4.024635115608324 angles : 59.99999991890345 59.99999987171031 60.000000015 volume : 46.09613775346724 A : -3.4854362538293855 0.0 -2.0123175618249705 B : -3.4854362538293855 0.0 2.0123175581750297 C : -2.323624174916067 3.286100806051642 -3.78335363038218e-09 PeriodicSite: Na (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000] PeriodicSite: Cl (-4.6472, 1.6431, -0.0000) [0.5000, 0.5000, 0.5000]],[Structure Summary Lattice abc : 3.50218997 3.50218997 3.50218997 angles : 90.0 90.0 90.0 volume : 42.95553176567333 A : 3.50218997 0.0 2.1444728683832323e-16 B : -2.1444728683832323e-16 3.50218997 2.1444728683832323e-16 C : 0.0 0.0 3.50218997 PeriodicSite: Na (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000] PeriodicSite: Cl (1.7511, 1.7511, 1.7511) [0.5000, 0.5000, 0.5000]])この2つを見比べると'angles'の値が60-60-60と90-90-90とあるので、結晶構造違いのようです。
塩化ナトリウムの結晶構造は立方格子(ccp)のものが普通なので、60-60-60のものは高圧下での構造ですかね?現状のNaCl_1はリスト型ですが、その中にはStructure型というpymatgenで広く使うオブジェクトが入っています。
type(NaCl_1) >>>list type(NaCl_1[0]) >>>pymatgen.core.structure.Structureたとえば、latticeメソッド(lattice:格子)を使うと,
格子に関するデータを表示できます。NaCl_1_Lattice = NaCl_1[0].lattice NaCl_1_Lattice >>> Lattice abc : 4.02463512 4.02463511817503 4.024635115608324 angles : 59.99999991890345 59.99999987171031 60.000000015 volume : 46.09613775346724 A : -3.4854362538293855 0.0 -2.0123175618249705 B : -3.4854362538293855 0.0 2.0123175581750297 C : -2.323624174916067 3.286100806051642 -3.78335363038218e-09さらにvolumeメソッドでは体積の値を取得できます。
NaCl_1_Lattice.volume >>> 46.09613775346724これで扱いにくいcifから数値データを取得できたので、機械学習に使う素材データとして扱えそうです。
- 投稿日:2019-10-02T16:00:59+09:00
Jupyter notebookでコードを折返したい。
Jupyter notebookでコードの折返しをしたかったが、日本語での解説記事が少なかったので、メモ程度に記しておきます。
Jupyterの設定フォルダ(私のWin環境では
C:\Users\(UserName)\.jupyter
)の下、フォルダnbconfoig
の中にあるnotebook.json
に以下を追記する。notebook.json{ "Cell": { "cm_config": { "lineWrapping": true } } }より良いコーディング環境を!
- 投稿日:2019-10-02T13:15:09+09:00
redis-pyからRedis Sentinelにアクセスする
TL;DR
redis-py
はRedis Sentinelに対応したモジュールがあるので、そちらを使ってRedis Sentinelにアクセスが可能- マスターがダウンした際に使っていた接続(コネクションプール)は、フェイルオーバーに追従してくれる
環境
今回の環境は、こちら。
$ cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) $ uname -a Linux localhost.localdomain 3.10.0-957.12.2.el7.x86_64 #1 SMP Tue May 14 21:24:32 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux $ redis-server -v Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b $ redis-sentinel -v Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b $ python -V Python 3.6.8 $ pip3 freeze redis==3.3.8CentOS 7、Redis 5.0.6、Python 3.6.8、redis-py 3.3.8です。
各サーバーは、以下のように用意します。
- マスター …
192.168.33.10
- レプリカ …
192.168.33.11
- Sentinel 1〜3 …
192.168.33.12
〜194.168.33.14
- クライアント …
192.168.33.15
Redis Sentinelの構築
まずは、Redis Sentinalを構築しましょう。
EPELから、Redisをインストールします。
$ sudo yum install epel-release $ sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm $ sudo yum --enablerepo=remi install redisまずは、Redisのレプリケーションを構成します。
Redisのマスターは、
/etc/redis.conf
のデフォルト設定から以下のように変更。
※デフォルト設定の全体は、最後に記載しますbind 0.0.0.0
レプリカ側は、
/etc/redis.conf
のデフォルト設定から以下のように変更。bind 0.0.0.0 replicaof 192.168.33.10 6379
Redisのマスター、レプリカをそれぞれ起動して
$ sudo systemctl start redisレプリケーションが動作していることを確認。
マスター側。
$ redis-cli -h 192.168.33.10 192.168.33.10:6379> info replication # Replication role:master connected_slaves:1 slave0:ip=192.168.33.11,port=6379,state=online,offset=28,lag=0 master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:28 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:28レプリカ側。
$ redis-cli -h 192.168.33.11 192.168.33.11:6379> info replication # Replication role:slave master_host:192.168.33.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:6 master_sync_in_progress:0 slave_repl_offset:56 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:56 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:56続いて、Redis Sentinelを構成します。
/etc/redis-sentinel.conf
のデフォルト設定から、以下のように変更します。
※デフォルト設定の全体は、最後に記載しますsentinel monitor mymaster 192.168.33.10 6379 2Redis Sentinel起動(3台で行います)。
$ sudo systemctl start redis-sentinel確認。
$ redis-cli -h 192.168.33.12 -p 26379 192.168.33.12:26379> info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.33.10:6379,slaves=1,sentinels=3マスター、レプリカがそれぞれひとつ、Sentinelが3つある状態ですね。
redis-py
で、Redis Sentinelにアクセスするそれでは、今度はRedis Sentinelに
redis-py
からアクセスしてみます。まずはインストール。$ pip3 install redis今回の
redis-py
のバージョンです。$ pip3 freeze redis==3.3.8
redis-py
からRedis Sentinelへのアクセスですが、以下を参考にコマンドラインプログラムを書いてみました。
sentinel_client.py
import re from redis.exceptions import ReadOnlyError from redis.sentinel import Sentinel import sys try: print('start sentinel client') sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)] sentinel = Sentinel(sentinels, decode_responses = True) redis = None while True: if redis != None: print('{}: > '.format(redis), end = '', flush = True) else: print('> ', end = '', flush = True) command = sys.stdin.readline().strip() if command == 'info master': print(sentinel.discover_master('mymaster')) elif command == 'info replicas': print(sentinel.discover_slaves('mymaster')) elif command == 'use master': redis = sentinel.master_for('mymaster') elif command == 'use replica': redis = sentinel.slave_for('mymaster') elif command == 'info': print(str(redis.info())) elif command.startswith('set '): tokens = re.split('\s+', command) key = tokens[1] value = tokens[2] try: redis.set(key, value) print('set {} = {}'.format(key, value)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command.startswith('get '): tokens = re.split('\s+', command) key = tokens[1] print('get[{}] = {}'.format(key, redis.get(key))) elif command.startswith('del '): tokens = re.split('\s+', command) key = tokens[1] try: redis.delete(key) print('del, {}'.format(key)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command == 'exit': print('bye bye!!') break elif not command: pass else: print('unknown command = {}'.format(command)) except KeyboardInterrupt: print('bye bye!!')Sentinelへのアクセスは、各Sentinelプロセスへのアクセス先を使って、Sentinelインスタンスを作成することで行います。
sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)] sentinel = Sentinel(sentinels, decode_responses = True)マスターおよびレプリカへのアクセスは、
Sentinel#master_for
またはSentinel#slave_for
で行います。redis = sentinel.master_for('mymaster') redis = sentinel.slave_for('mymaster')各メソッドの戻り値は、
SentinelConnectionPool
のインスタンスです。あとは、
SentinelConnectionPool
に対してRedisコマンドを実行すればOKです。elif command.startswith('set '): tokens = re.split('\s+', command) key = tokens[1] value = tokens[2] try: redis.set(key, value) print('set {} = {}'.format(key, value)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command.startswith('get '): tokens = re.split('\s+', command) key = tokens[1] print('get[{}] = {}'.format(key, redis.get(key))) elif command.startswith('del '): tokens = re.split('\s+', command) key = tokens[1] try: redis.delete(key) print('del, {}'.format(key)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e))あとは、情報取得系のコマンドを…。
if command == 'info master': print(sentinel.discover_master('mymaster')) elif command == 'info replicas': print(sentinel.discover_slaves('mymaster')) elif command == 'info': print(str(redis.info()))
discover〜
はSentinelにおけるマスター、レプリカの情報で、info
は接続しているRedisの情報です。では、実行してみます。
$ python3 sentinel_client.py start sentinel client >マスター、レプリカの情報。
> info master ('192.168.33.10', 6379) > info replicas [('192.168.33.11', 6379)]マスターに接続。
> use master
set、get。
Redis<SentinelConnectionPool<service=mymaster(master)>: > set key1 value1 set key1 = value1 Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1 get[key1] = value1レプリカにつなぎなおして、データ取得。
Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1 get[key1] = value1レプリカ側では、データの更新は不可です。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2 ReadOnlyError: You can't write against a read only replica.ここで、再度マスターに接続。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > use masterマスターのRedisを停止してみます。
$ sudo systemctl stop redis
redis-cli
で、マスターが切り替わったことを確認します。$ redis-cli -h 192.168.33.12 -p 26379 192.168.33.12:26379> info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.33.11:6379,slaves=1,sentinels=3この状態で、先程のマスターにつないだコマンドからデータを取得してみます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1 get[key1] = value1なんか、動きました…。
このRedisの情報を見てみます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > info {'redis_version': '5.0.6', 'redis_git_sha1': 0, 'redis_git_dirty': 0, 'redis_build_id': 'c3d7ebb6b1a2844b', 'redis_mode': 'standalone', 'os': 'Linux 3.10.0-957.12.2.el7.x86_64 x86_64', 'arch_bits': 64, 'multiplexing_api': 'epoll', 'atomicvar_api': 'atomic-builtin', 'gcc_version': '4.8.5', 'process_id': 24544, 'run_id': 'ad1e763f073c8fcce092a773f2bbd2c5933d2bf9', 'tcp_port': 6379, 'uptime_in_seconds': 440050, 'uptime_in_days': 5, 'hz': 10, 'configured_hz': 10, 'lru_clock': 9707634, 'executable': '/usr/bin/redis-server', 'config_file': '/etc/redis.conf', 'connected_clients': 7, 'client_recent_max_input_buffer': 2, 'client_recent_max_output_buffer': 0, 'blocked_clients': 0, 'used_memory': 2070088, 'used_memory_human': '1.97M', 'used_memory_rss': 4554752, 'used_memory_rss_human': '4.34M', 'used_memory_peak': 2173256, 'used_memory_peak_human': '2.07M', 'used_memory_peak_perc': '95.25%', 'used_memory_overhead': 2024062, 'used_memory_startup': 791416, 'used_memory_dataset': 46026, 'used_memory_dataset_perc': '3.60%', 'allocator_allocated': 2637720, 'allocator_active': 3035136, 'allocator_resident': 7573504, 'total_system_memory': 510861312, 'total_system_memory_human': '487.20M', 'used_memory_lua': 37888, 'used_memory_lua_human': '37.00K', 'used_memory_scripts': 0, 'used_memory_scripts_human': '0B', 'number_of_cached_scripts': 0, 'maxmemory': 0, 'maxmemory_human': '0B', 'maxmemory_policy': 'noeviction', 'allocator_frag_ratio': 1.15, 'allocator_frag_bytes': 397416, 'allocator_rss_ratio': 2.5, 'allocator_rss_bytes': 4538368, 'rss_overhead_ratio': 0.6, 'rss_overhead_bytes': -3018752, 'mem_fragmentation_ratio': 2.25, 'mem_fragmentation_bytes': 2526672, 'mem_not_counted_for_evict': 0, 'mem_replication_backlog': 1048576, 'mem_clients_slaves': 0, 'mem_clients_normal': 183998, 'mem_aof_buffer': 0, 'mem_allocator': 'jemalloc-5.1.0', 'active_defrag_running': 0, 'lazyfree_pending_objects': 0, 'loading': 0, 'rdb_changes_since_last_save': 0, 'rdb_bgsave_in_progress': 0, 'rdb_last_save_time': 1569988537, 'rdb_last_bgsave_status': 'ok', 'rdb_last_bgsave_time_sec': 0, 'rdb_current_bgsave_time_sec': -1, 'rdb_last_cow_size': 221184, 'aof_enabled': 0, 'aof_rewrite_in_progress': 0, 'aof_rewrite_scheduled': 0, 'aof_last_rewrite_time_sec': -1, 'aof_current_rewrite_time_sec': -1, 'aof_last_bgrewrite_status': 'ok', 'aof_last_write_status': 'ok', 'aof_last_cow_size': 0, 'total_connections_received': 26, 'total_commands_processed': 2737329, 'instantaneous_ops_per_sec': 3, 'total_net_input_bytes': 202595677, 'total_net_output_bytes': 1041974292, 'instantaneous_input_kbps': 0.2, 'instantaneous_output_kbps': 0.54, 'rejected_connections': 0, 'sync_full': 0, 'sync_partial_ok': 0, 'sync_partial_err': 0, 'expired_keys': 0, 'expired_stale_perc': 0.0, 'expired_time_cap_reached_count': 0, 'evicted_keys': 0, 'keyspace_hits': 3, 'keyspace_misses': 2, 'pubsub_channels': 1, 'pubsub_patterns': 0, 'latest_fork_usec': 277, 'migrate_cached_sockets': 0, 'slave_expires_tracked_keys': 0, 'active_defrag_hits': 0, 'active_defrag_misses': 0, 'active_defrag_key_hits': 0, 'active_defrag_key_misses': 0, 'role': 'master', 'connected_slaves': 0, 'master_replid': '56d4bc022b7a089800bfe723201c821f570f2fe4', 'master_replid2': '54810ab6453ffdf12f21ff34157e2e0d655f8a81', 'master_repl_offset': 91797500, 'second_repl_offset': 91786762, 'repl_backlog_active': 1, 'repl_backlog_size': 1048576, 'repl_backlog_first_byte_offset': 90748925, 'repl_backlog_histlen': 1048576, 'used_cpu_sys': 1280.370212, 'used_cpu_user': 41.081586, 'used_cpu_sys_children': 0.004907, 'used_cpu_user_children': 0.0, 'cluster_enabled': 0, 'db0': {'keys': 1, 'expires': 0, 'avg_ttl': 0}}レプリカがいないことになっていますね。
'connected_slaves': 0ということは、接続しているのはフェイルオーバーしたレプリカのようです。
この状態で、レプリカに接続してデータを取得してみると、なんと動きます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1 get[key1] = value1
discover〜
で見てみると、こういう状態です。Redis<SentinelConnectionPool<service=mymaster(slave)>: > info master ('192.168.33.11', 6379) Redis<SentinelConnectionPool<service=mymaster(slave)>: > info replicas []となると、この場合はレプリカにつないでも、つなぐべきレプリカがいないのでマスターに接続することになるわけですね。
実際、更新も可能です。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2 set key2 = value2というわけで、マスターを使っている時に接続先がダウンした場合、フェイルオーバーしたマスターに昇格した旧レプリカにつないでくれるということですね(Sentinelが検知するまでの間の挙動にについては、今回は見ていませんが)。
とりあえず、挙動はなんとなくわかった感じです。
付録:EPELからインストールした、RedisおよびRedis Sentinelのデフォルト値
EPELからインストールした、
/etc/redis.conf
のデフォルト値。$ sudo grep -v '#' /etc/redis.conf | grep -v '^$' bind 127.0.0.1 protected-mode yes port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_6379.pid loglevel notice logfile /var/log/redis/redis.log databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /var/lib/redis replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no replica-priority 100 lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no appendonly no appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yesEPELからインストールした、
/etc/redis-sentinel.conf
のデフォルト設定。$ sudo grep -v '#' /etc/redis-sentinel.conf | grep -v '^$' port 26379 daemonize no pidfile "/var/run/redis-sentinel.pid" logfile "/var/log/redis/sentinel.log" dir "/tmp" sentinel myid 88cdee281108c92337965c782f60653f4f5d00fe sentinel deny-scripts-reconfig yes sentinel monitor mymaster 127.0.0.1 6379 2 sentinel config-epoch mymaster 0 sentinel leader-epoch mymaster 0 protected-mode no supervised systemd sentinel known-replica mymaster 192.168.33.11 6379 sentinel known-sentinel mymaster 192.168.33.14 26379 4cdd5e38eccc965513db0d8be46b9eb6614418da sentinel known-sentinel mymaster 192.168.33.13 26379 22249d49a0b530709b7897cbbb88a2db6da6a1b1 sentinel current-epoch 0
- 投稿日:2019-10-02T13:15:09+09:00
redis-pyからRedis Sentinelにアクセスしてみる
TL;DR
redis-py
はRedis Sentinelに対応したモジュールがあるので、そちらを使ってRedis Sentinelにアクセスが可能- Redis Sentinel経由で取得したRedisへの接続はコネクションプールであり、通常のRedisへの操作と同様に利用可能
- マスターがダウンした際に使っていた接続(コネクションプール)は、フェイルオーバーに追従してくれる
環境
今回の環境は、こちら。
$ cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) $ uname -a Linux localhost.localdomain 3.10.0-957.12.2.el7.x86_64 #1 SMP Tue May 14 21:24:32 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux $ redis-server -v Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b $ redis-sentinel -v Redis server v=5.0.6 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=c3d7ebb6b1a2844b $ python -V Python 3.6.8 $ pip3 freeze redis==3.3.8CentOS 7、Redis 5.0.6、Python 3.6.8、redis-py 3.3.8です。
各サーバーは、以下のように用意します。
- マスター …
192.168.33.10
- レプリカ …
192.168.33.11
- Sentinel 1〜3 …
192.168.33.12
〜194.168.33.14
- クライアント …
192.168.33.15
Redis Sentinelの構築
まずは、Redis Sentinalを構築しましょう。
EPELから、Redisをインストールします。
$ sudo yum install epel-release $ sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm $ sudo yum --enablerepo=remi install redisまずは、Redisのレプリケーションを構成します。
Redisのマスターは、
/etc/redis.conf
のデフォルト設定から以下のように変更。
※デフォルト設定の全体は、最後に記載しますbind 0.0.0.0
レプリカ側は、
/etc/redis.conf
のデフォルト設定から以下のように変更。bind 0.0.0.0 replicaof 192.168.33.10 6379
Redisのマスター、レプリカをそれぞれ起動して
$ sudo systemctl start redisレプリケーションが動作していることを確認。
マスター側。
$ redis-cli -h 192.168.33.10 192.168.33.10:6379> info replication # Replication role:master connected_slaves:1 slave0:ip=192.168.33.11,port=6379,state=online,offset=28,lag=0 master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:28 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:28レプリカ側。
$ redis-cli -h 192.168.33.11 192.168.33.11:6379> info replication # Replication role:slave master_host:192.168.33.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:6 master_sync_in_progress:0 slave_repl_offset:56 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:54810ab6453ffdf12f21ff34157e2e0d655f8a81 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:56 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:56続いて、Redis Sentinelを構成します。
/etc/redis-sentinel.conf
のデフォルト設定から、以下のように変更します。
※デフォルト設定の全体は、最後に記載しますsentinel monitor mymaster 192.168.33.10 6379 2Redis Sentinel起動(3台で行います)。
$ sudo systemctl start redis-sentinel確認。
$ redis-cli -h 192.168.33.12 -p 26379 192.168.33.12:26379> info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.33.10:6379,slaves=1,sentinels=3マスター、レプリカがそれぞれひとつ、Sentinelが3つある状態ですね。
redis-py
で、Redis Sentinelにアクセスするそれでは、今度はRedis Sentinelに
redis-py
からアクセスしてみます。まずはインストール。$ pip3 install redis今回の
redis-py
のバージョンです。$ pip3 freeze redis==3.3.8
redis-py
からRedis Sentinelへのアクセスですが、以下を参考にコマンドラインプログラムを書いてみました。
sentinel_client.py
import re from redis.exceptions import ReadOnlyError from redis.sentinel import Sentinel import sys try: print('start sentinel client') sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)] sentinel = Sentinel(sentinels, decode_responses = True) redis = None while True: if redis != None: print('{}: > '.format(redis), end = '', flush = True) else: print('> ', end = '', flush = True) command = sys.stdin.readline().strip() if command == 'info master': print(sentinel.discover_master('mymaster')) elif command == 'info replicas': print(sentinel.discover_slaves('mymaster')) elif command == 'use master': redis = sentinel.master_for('mymaster') elif command == 'use replica': redis = sentinel.slave_for('mymaster') elif command == 'info': print(str(redis.info())) elif command.startswith('set '): tokens = re.split('\s+', command) key = tokens[1] value = tokens[2] try: redis.set(key, value) print('set {} = {}'.format(key, value)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command.startswith('get '): tokens = re.split('\s+', command) key = tokens[1] print('get[{}] = {}'.format(key, redis.get(key))) elif command.startswith('del '): tokens = re.split('\s+', command) key = tokens[1] try: redis.delete(key) print('del, {}'.format(key)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command == 'exit': print('bye bye!!') break elif not command: pass else: print('unknown command = {}'.format(command)) except KeyboardInterrupt: print('bye bye!!')Sentinelへのアクセスは、各Sentinelプロセスへのアクセス先を使って、Sentinelインスタンスを作成することで行います。
sentinels = [('192.168.33.12', 26379), ('192.168.33.13', 26379), ('192.168.33.14', 26379)] sentinel = Sentinel(sentinels, decode_responses = True)マスターおよびレプリカへのアクセスは、
Sentinel#master_for
またはSentinel#slave_for
で行います。redis = sentinel.master_for('mymaster') redis = sentinel.slave_for('mymaster')各メソッドの戻り値は、
SentinelConnectionPool
のインスタンスです。あとは、
SentinelConnectionPool
に対してRedisコマンドを実行すればOKです。elif command.startswith('set '): tokens = re.split('\s+', command) key = tokens[1] value = tokens[2] try: redis.set(key, value) print('set {} = {}'.format(key, value)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e)) elif command.startswith('get '): tokens = re.split('\s+', command) key = tokens[1] print('get[{}] = {}'.format(key, redis.get(key))) elif command.startswith('del '): tokens = re.split('\s+', command) key = tokens[1] try: redis.delete(key) print('del, {}'.format(key)) except ReadOnlyError as e: print('{}: {}'.format(e.__class__.__name__, e))あとは、情報取得系のコマンドを…。
if command == 'info master': print(sentinel.discover_master('mymaster')) elif command == 'info replicas': print(sentinel.discover_slaves('mymaster')) elif command == 'info': print(str(redis.info()))
discover〜
はSentinelにおけるマスター、レプリカの情報で、info
は接続しているRedisの情報です。では、実行してみます。
$ python3 sentinel_client.py start sentinel client >マスター、レプリカの情報。
> info master ('192.168.33.10', 6379) > info replicas [('192.168.33.11', 6379)]マスターに接続。
> use master
set、get。
Redis<SentinelConnectionPool<service=mymaster(master)>: > set key1 value1 set key1 = value1 Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1 get[key1] = value1レプリカにつなぎなおして、データ取得。
Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1 get[key1] = value1レプリカ側では、データの更新は不可です。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2 ReadOnlyError: You can't write against a read only replica.ここで、再度マスターに接続。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > use masterマスターのRedisを停止してみます。
$ sudo systemctl stop redis
redis-cli
で、マスターが切り替わったことを確認します。$ redis-cli -h 192.168.33.12 -p 26379 192.168.33.12:26379> info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.33.11:6379,slaves=1,sentinels=3この状態で、先程のマスターにつないだコマンドからデータを取得してみます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > get key1 get[key1] = value1なんか、動きました…。
このRedisの情報を見てみます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > info {'redis_version': '5.0.6', 'redis_git_sha1': 0, 'redis_git_dirty': 0, 'redis_build_id': 'c3d7ebb6b1a2844b', 'redis_mode': 'standalone', 'os': 'Linux 3.10.0-957.12.2.el7.x86_64 x86_64', 'arch_bits': 64, 'multiplexing_api': 'epoll', 'atomicvar_api': 'atomic-builtin', 'gcc_version': '4.8.5', 'process_id': 24544, 'run_id': 'ad1e763f073c8fcce092a773f2bbd2c5933d2bf9', 'tcp_port': 6379, 'uptime_in_seconds': 440050, 'uptime_in_days': 5, 'hz': 10, 'configured_hz': 10, 'lru_clock': 9707634, 'executable': '/usr/bin/redis-server', 'config_file': '/etc/redis.conf', 'connected_clients': 7, 'client_recent_max_input_buffer': 2, 'client_recent_max_output_buffer': 0, 'blocked_clients': 0, 'used_memory': 2070088, 'used_memory_human': '1.97M', 'used_memory_rss': 4554752, 'used_memory_rss_human': '4.34M', 'used_memory_peak': 2173256, 'used_memory_peak_human': '2.07M', 'used_memory_peak_perc': '95.25%', 'used_memory_overhead': 2024062, 'used_memory_startup': 791416, 'used_memory_dataset': 46026, 'used_memory_dataset_perc': '3.60%', 'allocator_allocated': 2637720, 'allocator_active': 3035136, 'allocator_resident': 7573504, 'total_system_memory': 510861312, 'total_system_memory_human': '487.20M', 'used_memory_lua': 37888, 'used_memory_lua_human': '37.00K', 'used_memory_scripts': 0, 'used_memory_scripts_human': '0B', 'number_of_cached_scripts': 0, 'maxmemory': 0, 'maxmemory_human': '0B', 'maxmemory_policy': 'noeviction', 'allocator_frag_ratio': 1.15, 'allocator_frag_bytes': 397416, 'allocator_rss_ratio': 2.5, 'allocator_rss_bytes': 4538368, 'rss_overhead_ratio': 0.6, 'rss_overhead_bytes': -3018752, 'mem_fragmentation_ratio': 2.25, 'mem_fragmentation_bytes': 2526672, 'mem_not_counted_for_evict': 0, 'mem_replication_backlog': 1048576, 'mem_clients_slaves': 0, 'mem_clients_normal': 183998, 'mem_aof_buffer': 0, 'mem_allocator': 'jemalloc-5.1.0', 'active_defrag_running': 0, 'lazyfree_pending_objects': 0, 'loading': 0, 'rdb_changes_since_last_save': 0, 'rdb_bgsave_in_progress': 0, 'rdb_last_save_time': 1569988537, 'rdb_last_bgsave_status': 'ok', 'rdb_last_bgsave_time_sec': 0, 'rdb_current_bgsave_time_sec': -1, 'rdb_last_cow_size': 221184, 'aof_enabled': 0, 'aof_rewrite_in_progress': 0, 'aof_rewrite_scheduled': 0, 'aof_last_rewrite_time_sec': -1, 'aof_current_rewrite_time_sec': -1, 'aof_last_bgrewrite_status': 'ok', 'aof_last_write_status': 'ok', 'aof_last_cow_size': 0, 'total_connections_received': 26, 'total_commands_processed': 2737329, 'instantaneous_ops_per_sec': 3, 'total_net_input_bytes': 202595677, 'total_net_output_bytes': 1041974292, 'instantaneous_input_kbps': 0.2, 'instantaneous_output_kbps': 0.54, 'rejected_connections': 0, 'sync_full': 0, 'sync_partial_ok': 0, 'sync_partial_err': 0, 'expired_keys': 0, 'expired_stale_perc': 0.0, 'expired_time_cap_reached_count': 0, 'evicted_keys': 0, 'keyspace_hits': 3, 'keyspace_misses': 2, 'pubsub_channels': 1, 'pubsub_patterns': 0, 'latest_fork_usec': 277, 'migrate_cached_sockets': 0, 'slave_expires_tracked_keys': 0, 'active_defrag_hits': 0, 'active_defrag_misses': 0, 'active_defrag_key_hits': 0, 'active_defrag_key_misses': 0, 'role': 'master', 'connected_slaves': 0, 'master_replid': '56d4bc022b7a089800bfe723201c821f570f2fe4', 'master_replid2': '54810ab6453ffdf12f21ff34157e2e0d655f8a81', 'master_repl_offset': 91797500, 'second_repl_offset': 91786762, 'repl_backlog_active': 1, 'repl_backlog_size': 1048576, 'repl_backlog_first_byte_offset': 90748925, 'repl_backlog_histlen': 1048576, 'used_cpu_sys': 1280.370212, 'used_cpu_user': 41.081586, 'used_cpu_sys_children': 0.004907, 'used_cpu_user_children': 0.0, 'cluster_enabled': 0, 'db0': {'keys': 1, 'expires': 0, 'avg_ttl': 0}}レプリカがいないことになっていますね。
'connected_slaves': 0ということは、接続しているのはフェイルオーバーしたレプリカのようです。
この状態で、レプリカに接続してデータを取得してみると、なんと動きます。
Redis<SentinelConnectionPool<service=mymaster(master)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > get key1 get[key1] = value1
discover〜
で見てみると、こういう状態です。Redis<SentinelConnectionPool<service=mymaster(slave)>: > info master ('192.168.33.11', 6379) Redis<SentinelConnectionPool<service=mymaster(slave)>: > info replicas []となると、この場合はレプリカにつないでも、つなぐべきレプリカがいないのでマスターに接続することになるわけですね。
実際、更新も可能です。
Redis<SentinelConnectionPool<service=mymaster(slave)>: > use replica Redis<SentinelConnectionPool<service=mymaster(slave)>: > set key2 value2 set key2 = value2というわけで、マスターを使っている時に接続先がダウンした場合、フェイルオーバーしたマスターに昇格した旧レプリカにつないでくれるということですね(Sentinelが検知するまでの間の挙動にについては、今回は見ていませんが)。
とりあえず、挙動はなんとなくわかった感じです。
付録:EPELからインストールした、RedisおよびRedis Sentinelのデフォルト値
EPELからインストールした、
/etc/redis.conf
のデフォルト値。$ sudo grep -v '#' /etc/redis.conf | grep -v '^$' bind 127.0.0.1 protected-mode yes port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_6379.pid loglevel notice logfile /var/log/redis/redis.log databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /var/lib/redis replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no replica-priority 100 lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no appendonly no appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yesEPELからインストールした、
/etc/redis-sentinel.conf
のデフォルト設定。$ sudo grep -v '#' /etc/redis-sentinel.conf | grep -v '^$' port 26379 daemonize no pidfile "/var/run/redis-sentinel.pid" logfile "/var/log/redis/sentinel.log" dir "/tmp" sentinel myid 88cdee281108c92337965c782f60653f4f5d00fe sentinel deny-scripts-reconfig yes sentinel monitor mymaster 127.0.0.1 6379 2 sentinel config-epoch mymaster 0 sentinel leader-epoch mymaster 0 protected-mode no supervised systemd sentinel known-replica mymaster 192.168.33.11 6379 sentinel known-sentinel mymaster 192.168.33.14 26379 4cdd5e38eccc965513db0d8be46b9eb6614418da sentinel known-sentinel mymaster 192.168.33.13 26379 22249d49a0b530709b7897cbbb88a2db6da6a1b1 sentinel current-epoch 0
- 投稿日:2019-10-02T08:36:13+09:00
PythonでStateモナドを書いてみる
Stateモナド
- ”状態”を受け取り、状態に対する計算結果と、計算後の”状態”を返す(状態付き計算とよぶ)ような関数を、モナドとして扱いたいときに用いるモナド
- 状態付き計算処理を簡潔に記述できる(関数合成できる)
参考
実装するまえに
状態付き計算とは
s
を状態、s
に対する何らかの計算結果をa
、計算後の状態をs'
としたとき、
以下の形で入出力が表現できる計算のこと。s -> (a, s')状態とは変数の値とかリストの中身とか、一般的な意味合いでの状態と思えばいい。
なぜ状態付き計算を使うのか
モナドが多く利用される純粋関数型プログラミング言語のHaskellのコードを例にとる。
以下は、スタック処理の例。stackExample :: State [Int] Int stackExample = do push 1 push 2 push 3 pop push 4 x <- pop return x > runState stackExample [] > (4,[2,1])(筆者はHaskellには触れたことがないので、コードそのものには特に言及しない。)
なにをやっているかに注目する。
上記プログラムは、1~4のデータを空の箱にに入れたり取り出したりし、最終的に 4 を取り出して 2 と 1 が残っている状態を表す、いわゆるスタック処理の一例である。
ここでは、変数への再代入なしに状態を更新しているようにふるまっている点が重要で、
このような処理を、Pythonで普通に実装しようとすると、例えば以下のように、
スタックの状態を保持するリストに対し、元々備わっている状態更新の処理を用いて、リストの中身を更新していく書き方になると思う。def stackExample(state): state.insert(0,1) state.insert(0,2) state.insert(0,3) state.pop(0) state.insert(0,4) ret = (state.pop(0), state) return ret stackExample([])(4, [2, 1])
このままでも簡潔に記述できている。
しかしここで問題なのは、state
の変数がいまどのような状態なのか、どんな状態になりうるのかがわからないというところにある。
これを他の外部処理でも更新したり、また別のところでは参照したりしようとすると、
各処理において、state
になにが入っているのか、十分注意して実装することになる。
しかしそこに想定外の状態が入り込むとプログラムはバグってしまうわけだ。
もちろん、実際には変数のチェック処理を実装することで想定外の挙動を起こさないよう努力する。
しかしながら未知の値を持ちうる変数はできるかぎりプログラムから排除したい。
とはいえ状態を更新するような処理をきちんと記述できなければ書きたいプログラムを書けなくなる。
そこで登場するのが状態付き関数であり、Stateモナドである。と思う。実装
pushとpop
まずは、リストを受け取り処理結果と処理後のリストを返すという形式で、
pushとpopを実装してみる。
入力したリストの中身を更新しないことに注意する。push
def push(a): return lambda x: (None, [a] + x) x = [1,2,3] y = push(4)(x) print(x) print(y)[1, 2, 3] ←push後でも中身は変わってない
x
(None, [4, 1, 2, 3]) ←y
pop
def pop(): return lambda x: (x[0], x[1:]) x = [1,2,3] y = pop()(x) print(x) print(y)[1, 2, 3] ← pop後も中身が変わってない
x
(1, [2, 3]) ←y
それぞれの処理はラムダ式を返すことで関数をバインドできるようにし、
モナドに拡張できるようにしている。def _bind(func): return lambda x: func(x) _bind(push(4))([1,2,3])(None, [4, 1, 2, 3])
_bind(pop())([1,2,3])(1, [2, 3])
stateモナドへの拡張
pushとpopの処理はつくった。
ここで、スタックの例に戻ってみる。stackExample :: State [Int] Int stackExample = do push 1 push 2 push 3 pop push 4 x <- pop return x > runState stackExample [] > (4,[2,1])さて、自前でつくった push・pop を用いて、上記のようにスタック処理を記述できるだろうか?
このままではできない。
push・pop において入力と出力の形式が異なるからだ。
- 入力
- [状態]
- 出力
- (計算結果, [状態])
push pop を関数合成できるようにするためには、
両者の入力も出力も同じように抽象的に扱う仕組みが必要である。
- 入力
- State ( , [状態])
- 出力
- State (計算結果, [状態])
そこでいよいよStateモナドだ。
モナドクラス(仮)
class Monad(): def __init__(self, a): raise NotImplementedError def _bind(self, func): raise NotImplementedError def __or__ (self, func): return self._bind(func) @staticmethod def call_state(s): return State(lambda : (s, s))Stateモナド
class State(Monad): def __init__(self, a): self.run_state = a def _bind(self, func): _, s = self.run_state() return State(lambda :func(s)) def __repr__(self): return 'State (%r, %r)' % self.run_state()動かしてみる
Monad.call_state([1, 2, 3, 4])State ([1, 2, 3, 4], [1, 2, 3, 4])
Monad.call_state([1, 2, 3, 4]) | pop()State (1, [2, 3, 4])
Monad.call_state([1, 2, 3, 4]) | push(5)State (None, [5, 1, 2, 3, 4])
Monad.call_state([1, 2, 3, 4]) | pop() | push(5)State (None, [5, 2, 3, 4])
スタックの例
def stackExample(state): return state | push(1) \ | push(2) \ | push(3) \ | pop() \ | push(4) \ | pop() state = Monad.call_state([]) ret = stackExample(state) print(state) print(ret)State ([], []) ←
state
State (4, [2, 1]) ←ret
push と pop の処理を合成して元々の変数の値を変化させることなく、
状態更新を扱うことができた。おわりに
以上のようにStateモナドを使うと
- 状態付き計算の関数合成ができる
ようだ。
個人的にはこの例を通じてStateモナドを書いてみて、
モナドを理解するうえでは、"閉じている"っていうのが重要なんだと思った。
モノイドとの関連もそれでより合点がいった。
- 投稿日:2019-10-02T08:29:10+09:00
PythonからExcelをwin32comで操作する
はじめに
Excel ファイルを自動で処理したい。そのための言語としていくつか手段があることが分かった。
どれを選ぶべきか。その中で結果として選んだ Python + win32com の操作例を書き残しておく。なぜ Python + win32com で Excel を操作するのか
Excel VBA, PowerShell + COM, Python + xlwings, Python + openpyxl, Python + win32com を比較した結果、自分のニーズに近いのは win32com でした。
まず、Excelを自動で操作したいと考えたとき、初めに思いつくのはExcel VBAでした。ただ、これは向かない用途があるようでした。
Excelファイルの所有者にVBA入れて管理するにはすべてのユーザにマクロ付きのExcelファイルの利用を強制することになるのでOSからExcelのバージョンまで細かな動きをVBA側で考慮する必要があります。VBAを強制することによるセキュリティ上のリスク等も考え始めるとあまり選択肢としてよくない。
次に、データが詰まってるExcelファイルとVBAを管理するExcelファイルを分離することを考えたとしても、VBAのコード管理がめんどい。Gitなりで非テキストファイルを管理するのは簡単でなく、がんばってモジュールを節目でエクスポートして管理、というのは面倒そう。継続的にメンテする費用がなければ避けたい選択肢だった。
Excel VBA の次に Excel の自動化で思いついたのは、 PowerShell + COM でした。が、PowerShell って癖があって苦手意識がある。エスケープがバックスラッシュなの忘れるし、パス名にワイルドカード文字列が入るとエスケープ処理に翻弄される程度の、低レベル PowerShell コーダーには敷居が高すぎる。100行程度ならPowerShellで頑張る。それ以上はお勧めできない。
次に他の言語はさておき、 Python + win32com か、xlwings, openpyxl で悩んだ。
openpyxl は、xlsx を読めるが計算式の計算手段を持って無い気がするのに計算結果を得られそうなメソッドがあって不安がある。おそらくキャッシュしたデータを読めるのだと思うけれど値に不整合があるのではないか、仮に無かったとして書き込み処理を行った後に読んではいけないデータなのではと不安を感じる。誤差が許される読み取り処理や、読み取りせずExcelを出力するならopenpyxlは使い勝手よさそう。
xlwings は、素朴なExcelファイルなら採用したいくらい良くできてる。
dir(workbook)
でメソッド一覧が出る、メソッド名が原則小文字で統一されている、など使い勝手が良い。ただし、すべてのExcelの機能に対応してないように見える。例えば、ざっと調べた限りでは保護されたExcelシートを解除したい場合は、VBAのコードをxlwings経由で実行する等のワークアラウンドが必要でデータ処理には向くが、かゆいところに手が届かない印象。また、Execel を導入済が前提となる。動作確認してないが macOS で動かしている事例があった。残ったwin32comは上記の課題をすべてクリアできそう。ただし、制約として、Windows+Excel導入済みが前提になる。デスクトップでExcelを実行中にwin32com経由でExcelを実行すると挙動が変わる部分があるなどテスト面で環境の影響を受けやすい。
dir(workbook)
でメソッド名の一覧が出なかったり、メソッド名の大文字小文字を誤ると期待通りに動かないといった動作もある。ドキュメントはあまりないので、Python書く際はVBAの知見なり、MS公式APIドキュメントなり、Stackoverflowなりを参照することになる。いずれも一長一短ですね。Excelを最も細かく使えるのがwin32comで一択。開発者にやさしく今後期待できるのがxlwings。全部で1~2画面程度の短いコードならPowerShellでもまあいいか。Excelファイルを自分だけで使うなら版管理なしのVBAでもいいか、という印象です。
前提
- Windows 10
- Python3
- Excel 2013
ディレクトリ、ファイル
. (directory) | + data (directory) | +-- sample.xlsxパッケージをインストールする
pip install pywin32
Excel を読む
Excel を起動・終了する
import win32com.client # 起動する app = win32com.client.Dispatch("Excel.Application") # 終了する app.Quit()xlsxファイルからワークブックを読み取り専用で開く
from pathlib import Path # 開く abspath = str(Path(r"data/sample.xlsx").resolve()) workbook = app.Workbooks.Open(abspath, UpdateLinks=0, ReadOnly=True) # 閉じる workbook.Close()ワークブックの一覧を得る
xxx[Index]
とxxx(Index)
で要素番号の開始が異なるので注意する。print(app.Workbooks.Count) for i in range(0, app.Workbooks.Count): print(app.Workbooks[i].name)print(app.Workbooks.Count) for i in range(1, app.Workbooks.Count + 1): print(app.Workbooks(i).name)ワークブックを有効にする
workbook.Activate()シートの一覧を得る
print(workbook.Worksheets.Count) for i in range(0, workbook.Worksheets.Count): print(workbook.Worksheets[i].name)print(workbook.Worksheets.Count) for i in range(1, workbook.Worksheets.Count + 1): print(workbook.Worksheets(i).name)シート名からシートを得る
sheet = workbook.Worksheets("Sheet1")シートからセルを得る
sheet.Cells.Item(1,1).Valueシートから範囲を得る
arg = "A1:B3" r = sheet.Range(arg) ret = list() for row_index in range(1, r.Rows.Count + 1): row = [] for col_index in range(1, r.Columns.Count + 1): row.append(r(row_index, col_index).Address) ret.append(row) print(ret) # --> [['$A$1', '$B$1'], ['$A$2', '$B$2'], ['$A$3', '$B$3']]セルの計算式を得る
cell.Formula = '=1+2' cell.Formula # --> '=1+2' cell.Value # --> 3.0セルの値を得る
row_index, col_index = 1, 2 cell = sheet.Cells(row_index, col_index) cell.ClearFormats() cell.NumberFormatLocal # --> 'G/標準' # 整数 cell.Value = 1 cell.Value # --> 1.0 # 文字列 cell.Value = 'あ' cell.Value # --> 'あ' # 日付に解釈されそうな文字列 cell.NumberFormatLocal # --> 'G/標準' cell.Value = '2019-01-01' cell.NumberFormatLocal # --> 'yyyy/m/d' cell.Value # --> pywintypes.datetime(2019, 1, 1, 0, 0, tzinfo=TimeZoneInfo('GMT Standard Time', True)) import datetime datetime.datetime.fromtimestamp(timestamp=pywindt.timestamp(), tz=pywindt.tzinfo) # --> datetime.datetime(2019, 1, 1, 0, 0, tzinfo=TimeZoneInfo('GMT Standard Time', True)) # 時刻に解釈されそうな文字列 cell.Value = '00:00:00' cell.NumberFormatLocal # --> 'h:mm:ss' cell.Value # --> '0.0' cell.Value = '24:00:00' cell.Value # --> '1.0' cell.Value = '48:00:00' cell.Value # --> '2.0'セルのテキストを得る
cell.ClearFormats() cell.NumberFormatLocal # --> 'G/標準' cell.Value = '=1' cell.Value # --> 1.0 cell.Text # --> '1'Excel を書く
新規ワークブックを開く
workbook = app.Workbooks.Add()ワークブックを保存する
workbook.Save()その他
Excel を見えなくする
app.Visible = Falseシートの保護を設定・解除する
sheet.Protect() sheet.Unprotect()警告を表示しないようにする
app.DisplayAlerts = Falseセルのアドレスを得る
cell.Address # --> '$B$1' cell.GetAddress() # --> '$B$1' cell.GetAddress(RowAbsolute=False, ColumnAbsolute=False) # --> 'B1' # NGな例 #cell.Address(RowAbsolute=False, ColumnAbsolute=False)Tips
dirメソッドは使えない
import win32com.client app = win32com.client.Dispatch("Excel.Application") dir(app) # --> ['_ApplyTypes_', '_FlagAsMethod', '_LazyAddAttr_', '_NewEnum', '_Release_', '__AttrToID__', '__LazyMap__', '__bool__', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_builtMethods_', '_enum_', '_find_dispatch_type_', '_get_good_object_', '_get_good_single_object_', '_lazydata_', '_make_method_', '_mapCachedItems_', '_oleobj_', '_olerepr_', '_print_details_', '_proc_', '_unicode_to_string_', '_username_', '_wrap_dispatch_']参考資料
ライブラリ比較
PythonでExcelファイルを扱うライブラリの比較
https://note.nkmk.me/python-excel-library/Microsoft
Workbook オブジェクト
https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbookExcel + VBA
VBAのソースを、Gitとかで管理する
https://qiita.com/ryotaro76/items/63cad3dfb891a9df7c88PowerShell + Excel
PowerShell で Excel をどうのこうのすることに興味を持ってくれると嬉しい
https://qiita.com/miyamiya/items/161372111b68bad0744aPython + openpyxl
openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files
https://openpyxl.readthedocs.io/en/stable/Python + win32com
Python for Windows (pywin32) Extensions
https://github.com/mhammond/pywin32TODO: Exccel + win32com でよくまとまった記事を見た記憶があるがリンクを見つけられない。見つけたら書く。
Python + xlwings
xlwings - Make Excel Fly!
https://docs.xlwings.org/en/stable/index.htmlExcelからPythonを使う
https://qiita.com/katzhide/items/60d0336b322105bf8fe9PythonとxlwingsでExcelファイルをいじる
https://idontwannawork.github.io/posts/edit-excel-with-python-and-xlwings/how to unprotect a sheet? #1032
https://github.com/xlwings/xlwings/issues/1032
- 投稿日:2019-10-02T07:17:45+09:00
pythonでインデントを不要にしてみた【php】
0. 動機
「pythonはc言語やjavaでいうブロックの代わりにインデントを使うんだよ」というのを そういうものだと受け入れたら、今までできたはずの言語で構文エラーしまくることになる事例を知った。
僕自身はpythonについて、ほとんどしらない。
唯一知ってるのは「pythonがインデントに意味を持たせたのは有害」ということだけ。
→pythonには「1行1ステートメント」という考え方があり、だからこそブロックの括弧やセミコロンは不要[1]というのは後から知った。
そこで、インデントの代わりにブロック{
}
を使って記述したpythonコードを、正しいpythonコードに置き換えるプログラムを作ろうという気になった。
phpがインストールされているサーバからみんなが使えるように、phpで作る。1.プログラムの構成
- テキストエリアから、インデントの代わりにブロックで書かれたpyファイルの中身を受け取る(元コンテンツと呼ぶことにする)
- 元ファイルのブロックはインデントに置き換え、元ファイルのインデントは捨てる
- 結果を表示
1-1.
次のようなblock2indent.htmlを作ってみた。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>インデントが不要でブロックが使えるpython</title> </head> <body> ①インデントの代わりにブロックを用いたpythonコードを入力<br> <form method="post" action="block2indent.php"> <textarea name="content" cols="50" rows="5"></textarea> <br> <br> ②改行コードを選択<br> <select name="returnCode"> <option value="rn"> \r\n </option> <option value="n"> \n </option> <option value="r"> \r </option> </select> <br> <br> ③<input type="submit"> </form> </body> </html>余談だが、Eclipseで「PHP 開発ツール (PDT)」をインストールし、エディタに「<」とだけ打ち込むと、入力候補としてという選択肢が表示される。これを無視してバックスペースキーを押して「<」を消すと、cssやhtmlなど様々なコードのテンプレートが選べるようになる。これでhtml 4.01 strictのコードなども容易にかける。
block2indent.htmlをクロームで開くと、図1.1.1のように表示され、「送信」を押すと、「ファイルが見つかりません」のエラーへ遷移した。
図1.1.1 block2indent.html
1-2.
block2indent.htmlを名前変更しblock2indent.phpとした。
そして、次のように追記した。<?php $samplePy= " class Man:{ def __inin__(self, name):{ self.name = name; print(\"インスタンス生成および初期化に成功\"); } def hello(self):{ print(\"Hello \" + self.name + \"!\") } } m = Man(\"David\"); m.hello(); "; /*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml(); function printHtml() { print '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>インデントが不要でブロックが使えるpython</title> </head> <body> ①インデントの代わりにブロックを用いたpythonコードを入力<br> <form method="post" action="block2indent.php"> <textarea name="content" cols="50" rows="5"></textarea> <br> <br> ②改行コードを選択<br> <select name="returnCode"> <option value="rn"> \r\n </option> <option value="n"> \n </option> <option value="r"> \r </option> </select> <br> <br> ③<input type="submit"> </form> </body> </html>'; } function killIndent($indentAlive) { $indentDying = preg_replace("/\A( |\t)+/i", "", $indentAlive); return preg_replace("/(\n|\r|(\r\n))( |\t)+/i", "$1", $indentDying); } function block2indent($blockAlive) { //行頭以外でブロックが開始していたら、ブロック開始直前に改行挿入 //同様に、ブロックの終わりも改行を強制 $blockDying = putNewLineBeforeBlockOpen($blockAlive); $blockDying = putNewLineAfterBlockClose($blockDying); //各行ごとの「深さ」を調べ、その分だけスペース4つを挿入 $blockDying = createIndent($blockDying); //ブロックをなくす return killBlock($blockDying); } function putNewLineBeforeBlockOpen($blockOpenWhileARow) { return preg_replace("/(.+){/i", "$1\r\n{", $blockOpenWhileARow); } function putNewLineAfterBlockClose($blockCloseWhileARow) { return preg_replace("/}(.+)/i", "}\r\n$1", $blockCloseWhileARow); } function createIndent($noIndent) { //改行コードを統一 $returnCode = strcmp($_POST['returnCode'], "r")===0 ? "\r": strcmp($_POST['returnCode'], "n")===0 ? "\n": "\r\n"; $noIndent = preg_replace("/\r([^\n])/i", $returnCode."$1", $noIndent); $noIndent = preg_replace("/([^\r])\n/i", "$1".$returnCode, $noIndent); //改行区切りで配列に入れる $lines = explode($returnCode, $noIndent); //各行に対して、深さを調べる $i = 0; $depth = 0; $lineIdx2depth = array(); foreach($lines as $line) { if(strpos($line, "{")===0) $depth++; $lineIdx2depth[$i] = $depth; if(strpos($line, "}")!==false) $depth--; $i++; } //書き込む $i = 0; $ownIndent = ""; foreach($lines as $line) { for($j=0; $j < $lineIdx2depth[$i]*4; $j++) $ownIndent .= " "; $ownIndent .= $line; $ownIndent .= $returnCode; $i++; } return $ownIndent; } function killBlock($blockAlive) { $returnCode = strcmp($_POST['returnCode'], "r")===0 ? "\r": strcmp($_POST['returnCode'], "n")===0 ? "\n": "\r\n"; return preg_replace("/".$returnCode."( |\t)*{( |\t)*/i", "", preg_replace("/".$returnCode."( |\t)*}( |\t)*".$returnCode."/i", "", $blockAlive)); }
printHtml
関数が実行されると、1-1でみたblock2indent.htmlの内容がphpにより出力される。
しかし今回はif(true)
に対するelse
として記述されているため、printHtml
は呼び出されない。その代わり、$samplePy
に対してkillIndent
関数やblock2indent
関数が適用されたものが出力される。
$samplePy
の内容は、次の通り、{
}
を用いて書かれた「謝ったpythonコード」である。class Man:{ def __inin__(self, name):{ self.name = name; print("インスタンス生成および初期化に成功"); } def hello(self):{ print("Hello " + self.name + "!") } } m = Man("David"); m.hello();これに
killIndent
関数が適用されると、次のようになる。class Man:{ def __inin__(self, name):{ self.name = name; print("インスタンス生成および初期化に成功"); } def hello(self):{ print("Hello " + self.name + "!") } } m = Man("David"); m.hello();さらに
block2indent
関数が適用されると、次のように正しいpythonコードが出力される。class Man: def __inin__(self, name): self.name = name; print("インスタンス生成および初期化に成功"); def hello(self): print("Hello " + self.name + "!") m = Man("David"); m.hello();実際には次の手順に従って実行されている。
{
の直前と}
の直後に改行がなければ挿入- 各行ごとに
{
}
の深さ$depth
の取得- 各行ごとに4×
$depth
個だけ空白を先頭に追加{
や}
を行ごと削除1-3
プログラム自体は完成したので、後はサーバにアップロードして、外部からhttp通信でアクセスできるようにすることを考えよう。
1-2で書いたblock2indent.phpに対し、
/*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml();の部分の先頭に
/
を追加し、//*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml();として、サーバへアップロードするだけでよい。
こうすることで、サーバ上のblock2indent.phpは次のように振舞うことになる。
- 自身へ何もpost送信されていない場合(=最初にアクセスされたとき)
- 1-1で書いたblock2indent.htmlと同じものを出力
- 送信ボタンが押されると、 自分自身へテキストエリア内の文字列をpost送信
- 自身へテキストエリア内の文字列がpost送信された場合
- 1-2のアルゴリズム通り、pythonコードへ変換を行う
- 結果を出力する
実際、図1.3.1のように入力し、送信ボタンを押すと、次のように正しいコードが出力された。(但し、改行コードの選択肢に
は含めていないので、「ソースを表示」から見る必要がある。)class Woman: def __init__(self, name): self.name = name; print("インスタンス生成および初期化に成功"); def hello(self): print("Hello " + self.name + "!") m = Man("Alice"); m.hello();入力例のインデントを多少変えても、出力は同じになる。
2. このプログラムを利用したい方
つまらないものですが、http://rights-for.men/block2indent.php からどうぞ。
3 n.参考
- 投稿日:2019-10-02T07:17:45+09:00
pythonでも中括弧{ }でブロックを作れるようにしてみた【php】
0. 動機
「pythonのブロックはc言語やjavaみたいに中括弧(
{
}
)でやるんじゃなくて、インデントを使うんだよ」というのを そういうものだと受け入れたら、今までできたはずの言語でうっかり同じことをしてしまい、構文エラーしまくることになる事例を知った。
僕自身はpythonについて、ほとんどしらない。
唯一知ってるのは「pythonがインデントに意味を持たせたのは有害」ということだけ。
→pythonには「1行1ステートメント」という考え方があり、だからこそブロックの括弧やセミコロンは不要[1]というのは後から知った。
そこで、インデントの代わりに{
}
を使ってブロックを記述したpythonコードを、正しいpythonコードに置き換えるプログラムを作ろうという気になった。
phpがインストールされているサーバからみんなが使えるように、phpで作る。1.プログラムの構成
- テキストエリアから、インデントの代わりにブロックで書かれたpyファイルの中身を受け取る(元コンテンツと呼ぶことにする)
- 元ファイルのブロックはインデントに置き換え、元ファイルのインデントは捨てる
- 結果を表示
1-1.
次のようなblock2indent.htmlを作ってみた。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>インデントが不要でブロックが使えるpython</title> </head> <body> ①インデントの代わりにブロックを用いたpythonコードを入力<br> <form method="post" action="block2indent.php"> <textarea name="content" cols="50" rows="5"></textarea> <br> <br> ②改行コードを選択<br> <select name="returnCode"> <option value="rn"> \r\n </option> <option value="n"> \n </option> <option value="r"> \r </option> </select> <br> <br> ③<input type="submit"> </form> </body> </html>余談だが、Eclipseで「PHP 開発ツール (PDT)」をインストールし、エディタに「<」とだけ打ち込むと、入力候補としてという選択肢が表示される。これを無視してバックスペースキーを押して「<」を消すと、cssやhtmlなど様々なコードのテンプレートが選べるようになる。これでhtml 4.01 strictのコードなども容易にかける。
block2indent.htmlをクロームで開くと、図1.1.1のように表示され、「送信」を押すと、「ファイルが見つかりません」のエラーへ遷移した。
図1.1.1 block2indent.html
1-2.
block2indent.htmlを名前変更しblock2indent.phpとした。
そして、次のように追記した。<?php $samplePy= " class Man:{ def __inin__(self, name):{ self.name = name; print(\"インスタンス生成および初期化に成功\"); } def hello(self):{ print(\"Hello \" + self.name + \"!\") } } m = Man(\"David\"); m.hello(); "; /*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml(); function printHtml() { print '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>インデントが不要でブロックが使えるpython</title> </head> <body> ①インデントの代わりにブロックを用いたpythonコードを入力<br> <form method="post" action="block2indent.php"> <textarea name="content" cols="50" rows="5"></textarea> <br> <br> ②改行コードを選択<br> <select name="returnCode"> <option value="rn"> \r\n </option> <option value="n"> \n </option> <option value="r"> \r </option> </select> <br> <br> ③<input type="submit"> </form> </body> </html>'; } function killIndent($indentAlive) { $indentDying = preg_replace("/\A( |\t)+/i", "", $indentAlive); return preg_replace("/(\n|\r|(\r\n))( |\t)+/i", "$1", $indentDying); } function block2indent($blockAlive) { //行頭以外でブロックが開始していたら、ブロック開始直前に改行挿入 //同様に、ブロックの終わりも改行を強制 $blockDying = putNewLineBeforeBlockOpen($blockAlive); $blockDying = putNewLineAfterBlockClose($blockDying); //各行ごとの「深さ」を調べ、その分だけスペース4つを挿入 $blockDying = createIndent($blockDying); //ブロックをなくす return killBlock($blockDying); } function putNewLineBeforeBlockOpen($blockOpenWhileARow) { return preg_replace("/(.+){/i", "$1\r\n{", $blockOpenWhileARow); } function putNewLineAfterBlockClose($blockCloseWhileARow) { return preg_replace("/}(.+)/i", "}\r\n$1", $blockCloseWhileARow); } function createIndent($noIndent) { //改行コードを統一 $returnCode = strcmp($_POST['returnCode'], "r")===0 ? "\r": strcmp($_POST['returnCode'], "n")===0 ? "\n": "\r\n"; $noIndent = preg_replace("/\r([^\n])/i", $returnCode."$1", $noIndent); $noIndent = preg_replace("/([^\r])\n/i", "$1".$returnCode, $noIndent); //改行区切りで配列に入れる $lines = explode($returnCode, $noIndent); //各行に対して、深さを調べる $i = 0; $depth = 0; $lineIdx2depth = array(); foreach($lines as $line) { if(strpos($line, "{")===0) $depth++; $lineIdx2depth[$i] = $depth; if(strpos($line, "}")!==false) $depth--; $i++; } //書き込む $i = 0; $ownIndent = ""; foreach($lines as $line) { for($j=0; $j < $lineIdx2depth[$i]*4; $j++) $ownIndent .= " "; $ownIndent .= $line; $ownIndent .= $returnCode; $i++; } return $ownIndent; } function killBlock($blockAlive) { $returnCode = strcmp($_POST['returnCode'], "r")===0 ? "\r": strcmp($_POST['returnCode'], "n")===0 ? "\n": "\r\n"; return preg_replace("/".$returnCode."( |\t)*{( |\t)*/i", "", preg_replace("/".$returnCode."( |\t)*}( |\t)*".$returnCode."/i", "", $blockAlive)); }
printHtml
関数が実行されると、1-1でみたblock2indent.htmlの内容がphpにより出力される。
しかし今回はif(true)
に対するelse
として記述されているため、printHtml
は呼び出されない。その代わり、$samplePy
に対してkillIndent
関数やblock2indent
関数が適用されたものが出力される。
$samplePy
の内容は、次の通り、{
}
を用いて書かれた「謝ったpythonコード」である。class Man:{ def __inin__(self, name):{ self.name = name; print("インスタンス生成および初期化に成功"); } def hello(self):{ print("Hello " + self.name + "!") } } m = Man("David"); m.hello();これに
killIndent
関数が適用されると、次のようになる。class Man:{ def __inin__(self, name):{ self.name = name; print("インスタンス生成および初期化に成功"); } def hello(self):{ print("Hello " + self.name + "!") } } m = Man("David"); m.hello();さらに
block2indent
関数が適用されると、次のように正しいpythonコードが出力される。class Man: def __inin__(self, name): self.name = name; print("インスタンス生成および初期化に成功"); def hello(self): print("Hello " + self.name + "!") m = Man("David"); m.hello();実際には次の手順に従って実行されている。
{
の直前と}
の直後に改行がなければ挿入- 各行ごとに
{
}
の深さ$depth
の取得- 各行ごとに4×
$depth
個だけ空白を先頭に追加{
や}
を行ごと削除1-3
プログラム自体は完成したので、後はサーバにアップロードして、外部からhttp通信でアクセスできるようにすることを考えよう。
1-2で書いたblock2indent.phpに対し、
/*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml();の部分の先頭に
/
を追加し、//*/ if(isset($_POST['content'])) print block2indent(killIndent($_POST['content'])); /*/ if(true) print block2indent(killIndent($samplePy)); //*/ else printHtml();として、サーバへアップロードするだけでよい。
こうすることで、サーバ上のblock2indent.phpは次のように振舞うことになる。
- 自身へ何もpost送信されていない場合(=最初にアクセスされたとき)
- 1-1で書いたblock2indent.htmlと同じものを出力
- 送信ボタンが押されると、 自分自身へテキストエリア内の文字列をpost送信
- 自身へテキストエリア内の文字列がpost送信された場合
- 1-2のアルゴリズム通り、pythonコードへ変換を行う
- 結果を出力する
実際、図1.3.1のように入力し、送信ボタンを押すと、次のように正しいコードが出力された。(但し、改行コードの選択肢に<br>は含めていないので、「ソースを表示」から見る必要がある。)
class Woman: def __init__(self, name): self.name = name; print("インスタンス生成および初期化に成功"); def hello(self): print("Hello " + self.name + "!") m = Man("Alice"); m.hello();入力例のインデントを多少変えても、出力は同じになる。
2. このプログラムを利用したい方
つまらないものですが、http://rights-for.men/block2indent.php からどうぞ。
3.参考
- 投稿日:2019-10-02T03:02:19+09:00
Pythonを使って擬似的にプログラミング言語を自作する
環境
macOS Mojave 10.14.4
python 3.5.4
g++ 9.2.0やりたいこと
なるべく気軽にプログラミング言語を作って遊びたい
できれば実行ファイルを生成したいどういう風にコンパイルするか
・コンパイラを作る
これはそこまで気軽でない&技術的に厳しい...ので別の方法を考える
・トランスパイルをしてから既存のコンパイラを使ってコンパイルする
コンパイラを作るのよりは気軽だし、個人的に楽しむだけなので強引な書き方をしてもいいのでこれにする
とりあえずトランスパイラを作ってみる
正しい作り方は知らないが別に今回は深く考えずに作ってみる
変換元の言語はcesという名前にし、返還後はc++のコードにすることにしたcesの仕様
・基本的にはBASICのような構文(というよりDckuino.jsのような感じかも)
・トランスパイルするとき、関数の引数は対応表の多重リスト内の番号で判別するまずは単純にトランスパイルをしたソースコードを出力してみる
ces.py# 変数の宣言 inp="" tmp=0 out="" INPUT=[] OUTPUT=[] k=0 # トランスパイルの対応表 ces_command=["imp","usi","out","box","#","retu","{","}","func"] cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],["// ",1],["return ",1,";","\n}"],["{"],["}"],[1," ",2,"()","{"]] # ENDと入力されるまで入力されたcesのコードをリストに追記 while inp != "END": inp=input() INPUT.append(list(map(str,inp.split()))) # 出力を見やすくするために一行空ける print() # 対応表を使ってトランスパイル for i in INPUT: if i[0] in ces_command: tmp = ces_command.index(i[0]) for u in cpp_command[tmp]: if type(u) is int: if u<=len(i)-1: out += i[u] else: out += u OUTPUT.append([out]) out = "" # トランスパイルした結果を出力 for i in OUTPUT: for j in i: print(j)結果
---入力--- # いつものおまじない imp bits/stdc++.h imp string usi std # 変数の宣言・代入 box int i 1 box int j 10 box string s “HelloWorld!” # main関数 func int main # 出力 out s out i+j # 0を返す retu 0 # 終了 END ---出力--- // いつものおまじない #include <bits/stdc++.h> #include <string> using namespace std; // 変数の宣言・代入 int i = 1; int j = 10; string s = “HelloWorld!”; // main関数 int main(){ // 出力 cout << s << endl; cout << i+j << endl; // 0を返す return 0; } // 終了うまくいった(インデントは汚いけど最終的にコンパイルするので気にしないでおく)
エラーコードを出力してみる
今のままだと存在しない命令を呼び出したときや、関数の引数が足りない場合(引数が多い場合、余計な引数は無視される)、無視してそのままトランスパイルされてしまうので、その場合はエラーメッセージを出力してトランスパイルを失敗させたい。
今回は存在しない命令を呼び出したときと関数の引数が足りないときの2パターン分のエラーメッセージを用意すればいいので、"^\nerror!\nThere are not enough arguments\nRequired amount : "n
" ^\nerror!\nThere is no ","code"," instruction"
Google先生の協力のもとエラーメッセージを作成した
これを先ほどのコードに混ぜるとces.py#変数の宣言 inp="" tmp=0 out="" INPUT=[] OUTPUT=[] ERROR=[] k=0 ind=0 bo=False # トランスパイルの対応表 ces_command=["imp","usi","out","box","#","retu","{","}","func"] cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],["// ",1],["return ",1,";","\n}"],["{"],["}"],[1," ",2,"()","{"]] # ENDと入力されるまで入力されたcesのコードをリストに追記 while inp != "END": inp=input() if inp[0]=="!": INPUT.append(["!",inp[1:len(inp)]]) else: INPUT.append(list(map(str,inp.split()))) # 出力を見やすくするために一行空ける print() # 対応表を使ってトランスパイル・エラーの判定 for ind,i in enumerate(INPUT,1): if i[0] in ces_command: tmp = ces_command.index(i[0]) for u in cpp_command[tmp]: if type(u) is int: if u<=len(i)-1: out += i[u] else: k=0RR for q in cpp_command[tmp]: if type(q) is int and q > k: k=q ERROR.append([str(i)," ^\nline : "+str(ind)+" error!\nThere are not enough arguments\nRequired amount : "+str(k)]) bo=True break else: out += u OUTPUT.append([out]) out = "" elif i != ["END"]: ERROR.append([i," ^\nline : "+str(ind)+" error!\nThere is no ",i," instruction"]) bo=True # エラーが出ていなければトランスパイルした結果を出力し、エラーが出ていればエラーを出力する if bo == True: for i in ERROR: for j in i: print(j) print() else: for i in OUTPUT: for j in i: print(j)こうなり、
---入力--- imp bits/stdc++.h hello world niceday hello box int out END ---出力--- ['hello'] ^ line : 2 error! There is no ['hello'] instruction ['world'] ^ line : 3 error! There is no ['world'] instruction ['niceday'] ^ line : 4 error! There is no ['niceday'] instruction ['hello'] ^ line : 5 error! There is no ['hello'] instruction ['box', 'int'] ^ line : 6 error! There are not enough arguments Required amount : 3 ['out'] ^ line : 7 error! There are not enough arguments Required amount : 1うまくいった!
ここまできたらあとはコンパイルをするだけ
コンパイルをどうやってするか
とりあえずコンパイラはg++を使うのは確定しているので、cesファイルを読み込んでトランスパイルをしたfile.cppを生成して、 g++ "file.cpp" -o "file.out" みたいなコマンドを実行するようにすれば良さそう
ces → c++ → 実行ファイルまでを実装したもの
ces.py# コマンド実行・エラー発生時に途中終了するためのモジュールを読み込む import sys import os # 変数の宣言 inp="" tmp=0 out="" INPUT=[] OUTPUT=[] ERROR=[] k=0 com="" bo=False # トランスパイルの対応表 ces_command=["imp","usi","impio","impbit","out","box","!","#","retu","{","}"] cpp_command=[["#include <",1,">"],["using namespace ",1,";"],["#include <iostream>\nusing namespace std;"],["#include <bits/stdc++.h>\nusing namespace std;"],["cout << ",1," << endl",";"],[1," ",2," = ",3,";"],[1],["// ",1],["return ",1,";"],["{"],["}"]] # 引数の取得 fni=sys.argv fn=fni[1] cn=fni[2] # 入力を受け付ける代わりに第1引数で指定されたファイルを読み込み、cesのコードをリストに追記していく _file=open(fn,"r") for line in _file: inp=line if inp[0]=="!": INPUT.append(["!",inp[1:len(inp)]]) else: INPUT.append(list(map(str,inp.split()))) # ファイルを閉じる _file.close() # 対応表を使ってトランスパイル・エラーの判定 for i in S: if i[0] in ces_command: tmp = ces_command.index(i[0]) for u in cpp_command[tmp]: if type(u) is int: if u<=len(i)-1: out += i[u] else: k=0 for q in cpp_command[tmp]: if type(q) is int and q > k: k=q ERROR.append([str(i)," ^\nerror!\nThere are not enough arguments\nRequired amount : "+str(k)]) bo=True break else: out += u OUTPUT.append([out]) out = "" elif i != ["END"]: ERROR.append([i," ^\nerror!\nThere is no ",i," instruction"]) bo=True # エラーが出ていなければトランスパイルした結果を第2引数で指定されたファイル.cppに上書きし、トランスパイルの成功を通知する。エラーが出ていればエラーを出力する if bo == True: # エラーの出力を見やすくするために一行空ける print() for i in E: for j in i: print(j) print() else: print(":) < ces trance was successful!") _file = open(cn+".cpp","w") for i in O: for j in i: _file.write(j) # ファイルを閉じ、第2引数で指定されたファイル名の実行ファイルを作成する _file.close() com="g++ "+cn+".cpp -o "+cn os.system(com) # トランスパイルしてcppファイルは消しておく com="rm "+cn+".cpp" os.system(com)試してみる
cesファイル
ces_test.cesimp bits/stdc++.h imp string usi std box int i 1 box int j 10 box string s "hello" box string t "world" func int main null out i+j out s out t out "niceday" retu 0コンパイル
user:~ user$ python ces.py ces_test.ces ces_test.out :) < ces trance was successful! user:~ user$ ./ces_test.out 11 hello world niceday user:~ user$コンパイルまでできてる!!!
すごく嬉しい:)
最後に
思ったよりも簡単にできたし、何より楽しいので、ぜひ一度擬似的にプログラミング言語を自作してみることをオヌヌメします。今回は個人的に楽しむようなのでまだ機能不足すぎますが、これからどんどん強化するのも楽しそうです。機能の拡張をやり次第追記していこうと思います。
- 投稿日:2019-10-02T02:31:35+09:00
IBM Cloud functionsを触ってみた〜その1〜
IBM CloudはWatsonのAPIを使うためだけのクラウドサービスってイメージが強いですが、実際はAWSやGCPのようにDBやアプリを動かすためのサーバーも提供されており、IBM Cloudだけでwebサービスをつくるなんてことも可能です。そしてAWSのlambdaのようなサーバーレス実行環境も存在します。その名も「IBM Cloud functions」。というわけで今回はこのIBM Cloud Functionsを使っていろいろ遊んでみようと思います。
IBM Cloud Functionsって何?
IBM Cloudで提供されているFunction as a Service(FaaS)のことです。Functionを実行するためのイベント(トリガー)が発生してからサーバーが立ち上がって、コードが実行されてサーバーが止まるまでの時間が動作時間として計算される仕組みになっています。もともとはApache OpenWhiskとして提供されていたようですが、2017年8月にIBM Cloud Functionsに名称変更されたようですが大きな仕様変更は無さそうです。標準ではNode.js, python, Ruby, Swift, PHP, Goが対応しています。それ以外の言語はdockerで構築した環境をデプロイすることで動作できるそうです。ちなみにライトプランでも使えます。
とりあえず動かしてみた
まずはお約束のHello Worldをやってみます。ダッシュボードを開いたらメニュー(左上の三本線)を開き、そこからfunctionsを開きます。
「作業の開始」をクリックし、そこからアクションを作成します。ここで言うアクションがFunctionのコードのことです。ということで「Create Action」をクリックします。
すると、新しいアクションの設定画面に移りますので、Function名と言語を定義します。今回はpython3を選択しています。Createをクリックすると、以下のようにコードエディタのある画面が表示されます。
中にはたった3行のコードが最初から書かれています。まずはシンプルにこのコードを実行してみます。
Invoke
をクリックしてみると右側に以下のような実行結果が出力されます。Logsには何も表示されていませんが、コードの中にあるprintの出力やエラーがここに表示されます。デバッグに使えますね。
REST APIで動かしてみる
では、今度はREST API的な使い方をやってみます。左側のメニューから「Endpoints」を選択してエンドポイントの情報を表示させます。その後、下の方にあるCURLの情報があるので、右端の書類のアイコンをクリックしてCurlコマンドをコピーします。ちなみに
API-KEY
の部分にはすでにAPIキーが入っているのでターミナル上でそのままコピーしたコマンドを実行できるようになっています。Curlを実行すると、以下のようなjsonが出力されます。
{ "activationId": "698e86c70a5f40bb8e86c70a5f50bbfd", "annotations": [ { "key": "path", "value": "your@mail_dev/kmiuraHelloWorld" }, { "key": "waitTime", "value": 42 }, { "key": "kind", "value": "python:3.7" }, { "key": "timeout", "value": false }, { "key": "limits", "value": { "concurrency": 1, "logs": 10, "memory": 256, "timeout": 60000 } } ], "duration": 2, "end": 1569937475019, "logs": [], "name": "kmiuraHelloWorld", "namespace": "YOUR_NAMESPACE", "publish": false, "response": { "result": { "message": "Hello world" }, "status": "success", "success": true }, "start": 1569937475017, "subject": "YOUR_MAIL", "version": "0.0.1" }なにか通知してみる
LINE Notifyのアクセストークンを発行する
せっかくなので何か通知をするアクションを作成してみます。そこで、LINE Notifyを使います。こちらからログインして、アカウント名→マイページを開きます。
マイページの下にある「トークンを発行する」をクリックして、Notify用のトークン名を設定し、「1:1でLINE Notifyから通知を受け取る」を選択してトークンを発行してください。
※発行されたトークンは発行画面を閉じると2度と確認することが出来ないので忘れずにコピーしておきます。
IBM Cloud Functionsでの設定
もう1度Cloud Functionsに戻って先程コピーしたアクセストークンを記入します。コードに直接書く方法もありますが、今回は「Parameters」に変数を定義してみます。今回は
Parameter Name
にはAPI_TOKEN
を、Parameter Value
には先程コピーしたアクセストークンを記入します。このとき、アクセストークンは必ずダブルクオーテーションで囲みます。ここで定義した値はコード上では、
dict["ACCESS_TOKEN"]
で呼び出すことが出来ます。以上を踏まえて、LINE Notifyに通知するコードを足したコードが以下に示すものになります。# # # main() will be run when you invoke this action # # @param Cloud Functions actions accept a single parameter, which must be a JSON object. # # @return The output of this action, which must be a JSON object. # # import sys import json import requests def main(dict): # set Value access_token = dict["ACCESS_TOKEN"] url = 'https://notify-api.line.me/api/notify' headers = { 'Authorization': 'Bearer {}'.format(access_token), } payload = { 'message': 'Hello World', } # request Notify response = requests.post(url, headers=headers, params=payload) res = json.loads(response.text) print(res) return { 'message': res["message"] }エディタのコードを上記に置き換えて、右上にある
Save
をクリックしてからInvoke
をクリックして実行します。すると、以下のようにメッセージが表示されれば、成功です。
IBM Cloud Functionsを使えば、サーバの保守を気にすることなく作りたい機能を実装出来てしまうので魅力的だと思いますが、動作が遅かったりデメリットもあったのでそこは次回また詳しく掘り下げた後にまとめようと思います。
- 投稿日:2019-10-02T01:41:56+09:00
ナップザック問題
入力
N W w1 v1 w2 v2 ・ ・ wN vN商品の数N、ナップザックの容量W、各商品の重さと値段スペース区切り
出力
重さW以下での価値の総和の最大値
コード
dp.pyN,W = map(int,input().split()) v = [0] * (N) w = [0] * (N) for i in range(N): w_t,v_t = list(map(int,input().split())) v[i] = v_t w[i] = w_t dp = [[0] * (W+1) for _ in range(N+1)] for j in range(N): for k in range(1,W+1): if k >= w[j]: dp[j+1][k] = max(dp[j][k],dp[j][k-w[j]]+v[j]) else: dp[j+1][k] = dp[j][k] print(dp[N][W])参考文献
この記事は以下の情報を参考にして執筆しました。