データを保持するコンテナクラスがあります。コンテナが作成されると、データを渡すためのさまざまな方法があります。
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なソリューションは何ですか?
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 を参照してください。
複数のコンストラクターを持つことはできませんが、複数の適切な名前のファクトリーメソッドを持つことができます。
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つから呼び出しが行われたことを確認できます。)
ほとんどのPythonicは、Python標準ライブラリがすでに実行しているものです。コア開発者Raymond Hettinger(collections
guy) これについて話してください 、およびクラスの作成方法に関する一般的なガイドライン。
dict.fromkeys()
がクラス初期化子ではなく、dict
のインスタンスを返すように、個別のクラスレベル関数を使用してインスタンスを初期化します。これにより、要件の変更に応じてメソッドシグネチャを変更することなく、必要な引数に柔軟に対応できます。
このコードのシステム目標は何ですか?私の観点から、あなたの重要なフレーズはbut the user has to know which method he requires.
です。あなたのコードでユーザーにどんな経験をしてもらいたいですか?それがインターフェース設計を促進するはずです。
次に、保守性に移行します。どのソリューションが最も読みやすく、保守しやすいでしょうか?繰り返しますが、ソリューションCは劣っていると感じています。私が一緒に仕事をしたほとんどのチームにとって、ソリューションBはAよりも望ましい方法です。どちらも簡単に読んで理解できますが、どちらも治療のために小さなコードブロックに簡単に分割できます。
私は正しく理解したかどうかはわかりませんが、これはうまくいかないでしょうか?
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 {}
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
最も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