概要
Python でクラスの定義を関数で行ってみます
サンプルコードとメリットなどを紹介します
環境
- macOS 11.7.10
- Python 3.11.6
- pyright 1.1.311
基本的な使い方
クラスは type を使って定義します
コンストラクタや属性、メソッドはすべて dict 形式で指定します
メソッドの場合ラムダ式を使って定義することもできます
def init(self, name="hawk", age=10):
setattr(self, "name", name)
setattr(self, "age", age)
def show_with_newline(self):
print(self.name)
print(self.age)
User = type(
"User",
(object,),
{
# コンストラクタを定義する
"__init__": init,
# クラスの属性を定義する、今回はコンストラクタで属性の初期化をしているが以下のコメントを外せばここでも属性の初期値を設定することは可能
# "name": "",
# "age": 10,
# メソッドの定義 (ラムダでも定義できる
"show": lambda self: print(f"name -> {self.name}, age -> {self.age}"),
# メソッドの定義 (関数名を指定する
"show_with_newline": show_with_newline,
},
)
u = User()
u.show()
u = User("hawksnowlog", 20)
u.show()
u.show_with_newline()
デメリット
- pyright でエラーが発生する
- User クラスに show メソッドはありません
- User クラスのコンストラクタには引数を指定することはできませんなど
- 当然 pyright で補完などが発生しない
- コードの可読性が下がる
- 一般的なクラス定義の記述ではなくなる
- 後述する関数化などをした場合にはどんなクラスがいくつ作られているのかコードからは認識できなくなる
動的に作成したクラスを使ってタイプヒントを使う
こんな使い方もできます (この場合でも pyright のエラーがでます
def init(self, name="hawk", age=10):
setattr(self, "name", name)
setattr(self, "age", age)
def show_with_newline(self):
print(self.name)
print(self.age)
User = type(
"User",
(object,),
{
# コンストラクタを定義する
"__init__": init,
# クラスの属性を定義する、今回はコンストラクタで属性の初期化をしているが以下のコメントを外せばここでも属性の初期値を設定することは可能
# "name": "",
# "age": 10,
# メソッドの定義 (ラムダでも定義できる
"show": lambda self: print(f"name -> {self.name}, age -> {self.age}"),
# メソッドの定義 (関数名を指定する
"show_with_newline": show_with_newline,
},
)
def create_user() -> User:
return User()
user = create_user()
user.show()
関数化する
クラス定義を生成する関数を定義します
こうすることでいろいろなクラスを動的に生成することができます
この場合は pyright で「Illegal type annotation: variable not allowed unless it is a type alias」が発生します
from typing import Any, Callable
def init(self, name="hawk", age=10):
setattr(self, "name", name)
setattr(self, "age", age)
def show_with_newline(self):
print(self.name)
print(self.age)
def factory_class(
class_name: str, attributes: dict[str, Any], funcs: dict[str, Callable]
) -> type:
return type(
class_name,
(object,),
dict(**attributes, **funcs),
)
User = factory_class(
"User", {"name": "hawk", "age": 10}, {"__init__": init, "show": show_with_newline}
)
def create_user() -> User:
return User()
user = create_user()
user.show()
最後に
Python で動的にクラスを生成する方法を紹介しました
クラスの定義を dict などで保持することができるのでデータドリブンなプログラミングができるようになります
ただメタプログラミングなのでコードの可読性や lsp との相性が悪くなるのでそのあたりのトレードオフを考慮する必要がありそうです
0 件のコメント:
コメントを投稿