これに対する明確な答えは見つかりません。残念ながら、Pythonクラスに複数の__init__
関数を含めることはできません。では、どうすればこの問題を解決できますか?
number_of_holes
プロパティを持つCheese
というクラスがあるとします。チーズオブジェクトを作成する方法は2つあります。
parmesan = Cheese(num_holes = 15)
number_of_holes
プロパティをランダム化するだけのものです。gouda = Cheese()
これを行うには1つの方法しか考えられませんが、それはちょっと不格好です。
class Cheese():
def __init__(self, num_holes = 0):
if (num_holes == 0):
# randomize number_of_holes
else:
number_of_holes = num_holes
あなたは何を言っていますか?別の方法はありますか?
実際にはNone
は "魔法"の値の方がはるかに優れています。
class Cheese():
def __init__(self, num_holes = None):
if num_holes is None:
...
あなたがより多くのパラメータを追加することの完全な自由を望むなら今、:
class Cheese():
def __init__(self, *args, **kwargs):
#args -- Tuple of anonymous arguments
#kwargs -- dictionary of named arguments
self.num_holes = kwargs.get('num_holes',random_holes())
*args
と**kwargs
の概念をよりよく説明するために(実際にこれらの名前を変更することができます):
def f(*args, **kwargs):
print 'args: ', args, ' kwargs: ', kwargs
>>> f('a')
args: ('a',) kwargs: {}
>>> f(ar='a')
args: () kwargs: {'ar': 'a'}
>>> f(1,2,param=3)
args: (1, 2) kwargs: {'param': 3}
num_holes=None
だけを使用するのであれば、デフォルトとして__init__
を使用しても問題ありません。
複数の独立した「コンストラクタ」が必要な場合は、これらをクラスメソッドとして提供できます。これらは通常ファクトリメソッドと呼ばれます。この場合、num_holes
のデフォルトを0
にすることができます。
class Cheese(object):
def __init__(self, num_holes=0):
"defaults to a solid cheese"
self.number_of_holes = num_holes
@classmethod
def random(cls):
return cls(randint(0, 100))
@classmethod
def slightly_holey(cls):
return cls(randint(0, 33))
@classmethod
def very_holey(cls):
return cls(randint(66, 100))
今このようなオブジェクトを作成します。
gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()
オプションのパラメータを使用したい場合、これらすべての答えは優れていますが、もう1つのPythonicの可能性は、クラスメソッドを使用してファクトリスタイルの擬似コンストラクタを生成することです。
def __init__(self, num_holes):
# do stuff with the number
@classmethod
def fromRandom(cls):
return cls( # some-random-number )
なぜあなたの解決策は「不格好」だと思いますか?個人的には、あなたのような状況では複数のオーバーロードされたコンストラクタよりもデフォルト値を持つ1つのコンストラクタを好むでしょう(Pythonはとにかくメソッドオーバーロードをサポートしません):
def __init__(self, num_holes=None):
if num_holes is None:
# Construct a gouda
else:
# custom cheese
# common initialization
たくさんの異なるコンストラクタを持つ非常に複雑なケースでは、代わりに異なるファクトリ関数を使うほうがきれいかもしれません:
@classmethod
def create_gouda(cls):
c = Cheese()
# ...
return c
@classmethod
def create_cheddar(cls):
# ...
あなたのチーズの例では、チーズのゴーダサブクラスを使用したいと思うかもしれませんが...
これらはあなたの実装には良い考えですが、もしあなたがチーズ作りのインターフェースをユーザーに提示しているのであれば。彼らは、チーズにいくつの穴があるのか、あるいはどのような内部構造がチーズを作るのに使われるのかを気にしていません。あなたのコードのユーザーはただ "gouda"か "parmesean"が欲しいですか?
それでは、なぜこれをしないでください。
# cheese_user.py
from cheeses import make_gouda, make_parmesean
gouda = make_gouda()
paremesean = make_parmesean()
そして、あなたは実際に関数を実装するために上記の方法のどれでも使うことができます:
# cheeses.py
class Cheese(object):
def __init__(self, *args, **kwargs):
#args -- Tuple of anonymous arguments
#kwargs -- dictionary of named arguments
self.num_holes = kwargs.get('num_holes',random_holes())
def make_gouda():
return Cheese()
def make_paremesean():
return Cheese(num_holes=15)
これは優れたカプセル化手法であり、私はそれがもっとPythonicだと思います。私には、このようなやり方が、アヒルの型付けとより一致しています。あなたは単にゴーダオブジェクトを求めているだけで、それがどんなクラスであるかはあまり気にしません。
すでに投稿されている解決策を間違いなく好むべきですが、まだこの解決策を述べていないので、完全を期すために言及する価値があると思います。
@classmethod
アプローチは、デフォルトのコンストラクタ(__init__
)を呼び出さない代替のコンストラクタを提供するように修正できます。代わりに、インスタンスは__new__
を使用して作成されます。
これは、初期化のタイプがコンストラクタ引数のタイプに基づいて選択できず、コンストラクタがコードを共有しない場合に使用できます。
例:
class MyClass(set):
def __init__(self, filename):
self._value = load_from_file(filename)
@classmethod
def from_somewhere(cls, somename):
obj = cls.__new__(cls) # Does not call __init__
obj._value = load_from_somewhere(somename)
return obj
最も良い答えはデフォルト引数についての上記のものですが、私はこれを書くのを楽しんでいました、そしてそれは確かに「複数のコンストラクタ」の法案に合います。自己責任。
new メソッドについてはどうでしょうか。
"典型的な実装では、super(currentclass、cls). new (cls [、...])を適切に指定してスーパークラスの new ()メソッドを呼び出すことで、クラスの新しいインスタンスを作成します。引数を取得し、必要に応じて新しく作成したインスタンスを変更してから返します。」
そのため、適切なコンストラクタメソッドを追加することで、 new メソッドにクラス定義を変更させることができます。
class Cheese(object):
def __new__(cls, *args, **kwargs):
obj = super(Cheese, cls).__new__(cls)
num_holes = kwargs.get('num_holes', random_holes())
if num_holes == 0:
cls.__init__ = cls.foomethod
else:
cls.__init__ = cls.barmethod
return obj
def foomethod(self, *args, **kwargs):
print "foomethod called as __init__ for Cheese"
def barmethod(self, *args, **kwargs):
print "barmethod called as __init__ for Cheese"
if __== "__main__":
parm = Cheese(num_holes=5)
代わりにデフォルトとしてnum_holes=None
を使用してください。それからnum_holes is None
かどうかをチェックし、もしそうなら、ランダム化します。とにかく、それが私の一般的な見方です。
より根本的に異なる構築方法は、cls
のインスタンスを返すクラスメソッドを保証するかもしれません。
継承を使います。穴の数よりも多くの違いがあるとしている場合は特に。特にゴーダがパルメザンと異なるメンバーのセットを持つ必要があるならば。
class Gouda(Cheese):
def __init__(self):
super(Gouda).__init__(num_holes=10)
class Parmesan(Cheese):
def __init__(self):
super(Parmesan).__init__(num_holes=15)
私の最初の答え が批判されたので 根拠に基づいて 私の特別な目的のコンストラクターは(ユニークな)デフォルトのコンストラクターを呼び出さなかったので、私はここに、すべてのコンストラクターはデフォルトのコンストラクターを呼び出します。
class Cheese:
def __init__(self, *args, _initialiser="_default_init", **kwargs):
"""A multi-initialiser.
"""
getattr(self, _initialiser)(*args, **kwargs)
def _default_init(self, ...):
"""A user-friendly smart or general-purpose initialiser.
"""
...
def _init_parmesan(self, ...):
"""A special initialiser for Parmesan cheese.
"""
...
def _init_gouda(self, ...):
"""A special initialiser for Gouda cheese.
"""
...
@classmethod
def make_parmesan(cls, *args, **kwargs):
return cls(*args, **kwargs, _initialiser="_init_parmesan")
@classmethod
def make_gouda(cls, *args, **kwargs):
return cls(*args, **kwargs, _initialiser="_init_gouda")
class Cheese:
def __init__(self, *args, **kwargs):
"""A user-friendly initialiser for the general-purpose constructor.
"""
...
def _init_parmesan(self, *args, **kwargs):
"""A special initialiser for Parmesan cheese.
"""
...
def _init_gauda(self, *args, **kwargs):
"""A special initialiser for Gauda cheese.
"""
...
@classmethod
def make_parmesan(cls, *args, **kwargs):
new = cls.__new__(cls)
new._init_parmesan(*args, **kwargs)
return new
@classmethod
def make_gauda(cls, *args, **kwargs):
new = cls.__new__(cls)
new._init_gauda(*args, **kwargs)
return new
これが私が作成しなければならなかったYearQuarter
クラスのためにそれを解決した方法です。私はvalue
と呼ばれる単一のパラメータで__init__
を作成しました。 __init__
のコードはvalue
パラメータがどんな型であるかを決定し、それに応じてデータを処理します。複数の入力パラメータが必要な場合は、それらを単一のTupleにまとめて、value
がTupleであることをテストするだけです。
あなたはこのようにそれを使います:
>>> temp = YearQuarter(datetime.date(2017, 1, 18))
>>> print temp
2017-Q1
>>> temp = YearQuarter((2017, 1))
>>> print temp
2017-Q1
これが__init__
とそれ以外のクラスの外観です。
import datetime
class YearQuarter:
def __init__(self, value):
if type(value) is datetime.date:
self._year = value.year
self._quarter = (value.month + 2) / 3
Elif type(value) is Tuple:
self._year = int(value[0])
self._quarter = int(value[1])
def __str__(self):
return '{0}-Q{1}'.format(self._year, self._quarter)
もちろん__init__
を複数のエラーメッセージで拡張することができます。この例では省略しました。
これは私が推測してトリッキーなかなりきれいな方法です
class A(object):
def __init__(self,e,f,g):
self.__dict__.update({k: v for k,v in locals().items() if k!='self'})
def bc(self):
print(self.f)
k=A(e=5,f=6,g=12)
k.bc() # >>>6