web-dev-qa-db-ja.com

複数のコンストラクター:Pythonicの方法?

データを保持するコンテナクラスがあります。コンテナが作成されると、データを渡すためのさまざまな方法があります。

  1. データを含むファイルを渡す
  2. 引数を介してデータを直接渡す
  3. データを渡さないでください。空のコンテナを作成するだけです

Javaでは、3つのコンストラクターを作成します。 Pythonで可能な場合は、次のようになります。

class Container:

    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

Pythonには、3つの明らかな解決策がありますが、どれもきれいではありません。

A:キーワード引数の使用:

def __init__(self, **kwargs):
    if 'file' in kwargs:
        ...
    Elif 'timestamp' in kwargs and 'data' in kwargs and 'metadata' in kwargs:
        ...
    else:
        ... create empty container

B:デフォルト引数の使用:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        ...
    Elif timestamp and data and metadata:
        ...
    else:
        ... create empty container

C:空のコンテナーを作成するためのコンストラクターのみを提供します。さまざまなソースからのデータでコンテナを埋めるメソッドを提供します。

def __init__(self):
    self.timestamp = 0
    self.data = []
    self.metadata = {}

def add_data_from_file(file):
    ...

def add_data(timestamp, data, metadata):
    ...

ソリューションAとBは基本的に同じです。特に、このメソッドに必要なすべての引数が提供されているかどうかを確認する必要があるため、if/elseを実行するのは好きではありません。コードをデータを追加する4番目の方法で拡張する場合、AはBよりも少し柔軟です。

ソリューションCは最も良いように見えますが、ユーザーはどのメソッドが必要かを知っている必要があります。例:argsが何であるかわからない場合、彼はc = Container(args)を実行できません。

最もPythonicなソリューションは何ですか?

58
Johannes

Pythonに同じ名前のメソッドを複数含めることはできません。関数のオーバーロード-Javaとは異なり-はサポートされていません。

デフォルトのパラメーターまたは**kwargsおよび*args引数を使用します。

@staticmethodまたは@classmethodデコレータを使用して静的メソッドまたはクラスメソッドを作成し、クラスのインスタンスを返すか、他のコンストラクタを追加できます。

以下を行うことをお勧めします。

class F:

    def __init__(self, timestamp=0, data=None, metadata=None):
        self.timestamp = timestamp
        self.data = list() if data is None else data
        self.metadata = dict() if metadata is None else metadata

    @classmethod
    def from_file(cls, path):
       _file = cls.get_file(path)
       timestamp = _file.get_timestamp()
       data = _file.get_data()
       metadata = _file.get_metadata()       
       return cls(timestamp, data, metadata)

    @classmethod
    def from_metadata(cls, timestamp, data, metadata):
        return cls(timestamp, data, metadata)

    @staticmethod
    def get_file(path):
        # ...
        pass

pythonでデフォルトとして可変型を使用しないでください。 - here を参照してください。

77
glegoux

複数のコンストラクターを持つことはできませんが、複数の適切な名前のファクトリーメソッドを持つことができます。

class Document(object):

    def __init__(self, whatever args you need):
        """Do not invoke directly. Use from_NNN methods."""
        # Implementation is likely a mix of A and B approaches. 

    @classmethod
    def from_string(cls, string):
        # Do any necessary preparations, use the `string`
        return cls(...)

    @classmethod
    def from_json_file(cls, file_object):
        # Read and interpret the file as you want
        return cls(...)

    @classmethod
    def from_docx_file(cls, file_object):
        # Read and interpret the file as you want, differently.
        return cls(...)

    # etc.

ただし、ユーザーがコンストラクタを直接使用することを簡単に防ぐことはできません。 (開発中の安全上の予防措置として重要な場合は、コンストラクターで呼び出しスタックを分析し、予想されるメソッドの1つから呼び出しが行われたことを確認できます。)

26
9000

ほとんどのPythonicは、Python標準ライブラリがすでに実行しているものです。コア開発者Raymond Hettinger(collections guy) これについて話してください 、およびクラスの作成方法に関する一般的なガイドライン。

dict.fromkeys()がクラス初期化子ではなく、dictのインスタンスを返すように、個別のクラスレベル関数を使用してインスタンスを初期化します。これにより、要件の変更に応じてメソッドシグネチャを変更することなく、必要な引数に柔軟に対応できます。

16
Arya McCarthy

このコードのシステム目標は何ですか?私の観点から、あなたの重要なフレーズはbut the user has to know which method he requires.です。あなたのコードでユーザーにどんな経験をしてもらいたいですか?それがインターフェース設計を促進するはずです。

次に、保守性に移行します。どのソリューションが最も読みやすく、保守しやすいでしょうか?繰り返しますが、ソリューションCは劣っていると感じています。私が一緒に仕事をしたほとんどのチームにとって、ソリューションBはAよりも望ましい方法です。どちらも簡単に読んで理解できますが、どちらも治療のために小さなコードブロックに簡単に分割できます。

5
Prune

私は正しく理解したかどうかはわかりませんが、これはうまくいかないでしょうか?

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        ...
    else:
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

または、次のこともできます。

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        # Implement get_data to return all the stuff as a Tuple
        timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp
    self.data = data
    self.metadata = metadata

Jon Kiparskyのアドバイスに感謝します。dataおよびmetadataのグローバル宣言を回避するより良い方法があるので、これが新しい方法です。

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        # Implement get_data to return all the stuff as a Tuple
        with open(file) as f:
            timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp or 0
    self.data = data or []
    self.metadata = metadata or {}
4
Gabriel Ecker

Python 3.4+を使用している場合は、 functools.singledispatch デコレーターを使用してこれを行うことができます( @ ZeroPiraeus が書いたmethoddispatchデコレーターから少し助けを借りて) 彼の答え ):

class Container:

    @methoddispatch
    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    @__init__.register(File)
    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    @__init__.register(Timestamp)
    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata
3
Sean Vieira

最もPython的な方法は、オプションの引数にデフォルト値があることを確認することです。したがって、必要であることがわかっているすべての引数を含め、適切なデフォルトを割り当てます。

def __init__(self, timestamp=None, data=[], metadata={}):
    timestamp = time.now()

覚えておくべき重要なことは、必要な引数はnotにデフォルトが含まれている必要があるということです。これらが含まれていない場合はエラーを発生させたいからです。

引数リストの最後で*argsおよび**kwargsを使用して、さらにオプションの引数を受け入れることができます。

def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards):
    if 'something' in kwargs:
        # do something
0
Soviut