Denoのsubprocessを用いたtestでTest case is leaking resources.というエラーが出るときの対応方法
Denoのテストで下記のようにsubprocessを使って、コマンド実行結果をテストしようとした。
Deno.test({ name: "test name", fn: async () => { const p = Deno.run({ cmd: [ /*コマンド*/], stdout: "piped", }); const { code } = await p.status(); // 返り値が0であることを確認 assertEquals(code, 0); const rawOutput = await p.output(); const stdoutResult = new TextDecoder().decode(rawOutput).trim(); // コマンドの実行結果が"foo"であることを確認 assertEquals(stdoutResult, "foo"); } });
だが、このテストをdeno test --allow-run
で実行しようとすると、下記のようなエラーとなる。
AssertionError: Test case is leaking resources. Before: { "0": "stdin", "1": "stdout", "2": "stderr" } After: { "0": "stdin", "1": "stdout", "2": "stderr", "4": "child" } Make sure to close all open resource handles returned from Deno APIs before finishing test case.
どうやら開放されていないリソースがあるとこのようなエラーが表示されるらしい。
インターネットで検索してみたところ、日本語で書かれた解説を見つけたので下記に貼る。
(Zennで無償で公開されている Effective Deno内の内容となる)
そしてこのリソース開放に関するエラーを解消するためには下記のような対象方法がある。
が、これは上で紹介した書籍内にも書かれているが、あまりおすすめされた方法ではなく、本来であればリソース開放のための処理を記載する必要がある。
Deno.test({ name: "test name", fn: async () => { const p = Deno.run({ cmd: [ /*コマンド*/], stdout: "piped", }); const { code } = await p.status(); // 返り値が0であることを確認 assertEquals(code, 0); const rawOutput = await p.output(); const stdoutResult = new TextDecoder().decode(rawOutput).trim(); // コマンドの実行結果が"foo"であることを確認 assertEquals(stdoutResult, "foo"); }, sanitizeResources: false, sanitizeOps: false, });
では、今回のようなsubprocessを用いた例の場合、どうすればよいかと思い調べていたところ、aleph.js
内のとあるPRのテストの書き方が参考になった。
(しかも偶然にも上で紹介したEffective Denoの作者の方が出したPRだった)
こちらの書き方を参考に下記のようにコードを修正した。
Deno.test({ name: "test name", fn: async () => { const p = Deno.run({ cmd: [ /*コマンド*/], stdout: "piped", }); const { code } = await p.status(); // 返り値が0であることを確認 assertEquals(code, 0); const rawOutput = await p.output(); const stdoutResult = new TextDecoder().decode(rawOutput).trim(); // コマンドの実行結果が"foo"であることを確認 assertEquals(stdoutResult, "foo"); // リソースを閉じる await p.close(); } });
これでリソース開放に関する表示は解消された。
以上、備忘録でした。
M1 Mac環境でのcx_Freezeの実行ファイル作成までのメモ
Pythonで配布可能な実行ファイルを作成する際に有効な手段としてcx_Freezeがある。
少し前に下記のポストでも書いたが、似た選択肢としてはpyinstallerがあるが、こちらはライセンスがGPLなので、例えば商用で利用するようなケースでは厳しくなってくる。
その点、cx_FreezeはPython Software Foundation License となっているので、GPLよりは扱いやすい。
目次
このポストは少々長いため、まずは目次をこちらに記載する。
- 目次
- M1 MacBook Airだとcx_Freezeのインストールが失敗する(ld: library not found for -lintl)
- GitHubでissueを立てたところ、作者の方が相談に乗ってくれた
- M1 MacBook Airでcx_Freezeのインストールを行い、配布可能な実行ファイルを作成するまでのメモ
M1 MacBook Airだとcx_Freezeのインストールが失敗する(ld: library not found for -lintl)
ところでM1 MacBook Airでcx_Freezeのインストールを試したところ、下記のようなエラーが出て失敗してしまった。
環境は pyenvで構築したPython 3.10.1
である。
インストール時のコマンド
pip install --upgrade cx_Freeze
エラーのメッセージを一部抜粋。
ld: library not found for -lintl clang: error: linker command failed with exit code 1 (use -v to see invocation) error: command '/usr/bin/clang' failed with exit code 1
intel MacBook Proだとcx_Freezeのインストールは問題なさそう
intelチップのMacBook Proで同様の環境( pyenv経由で構築したPython 3.10.1
)を再現し、こちらでインストールしたところ、intelチップのmacでは問題なくcx_Freezeのインストールは完了した。
実行ファイルを作成し、下記のtkinterのサンプルアプリケーションが動くところまで確認している。
cx_Freeze/cx_Freeze/samples/Tkinter at main · marcelotduarte/cx_Freeze · GitHub
GitHubでissueを立てたところ、作者の方が相談に乗ってくれた
エラー内容でググっても私と同じような問題が出なかったことや、同様の問題がGitHubのissueにはなかったようなので、issueを立ててみたところ、作者の方が色々と相談に乗ってくれた。
こちらのissue内でより詳細なエラーログや試行したことの内容はすべて記載されているので、より詳細に内容を知りたい方はissueを参照してください。
M1 MacBook Airでcx_Freezeのインストールを行い、配布可能な実行ファイルを作成するまでのメモ
M1 Macでcx_Freezeを使う際は miniforge
経由でインストールすると動くようだった。
miniforgeを選択した理由
pyenv経由ではminiconda環境を構築することも可能だが、残念ながらminicondaはarm64(m1 mac)には対応していないようだったので、M1でも動く環境であるminiforgeを選択した次第である。
私は今回Python 3.10.1の環境でcx_Freezeを動かしたかったので、下記のようにセットアップをしてcx_Freezeのインストールを行った。
(実際にはまず3.9系のインストールから行うなど他のことも行っているが、そちらについてはissue内のログを参照いただけたらと思います)
# miniforgeのセットアップ pyenv install miniforge3-4.10.3-10 # 指定したディレクトリ内のみでminiforge環境を有効化 pyenv local miniforge3-4.10.3-10 # 仮想環境構築 export SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk conda create -n cx310conda -c conda-forge python=3.10 libpython-static -y conda init zsh source ~/.zshrc conda activate cx310conda # cx_Fre conda install -c conda-forge c-compiler importlib-metadata -y pip install --no-binary :all: --pre cx_Freeze -v
なお、cx_Freezeのページを見ると下記のようなインストール方法も紹介されていたが、
conda install -c conda-forge cx_freeze
私の環境ではこの方法でインストールしようとしても、
PackagesNotFoundError: The following packages are not available from current channels:
というエラーが出てしまい、installできなかったため、上記のやり方でインストールしている。
なお、こちらについては作者の方でconda-forgeにosx-arm64のビルドを登録リクエストしてくれたようなので、そのうち conda-forge
でもインストールが可能になるかもしれない。
というわけで、miniforge
を用いることでm1 macでもcx_Freezeが使えるようになった。
下記のキャプチャは実際にcx_Freezeを使って作成したtkinterのサンプルアプリを実行したところ。
追記: Apple Silicon用のビルドがconda intallを用いたインストールにも対応した
現在はconda install
でもM1(Apple Silicon) Mac用のビルドがダウンロードできるようになった。そのため下記のコマンドでインストールが可能。
conda install -c conda-forge cx_freeze
M1(Apple Silicon) Macでも下記のコマンドでcx_Freezeがインストールできるようになりました。対応に感謝。
— Yuki Shindo (@shinshin86) January 24, 2022
```
conda install -c conda-forge cx_freeze
```
M1環境でも使いたかったので嬉しい。ちなみに今日時点でcx_Freezeの6.10が入ります。
詳細は下記のissueにて。https://t.co/g3mn9kcC92
cx_Freezeを用いてmac環境で配布可能な実行ファイルを作成する方法
次にmac環境で配布可能な実行ファイルを作成する方法だが、これも作者の方に教えてもらったのだが、mac環境で配布可能な実行ファイルを作成する方法は下記のようだ。
python setup.py bdist_mac
https://cx-freeze.readthedocs.io/en/latest/setup_script.html#bdist-mac
これで上に貼ったtkinterのサンプルアプリケーションをcx_Freezeを用いて配布可能な実行ファイルとしてコンパイルすることができた。
作者の方には色々と相談に乗ってくれて感謝である。
以上のようなやり方で、miniforgeを使うことでM1 Macでもcx_Freezeを扱うことができるので、興味ある方はぜひ利用してみてください。
JavaScriptで動的に関数の引数を与える場合はスプレッド構文を使えばよかった
最近、こういう使い方もできるのかと学んだので備忘録がてら残しておこうと思ったのだが、書き始めてからMDNを参照したら、自分がこれから書こうと思ったそのままの例が載っていて、自分の知識の無さに幻滅した次第。
せっかく書き出したのでこちらのポストはそのまま公開しておくが、まあJavaScriptの基本でした。
以下、本文。
const arr = [1,2,3] console.log(...arr)
このコードを実行すると、実行結果は下記となる。
1 2 3
で、下記のようなコードがあった場合、sum
関数の返り値は6となる。
function sum(n1,n2,n3) { if(n1 && n2 && n3) { return n1 + n2 + n3; } else if (n1 && n2) { return n1 + n2; } else { return "足し算をするための引数が足りません"; } } const arr = [1,2,3] console.log(...arr) console.log(sum(...arr))
配列をスプレッド構文で展開した場合 1 2 3
となるが、これはそのまま関数の引数として良さげに渡すことが可能なようだ。
これを使えば動的に引数を関数に渡すことができると思ったのだが、最初にも書いたとおり、JavaScriptの基本だった。
「こういう使い方もできるんだ!」とドヤってポストしなくてよかったと思った。
colors.jsの今回の問題で我々が対応すべきことについての個人的なメモ(実務的な部分のみ)
colors
というnpmパッケージの最新versionについて意図的に悪意のあるコードが実行されるようになっている。
こちらの問題のあるversionなどの具体的な内容についてはazuさんが分かりやすくまとめてくれているので、こちらを参照してみるのが良い。
npmパッケージで依存しているパッケージは多いので、影響はでかい。例えばFirebase CLIも一時期影響を受けていたようだ。
目次
追記にも書いたが、日本時間の2022年1月11日現在、悪意あるバージョン(1.4.1や1.4.2)はnpmからは削除されており、colors.jsをインストールしても問題ない状態になっている。
影響のあるversionと対応方法
なお、影響のあるversionについて引用させていただくと、
- 1.4.44-liberty-2
- 1.4.1
- 1.4.2
となっており、 ^1.0.0
などのversion指定で入っている場合、2022年1月10日時点での1.x.xの最新である1.4.2が選択される状態となっているよう。
colors
を利用しているかは下記のコマンドで確認が可能。
npm ls colors
yarn why colors
もし利用している場合、下記のようにバージョン固定でインストールをするなどの対応が必要。
(1.4.0
は問題ないバージョンであることが確認されており、また、npmでは現在publishしてから24時間以上経過したパッケージのunpublishは基本的にできないようになっているため、問題ないバージョンであることは保証される形となっている。ここらへんも詳細は上の記事を読んでください)
# npm install npm install colors@1.4.0 # yarn add yarn add colors@1.4.0
また、colors
はよく使われているパッケージのため、依存の依存に入っていることも少なくない。
こちらについては基本的にパッケージの更新待ちとなるため、更新が来たら npm update
で更新する。
動く状態の package-lock.json
がある場合は、現状はそのままにしておくのが良い。
package-lock.json
内の colors
を 1.4.0
に直接書き換える方法もあるようだが、これは良いやり方かはちょっとわからない。
(こちらは上に貼った記事内にも記載がある。コードのdiffなども載っているのでそちらの参考をしてみてください)
なお、colorsのリポジトリは下記。
colors.js
の作者が書いたfaker.js
についても最新版では空の状態となっているので、こちらについても最新版を入れると動かないものと思われる。
https://github.com/Marak/faker.jsgithub.com
最後に
私は作者の心情を計り知ることはできないし、よってそこについては何も書かない。
が昨晩、この問題が発覚して自身でもGitHubのissueを見たり、Twitterでの様々な方の発言などを読んだりしていた。
Twitterには、様々な角度・立場からの発言が溢れていて読んでいてすごく考えさせられるものも多かった。
また、偶然にも、ちょうど昨日日中にPHPの現場でOSSについての話を聞いていて、OSSの難しさについてもちょっと考えたりしていたのもすごい偶然だった。
色々と自分の中でも様々な考えが交差するが、うまくそれを言葉にするのが難しい。
よってここで筆を置こうと思う。
追記:2022年1月11日時点で、悪意あるバージョン(1.4.1や1.4.2)はnpmからは削除されている
2022年1月11日時点で、悪意あるバージョン(1.4.1や1.4.2)はnpmからは削除されており、現在はcolors.jsをインストールしても問題のないバージョンがインストールされるようになっている。
またこちらの問題については下記のように報告されているので、そちらのリンクも貼っておく。
tkinterでLabelのテキストを変更する方法
tkinterのLabelのテキストを変更する方法に関する備忘録。
Labelのテキストを変更する方法は簡単で、tk.Label
で生成したLabelインスタンスの text
属性を変更することで、テキストを変更することができる。
具体的には
label["text"] = "変更後のテキスト"
といった形で変更可能。
tkinterでLabelのテキストを変更するサンプル
サンプルコードを下に記載する。
import tkinter as tk from datetime import datetime class App: def __init__(self, root): self.root = root root.title("Label text change sample") root.geometry("250x100") self.datetime_label = tk.Label(root, text="") self.datetime_label.pack() tk.Button(text="Display datetime", width=20, command=self.display_datetime).pack() root.mainloop() def display_datetime(self): self.datetime_label["text"] = datetime.now() def main(): root = tk.Tk() app = App(root) app.root.mainloop() if __name__ == "__main__": main()
このコードを実行して、Display datetime
ボタンを押すと、現在時刻がLabel上に表示される。
なお、こちらのコードはGitHubにも上げている。
Labelのテキストを変更する際に気をつけたいところ
これは個人的に一瞬ハマりかけたところなのだが、tkinterではLabelなどの生成時にメソッドチェーン的な形でpack
や grid
をつなげて書くことができる。
例えば下記のような形。
(上に貼ったサンプルコードの一部を引用している)
tk.Button(text="Display datetime", width=20, command=self.display_datetime).pack()
このように1行で書けるのは便利なので、ついついなんでもこう書いてしまいがちだが、このpack
や grid
などの返り値は NoneType
であることに注意したい。
つまり、下記のように書くとエラーとなる。
(これも上のコード内の引用)
self.datetime_label = tk.Label(root, text="").pack()
こうやって書いてしまうと、self.datetime_label
には LabelのインスタンスではなくNoneType
が入るため、このままアプリを起動してボタンを押すと、
TypeError: 'NoneType' object does not support item assignment
というエラーが出てしまう。
一瞬なぜエラーになったのか分からなかったが、落ち着いて考えてみればNoneTypeが代入されてしまっているだけということに気づいた。
ついついメソッドチェーンで繋げて書くのは便利なので使いがちだが、こういう細かいところで気をつけていかねばと思った次第である。
pyinstallerをmacで利用したときに"OSError: Python library not found: libpython3.10m.dylib, libpython3.10.dylib, Python3, .Python, Python" というエラーが出た場合の対応手順メモ
pyinstallerを利用したときのエラー対応に関する備忘録です。
目次
環境
エラーが起きた際の私の環境は以下。
- M1 Mac(MacBook Air)
- Pythonのversionは3.10.1 (pyenv経由でインストールしている)
事象
pyinstallerを用いて下記のようなコマンドで実行ファイルを生成しようとしたら、
pyinstaller --onefile --noconsole --icon=icon.png main.py
下記のようなエラーが出て失敗した
OSError: Python library not found: libpython3.10m.dylib, libpython3.10.dylib, Python3, .Python, Python
対応方法
pyenvを用いてPython環境の構築は行っていたが、PYTHON_CONFIGURE_OPTS="--enable-shared"
というオプションを付けて、再度上書きでインストールする。
PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.10.1
その後、一度pyinstallerをuninstallして、再度install。
pip uninstall pyinstaller pip install pyinstaller
これでpyinstallerを利用したときに発生していたエラーは解消し、実行ファイルの生成が行えた。
以上備忘録でした。
余談: pyinstallerのライセンスはGPLなので商用利用などには注意
ちなみにこれらの備忘録を書きながらpyinstallerを色々と試していたのですが、ドキュメントを読んでいたときにpyinstallerのライセンスはGPLだということに気づいた。
そのため商用利用などするときには注意が必要。
一応下記のような形で回避する方法もあるようだが、ポスト内にも書かれているように自己責任で、ということみたい。
pyinstallerはぐぐると日本語の情報もたくさん出てくるし、メジャーなやり方のようだが、ここについては気をつけたいところ。
pyinstallerがGPLで使えないなら、cx_Freezeという選択肢?
pyinstallerのだいたいツールとしてはcx_Freezeというツールもあるようだ。
こちらはまだ触っていないが、ライセンスはPython Software Foundation Licenseとなっている。
追記: ちょっと触ってみたが自身の環境だとcx_FreezeはM1 Macだと動かずにInter Macだと動いた。
調べながらのメモはtwitterに投稿しているので、そちらをこちらにもはる。
cx_Freezeをインストールしようとするとエラーになってしまうな...(Python 3.10.1) ドキュメント見る限り、サポートはしていそうだけど。https://t.co/aulpydfDG0
— Yuki Shindo (@shinshin86) January 7, 2022
さらに追記。
その後、色々とあり、M1 Mac環境でもcx_Freezeを使えるようになった。
そちらについては下記のポストにまとめているので参照してみてください。
tkinterとsqlite3を使ってGUIアプリを作ってみる
tkinterとsqlite3(SQLite)を使ってみた際の備忘録です。
sqlite3でdatetimeを(擬似的に)扱う方法については下記を参考にしている。
色々と実装例を見ていると、フロントエンドとバックエンドで構成するほうが良さそうだが、今回はサンプルということで一つのコードにまとめてしまっている。
作ったアプリは下記のようなメッセージを登録・表示できるだけのアプリ。
アプリの動作概要としては、
- 画面上部のフォームにメッセージを入力して、
Save to SQLite
ボタンを押すことでsqlite3を使ってSQLiteにデータを書き込む。 - また、画面下部にある
View all data
ボタンを押すことでSQLite内のすべてのデータを取得してListbox
内に表示する。
以上のようなとてもシンプルなアプリ。
実際のソースコードは下記となる。
import tkinter as tk import tkinter.messagebox as tkm import sqlite3 from datetime import datetime DB_NAME = "msg.db" # msg.dbが作られていない場合は作成を行う def init_db_table(): # set converter detect_types = sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES sqlite3.dbapi2.converters['DATETIME'] = sqlite3.dbapi2.converters['TIMESTAMP'] conn = sqlite3.connect(DB_NAME) cur = conn.cursor() sql_statement = "CREATE TABLE IF NOT EXISTS msg (id INTEGER PRIMARY KEY, message TEXT, created_at datetime)" cur.execute(sql_statement) conn.commit() conn.close() # DB内のデータを全件取得 def view(): conn = sqlite3.connect(DB_NAME) cur = conn.cursor() cur.execute("select * from msg") raws = cur.fetchall() conn.close() return raws # DBにデータを追加する def insert(message): conn = sqlite3.connect(DB_NAME) cur = conn.cursor() sql_statement = "insert into msg values(NULL, ?, ?)" cur.execute(sql_statement, (message, datetime.now())) conn.commit() conn.close() # insert関数を通じて入力されたmessageを保存するための関数 def save_msg(message): insert(message) tkm.showinfo("Save", "Save message") # view関数を通じて取得した全件分のデータを整形してListboxに表示するための関数 def view_msg(listbox): listbox.delete(0, tk.END) for row in view(): data = "ID: " + str(row[0]) + ", Message: " + row[1] + ", CreatedAt:" + str(row[2]) listbox.insert(tk.END, data) # アプリ起動時にDBが作られていない場合はDBの初期化を行う init_db_table() # GUIの初期化 root = tk.Tk() label = tk.Label(root, text="Your message") label.pack() text_form = tk.Entry(root) text_form.pack() save_button = tk.Button(root, text="Save to SQLite", width=20, command=lambda: save_msg(text_form.get())) save_button.pack() listbox = tk.Listbox(root, width=50, height=15) listbox.pack() view_button = tk.Button(root, text="View all data", width=20, command=lambda: view_msg(listbox)) view_button.pack() root.title("sqlite3 sample") root.geometry("600x400") root.mainloop()
コードについては以上となる。
tkinterのより詳細な使い方については、以前下記の記事でもまとめているので、よろしければご覧ください。
あと、上のポストにも書いているのだけど、tkinterで日本語入力が行えないことの解決方法についてはZennにも記載していますので、そちらも併せて参照してみてください。
(原因を調べた限り、Macでtkinterを使おうとすると結構この問題については遭遇する方も多そうなので、こちらのリンクもはらせてもらいます)