いちごとバナナを必要とするケーキがあるとします。このケーキに白菜まで渡すとエラーになります。
class Cake:
def __init__(self, ichigo, banana):
self.ichigo = ichigo
self.banana = banana
Cake(ichigo=1, banana=2) # OK
Cake(ichigo=1, banana=2, hakusai=4) # NG
# TypeError: Cake.__init__() got an unexpected keyword argument 'hakusai'
しかし、場合によっては渡す側から何が必要かわからないのでとにかく全部渡したいということもあると思います。その場合は、ケーキ側で必要なもの一覧 argnames をもっておき、必要でないものはフィルタしてからコンストラクタを呼ぶクラスメソッド create() を用意する手があると思います (以下)。クラスメソッドは基底クラスにさえ書いておけば派生クラスでも利用できます。
class Cake:
argnames = {'ichigo', 'banana'}
def __init__(self, ichigo, banana):
self.ichigo = ichigo
self.banana = banana
@classmethod
def create(cls, **kwargs):
filtered = {k: v for k, v in kwargs.items() if k in cls.argnames}
return cls(**filtered)
class ChocolateCake(Cake):
argnames = {'ichigo', 'banana', 'chocolate'}
def __init__(self, ichigo, banana, chocolate):
super().__init__(ichigo, banana)
self.chocolate = chocolate
Cake.create(ichigo=1, banana=2) # OK
Cake.create(ichigo=1, banana=2, hakusai=4) # OK
ChocolateCake.create(ichigo=1, banana=2, chocolate=3, hakusai=4) # OK
必要なもの一覧 argnames を自分で書きたくない場合は、初回の create() 時に標準ライブラリ inspcet でコンストラクタのシグネチャ (引数情報) を取得してセットする手があります (以下)。
import inspect
class Cake:
def __init__(self, ichigo, banana):
self.ichigo = ichigo
self.banana = banana
@classmethod
def create(cls, **kwargs):
# if not hasattr(cls, 'argnames'): # This is wrong.
if 'argnames' not in cls.__dict__:
cls.argnames = set(inspect.signature(cls.__init__).parameters.keys())
cls.argnames.remove('self')
filtered = {k: v for k, v in kwargs.items() if k in cls.argnames}
return cls(**filtered)
class ChocolateCake(Cake):
def __init__(self, ichigo, banana, chocolate):
super().__init__(ichigo, banana)
self.chocolate = chocolate
Cake.create(ichigo=1, banana=2) # OK
Cake.create(ichigo=1, banana=2, hakusai=4) # OK
ChocolateCake.create(ichigo=1, banana=2, chocolate=3, hakusai=4) # OK
このとき、argnames をセット済みかは cls.__dict__ で確認する必要があります。もし hasattr() で判定すると、チョコレートケーキ用の argnames がセット済みか確認するのに、基底クラスのケーキまで見にいってしまいます (ので、ケーキが先にインスタンス化された場合にチョコレートケーキ用の argnames をセットできなくなります)。cls.__dict__ を見ればチョコレートケーキ自体にセットされているかを確認できます。
参考文献
-
https://docs.python.org/3/reference/datamodel.html#custom-classes
-
type.__dict__について記述があります。
-