はじまり
よしよし、これは便利だぞ。
以前の不満が解消されたな。
Pythonで便利な事柄まとめ
僕が今までPythonを使ってきて、なんでこうなってるんだ訳わからん、こうだったら良いのに、とかとか思ってきてその都度ググっては、Pythonによる開発が楽になるようにある程度土壌は固まってきたような気はしています。
そこで、僕が現在Pythonの開発に導入してみて便利だと思った事柄をまとめましたので、便利そうだなと思ったら試してみてください。それではいきます。
1. Pythonのルートパスを設定する。
Pythonの開発をしてその作ったツールをパッケージ化しようと思った時のことです。
パッケージ化しようとも思ってなかった時は、ソースコードがリポジトリのルートディレクトリに散乱していて、その中にある「main.py」的なやつを動くようにしていたわけなんですけど、パッケージ化しようとなるとそうは行きません。さすがにそんな汚い状態でパッケージ化したくはありません。
なので、main.pyも含めてPythonで動くファイルは全て「src」的なフォルダに入れて、テストコードが書かれているファイルは「tests」的なフォルダに入れましたとさ。そして、Pythonの開発を再開しようと思った矢先のことです。なぜか、さっきまで汚いながらも問題なく動いていたPythonファイル達は、ImportErrorが起こることで全く動かなくなったのです。(厳密に言うと、pytestが動かなくなった。)
「src」フォルダにあるファイルを動かせば、「tests」フォルダにあるファイルが動かなくなる。かといって「src」と「tests」を同じフォルダに配置するのも不細工な感じがしてしまう。一体どうすればいいんだ・・・。
そんな時にまるっとImportErrorを解決してくれたのが、「pyproject.toml」でした。そいつをこんな感じのディレクトリ構造で設置します。
/
│ pyproject.toml
│
├─src
│ a.py
│ b.py
│
├─tests
│ test_a.py
│ test_b.py
そして、「pyproject.toml」の中はこう書いておけばとりあえずは動くかと思います。
[tool.pytest.ini_options]
pythonpath = "src"
testpaths = ["tests",]
2. reuqrements.txtを開発用と本番用に分ける。
Python用のパッケージを作ってそれをインストールする時に、開発の時に使っていたテスト用のライブラリやメモリ使用量などの測定に使っていたライブラリは不要です。なのでそれらのライブラリはパッケージをインストールした時には混ざっていないようにしたい。
そういう時には、「requirements.txt」に加えて「requirements」フォルダも作成しておいて、以下のようなディレクトリ構成で配置しておきます。
/
│ requirements.txt
│
├─requirements
│ common.txt
│ dev.txt
│ prod.txt
│
そして、それぞれのテキストファイルには以下のように書いておきます。
「common.txt」は例えばこのようにします。
PyGithub
functions_framework>=3.5.0
「dev.txt」は例えばこのようにします。
# requirements for development
-r common.txt
# List packages that are only used in the development environment below:
pytest
pytest-cov
pytest-mock
pytest-xdist
memory-profiler>=0.61.0
「prod.txt」は例えばこのようにします。
# requirements for production
-r common.txt
# List packages that are only used in the commercial environment below:
そしていつもの「requirements.txt」にはこのように書いておきます。
-r requirements/prod.txt
ちなみにこの方策はこちらの記事を参考にさせていただきました。
3. Pytest実行方法のあれこれ。
Pythonで書いたソースコードを強固なものにするために、Pytestはとても有用です。そして、そのPytestをより便利なものにするために、Pytestに加えて以下のライブラリもインストールするようにしています。
pytest
pytest-cov
pytest-mock
pytest-xdist
- pytest-cov・・・テスト実行後にカバレッジ(網羅率)を表示することが可能になります。
- pytest-mock・・・テスト時にソースコードの中をモックしやすくするモジュールが追加されます。
- pytest-xdist・・・テストを並列実行できるようにします。
例えば、カバレッジを取得するためには、以下のように実行しています。
python -m pytest --cov=src --cov-branch --tb=short
また、カバレッジの取得、およびテストを並列実行して速く終わらせたい時には、以下のように実行しています。
python -m pytest -n auto --cov=src --cov-branch --cov-report=html --tb=short
この他にもPytestを便利にする拡張ライブラリ的なものはあると思いますので、Pythonコードのテストをより迅速に行っていきたいですね。
4. 現在実行している関数の名前を取得する。
ログを出力する際に、どの処理で出力されたログなのかを確認したいです。ログの冒頭に関数名を書いてもいいですが、既に書いた関数からコピペする際に、その冒頭に書いてある関数名を更新し忘れることも起こり得ます。
その抜け漏れを防止するために、「inspect」ライブラリを呼び出して実行中の関数名をログに出力するようにします。inspectはPythonのビルトインライブラリです。
import inspect
func_name = inspect.currentframe().f_code.co_name
pprint(f"{func_name}: start processing into PostgreSQL...")
5. メモリの使用量を把握する。
クラウド上にサーバレス関数をデプロイする際に、その関数がどれぐらいのメモリを使用するのかは把握しておく必要があります。その際に、関数内の各処理におけるメモリ使用率が確認できる「memory_profiler」のライブラリを使います。
pip install memory-profiler
例えば、こんな感じで使います。
from memory_profiler import profile
with open("./memory_profiler.log", "w") as f:
profile(execute, stream=f)()
6. 標準出力をファイルに残す。
個人的なツールを作っていても、処理の内容が煩雑になってくると、テストコードを書きたくなってきます。
特に、APIを叩いてJSONを受け取ってどーのこーのする処理では、やはりテストコードを書いて、正常な挙動を担保するのと、実際にどんな動きになっているのかを確認できるようにしたいです。レコードの数も膨大になってくれば、スクリプトの挙動を個所で確認できるようにしたい。
そこで、処理の途中の標準出力をテキストファイルに出力できるようにして、テストデータを楽に作れるようにします。標準出力をテキストファイルに出力する処理って、以前は作っていなかったんですよね。
そこでPythonの標準出力先を変える設定に関して少し調べました。そしたら、少し気を付けた方が良い点が見つかりました。
Pythonの標準出力をテキストファイルに出力する時に、sys.stdout
を避難させておくと、コンソールに出力できなくなる事故を防げます。
outputs_stdout_to_file = True
STDOUT = sys.stdout
try:
if outputs_stdout_to_file:
sys.stdout = open("./tmp_log.txt", "w")
with open("./memory_profiler.log", "w") as f:
profile(execute, stream=f)()
except Exception as e:
raise e
finally:
sys.stdout = STDOUT
普通は、sys.stdout = sys.stdout
で戻せるらしくて、環境起因の稀な事象らしいんですけど、自分がもしもそんな事故に引っ掛かってしまったら面倒なので、知っておいて良かったです。
7. NemotronでDocstringを作る。
PythonのDocstringも書いていきたくて、Sphinxで利用できる「reStructuredTextスタイル」で書きたいんですけど、色々な大規模言語モデルにそのスタイルでDocstringを書くように頼んでも全然書いてくれないんですよね。
そこで、NVIDIAが作ったLlama-3.1ベースの大規模言語モデルである「Nemorton-70B-Instruct」にreStructuredTextスタイルでPythonのDocstringを書かせたら、今までの中で最も良い感じです。
しかし、内容物が長過ぎるのが欠点かも。まあ、LLMをトレーニングする合成データの生成のために作られたAIなので、少しおしゃべり好きな部分があるのかもしれません。
Add Docstring with reStructuredText style into following Python script. Write it over here too.
"""
Execute Database Operations on a PostgreSQL Database
This function executes a given SQL query on a PostgreSQL database engine.
It optionally accepts a list of records to be inserted into the database
if the query is designed for insertion operations.
:param engine: A SQLAlchemy database engine instance connected to a PostgreSQL database.
:type engine: sqlalchemy.engine.base.Engine
:param query: The SQL query to be executed. For insert operations, the query should specify columns, e.g., ``INSERT INTO table (column1, column2) VALUES (:column1, :column2)``.
:type query: str
:param records: Optional list of lists containing data to be inserted. Each inner list represents a row, with elements corresponding to the columns specified in the query.
:type records: List[List[any]], optional (default is None)
:Example:
.. code-block:: python
from sqlalchemy import create_engine
from typing import List, Any
# Assuming 'sqlalchemy' and necessary dependencies are installed
engine = create_engine('postgresql://user:password@localhost/dbname')
# Example query for insertion with specified columns
insert_query = "INSERT INTO mytable (id, name) VALUES (:id, :name)"
# Sample records to insert
records_to_insert = [[1, 'John Doe'], [2, 'Jane Doe']]
execute_db_ops(engine, insert_query, records_to_insert)
# Execute a query without records (e.g., SELECT, UPDATE, DELETE)
select_query = "SELECT * FROM mytable"
execute_db_ops(engine, select_query)
"""
まとめ
今回は、Pythonで開発する時に導入してみて個人的に便利だった事柄をまとめて紹介しました。
上記のライブラリやファイルを導入して、快適に開発ができるようになりました。他にも開発が楽になるようなコツはないかなあ。
おしまい
もっともっと便利にしたいなあ。
開発を積み重ねていくしかないな。
以上になります!
コメント