Act 05. Pythonの関数とモジュール
はじめに
Act 01. AIで外国為替を自動売買するまでの道のりをベースに学習を進めて行く。
今回はPythonの関数とモジュールについて書こうと思う。
以前自分で書いた記事「Act 02. WindowsでPythonの環境構築とバージョン管理を行う
」の環境で試す。
Pythonのバージョンは3.12.7
関数とモジュールの違い
まずは一応Pythonの関数とモジュールの違いについて。
関数
- 定義: 関数は、特定のタスクを実行するための再利用可能なコードのブロック
-
特徴:
- 入力(引数)を受け取り、処理を行い、結果(戻り値)を返す
- 他の関数やプログラムから呼び出して使用可能
- 同じ処理を何度も行う必要がある場合に便利
モジュール
-
定義: モジュールは、関連する関数、クラス、変数などをまとめたファイル(通常は
.py
拡張子) -
特徴:
- 複数の関数やクラスを含むことができ、特定の機能やライブラリを構造化して管理するために使用される
- 他のモジュールやプログラムからインポートして使用可能
- Python標準ライブラリやサードパーティのライブラリとしも利用可能
まとめ
- 関数は特定の処理を行うためのコードのブロックであり、モジュールはそのような関数を含むファイル。モジュールを使うことで、コードを整理し、再利用性を高めることができる。
関数
関数の定義
Pythonでは、def
キーワードを使って関数を定義する。基本的な構文は以下の通り。
def 関数名(引数1, 引数2, ...):
"""関数の説明(オプション)"""
処理内容
return 戻り値 # 戻り値はオプション
例
-
引数なし、戻り値なしの関数
この関数を呼び出すと、"Hello, World!"が出力される。def greet(): print("Hello, World!")
greet()
-
引数あり、戻り値ありの関数
この関数は二つの数を受け取り、その合計を返す。def add(a, b): return a + b
result = add(3, 5) print(result) # 8
-
デフォルト引数を持つ関数
引数が指定されない場合、"World"が使用される。def greet(name="World"): print(f"Hello, {name}!")
指定する場合と指定しない場合の出力は以下の通り。
greet() # Hello, World! greet("Alice") # Hello, Alice!
-
可変長引数を持つ関数
この関数は任意の数の引数を受け取り、その合計を返す。def summarize(*args): return sum(args)
例えばこんな感じ。
total = summarize(1, 2, 3, 4, 5) print(total) # 15
-
可変長のキーワード引数を持つ関数
この関数は任意の数のキーワード引数を受け取り、その内容を出力する。def print_info(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}")
例えばこんな感じ。
print_info(name="Alice", age=30, city="Tokyo") # name: Alice # age: 30 # city: Tokyo
**kwargs
とかって便利だけど、何が来るか分からないの怖すぎない・・?
関数の最初の方で型チェックとか何かしらすることが出来る。例えば以下のようなイメージ。def configure_settings(**kwargs): if "theme" not in kwargs or not isinstance(kwargs["theme"], str): print("theme does not exist or the type is not str type.") return False if "language" not in kwargs or not isinstance(kwargs["language"], str): print("language does not exist or the type is not str type.") return False # バリデーションチェックに成功したとき print("validation check successed!")
実行するとこんな感じ
configure_settings(theme="dark", language="Japanese") # validation check successed! configure_settings(theme=1, language="Japanese") # theme does not exist or the type is not str type. configure_settings(theme="dark") # language does not exist or the type is not str type.
いやいや、これって数が増えれば増えるほど面倒じゃない?と思ってしまった。
便利なライブラリ紹介
そんな時はdataclasses
モジュールやPydantic
モジュールが使えるかも。
dataclasses
まずはdataclasses
モジュールを使ってみる。
from dataclasses import dataclass
@dataclass
class Config:
theme: str
language: str
font_size: int
def apply_settings(config: Config):
print(f"Theme: {config.theme}, Language: {config.language}, Font Size: {config.font_size}")
# 使用例
config = Config(theme="dark", language="Japanese", font_size=12)
apply_settings(config) # Theme: dark, Language: Japanese, Font Size: 12
ちなみにこんな感じで補完される。(画像の下側に注目)
@dataclass
を取ると補完されない。(画像の下側に注目)
これなら結構便利かも。と思ったのもつかの間。
theme
をint型にしても正常に動いてしまった。
config = Config(theme=1, language="Japanese", font_size=12)
apply_settings(config) # Theme: 1, Language: Japanese, Font Size: 12
あくまで補完機能が追加されるだけでバリデーションチェックはしてないっぽい。
Pydantic
そんな時はPydantic
を使う。一般的な書き方は以下の通り。
※pip install pydantic
でインストールする必要あり
from pydantic import BaseModel
class Config(BaseModel):
theme: str
language: str
font_size: int
def apply_settings(config: Config):
print(f"Theme: {config.theme}, Language: {config.language}, Font Size: {config.font_size}")
# 使用例
config = Config(theme="dark", language="Japanese", font_size=12)
apply_settings(config) # Theme: dark, Language: Japanese, Font Size: 12
使用するConfigと関数は同様で不正なパターンも試してみる。
どちらもエラーが発生するためtry-except
を使うことになる。
-
引数が不足しているパターン
try: invalid_config = Config(theme="dark", language="Japanese") # font_sizeが欠けている except Exception as e: print(e)
実行結果はエラーが発生。どれが不足しているか分かるしいい感じ。
1 validation error for Config font_size Field required [type=missing, input_value={'theme': 'dark', 'language': 'Japanese'}, input_type=dict] For further information visit https://errors.pydantic.dev/2.9/v/missing
-
型が異なるパターン
try: invalid_config = Config(theme=1, language="Japanese", font_size=12) # themeの型が異なる except Exception as e: print(e)
実行結果はエラーが発生。こちらも問題なし。
1 validation error for Config theme Input should be a valid string [type=string_type, input_value=1, input_type=int] For further information visit https://errors.pydantic.dev/2.9/v/string_type
モジュール
次にモジュールについてだが、ここまで結構長かったので疲れた。
けど後少し頑張ろう。
まずは自作モジュールを作成する方法について。
結論、以下のようなフォルダ構成でOK。
.
├── Act05.py
└── my_package
├── __init__.py
├── another_module.py
└── my_module.py
フォルダ名やファイル名はなんでもOKだが、another_module.py
やmy_module.py
にdef
などで関数を定義する。
※__init__.py
はそのままの名前
モジュールを使用したいファイル内でfrom my_package import my_module
のような形式でインポートを行う。
__init__.py
は、Pythonのパッケージを示すための特別なファイル。このファイルが存在することで、そのディレクトリがパッケージとして扱われる。
__init__.py
は少し特殊で、以下のような役割がある。
※ついでにパッケージの使用方法も記載しておく
1. パッケージの定義
__init__.py
が存在するディレクトリは、Pythonにとってパッケージとして認識される。
このため、そのディレクトリ内のモジュールをインポートできるようになる。
2. 初期化処理
__init__.py
にコードを記述することで、パッケージがインポートされたときに実行される初期化処理を定義できる。たとえば、共通の設定や定数を初期化するのに使うことができる。
試してみる。
__init__.py
に以下を記述する。
print("my_packageがインポートされた")
my_module.py
に以下を記述する。
def hello():
print("hello world")
Act05.py
に以下を記述する。
from my_package import my_module
my_module.hello()
以下は出力
my_packageがインポートされた
hello world
3. 名前空間の管理
__init__.py
を使用することで、パッケージ内で公開するモジュールやクラスを制御できる。たとえば、特定のモジュールだけを外部に公開したい場合、__all__
リストを使うことができる。
__init__.py
に以下を記述する。
__all__ = ['my_module'] # my_moduleだけを公開
another_module.py
に以下を記述する。
def add(a: int, b: int):
"""
引数2つを加算する関数
"""
return a + b
Act05.py
に以下を記述する。
from my_package import my_module, another_module
my_module.hello()
add = another_module.add(10, 20)
print(add)
以下は出力
全然another_moduleのadd関数を使えるやないかい!
hello world
30
本当は使えない想定だった。
少し調べてみたらどうやらfrom my_package import *
でインポートしたときは使えなくなるらしい。
Act05.py
を以下の通り修正。
# from my_package import my_module, another_module
from my_package import *
my_module.hello()
add = another_module.add(10, 20)
print(add)
するとanother_moduleが参照できなくなった。めでたしめでたし。
4. 簡易インポート
__init__.py
を使って、パッケージ内のモジュールを簡単にインポートできるようにすることもできる。
from .my_module import hello
これにより、外部からは次のように呼び出せるようになる。
from my_package import hello
hello()
さいごに
個人的には**kwargs
が気に入らなかったから、dataclasses
とPydantic
の存在を知れたのはかなりアツかった。
やっぱ知ってるつもりになって勉強しないのはだめだね。反省。
Discussion