web-dev-qa-db-ja.com

Pythonで複数のコンストラクタを持つためのきれいでPythonicな方法は何ですか?

これに対する明確な答えは見つかりません。残念ながら、Pythonクラスに複数の__init__関数を含めることはできません。では、どうすればこの問題を解決できますか?

number_of_holesプロパティを持つCheeseというクラスがあるとします。チーズオブジェクトを作成する方法は2つあります。

  1. このような穴がいくつかあります:parmesan = Cheese(num_holes = 15)
  2. 引数を取らず、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

あなたは何を言っていますか?別の方法はありますか?

611
winsmith

実際には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}

http://docs.python.org/reference/expressions.html#calls

738
vartec

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()
577
Ber

オプションのパラメータを使用したい場合、これらすべての答えは優れていますが、もう1つのPythonicの可能性は、クラスメソッドを使用してファクトリスタイルの擬似コンストラクタを生成することです。

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )
21

なぜあなたの解決策は「不格好」だと思いますか?個人的には、あなたのような状況では複数のオーバーロードされたコンストラクタよりもデフォルト値を持つ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):
    # ...

あなたのチーズの例では、チーズのゴーダサブクラスを使用したいと思うかもしれませんが...

17
Ferdinand Beyer

これらはあなたの実装には良い考えですが、もしあなたがチーズ作りのインターフェースをユーザーに提示しているのであれば。彼らは、チーズにいくつの穴があるのか​​、あるいはどのような内部構造がチーズを作るのに使われるのかを気にしていません。あなたのコードのユーザーはただ "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だと思います。私には、このようなやり方が、アヒルの型付けとより一致しています。あなたは単にゴーダオブジェクトを求めているだけで、それがどんなクラスであるかはあまり気にしません。

17
Brad C

すでに投稿されている解決策を間違いなく好むべきですが、まだこの解決策を述べていないので、完全を期すために言及する価値があると思います。

@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
13

最も良い答えはデフォルト引数についての上記のものですが、私はこれを書くのを楽しんでいました、そしてそれは確かに「複数のコンストラクタ」の法案に合います。自己責任。

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)
9
mluebke

代わりにデフォルトとしてnum_holes=Noneを使用してください。それからnum_holes is Noneかどうかをチェックし、もしそうなら、ランダム化します。とにかく、それが私の一般的な見方です。

より根本的に異なる構築方法は、clsのインスタンスを返すクラスメソッドを保証するかもしれません。

8

継承を使います。穴の数よりも多くの違いがあるとしている場合は特に。特にゴーダがパルメザンと異なるメンバーのセットを持つ必要があるならば。

class Gouda(Cheese):
    def __init__(self):
        super(Gouda).__init__(num_holes=10)


class Parmesan(Cheese):
    def __init__(self):
        super(Parmesan).__init__(num_holes=15) 
2
Michel Samia

私の最初の答え が批判されたので 根拠に基づいて 私の特別な目的のコンストラクターは(ユニークな)デフォルトのコンストラクターを呼び出さなかったので、私はここに、すべてのコンストラクターはデフォルトのコンストラクターを呼び出します。

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")
0
Alexey
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
0
Alexey

これが私が作成しなければならなかった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__を複数のエラーメッセージで拡張することができます。この例では省略しました。

0
Elmex80s

これは私が推測してトリッキーなかなりきれいな方法です

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
0
Amin Pial