Multiple constructors: the Pythonic way?
我有一个保存数据的容器类。创建容器时,有不同的方法来传递数据。
在爪哇,我将创建三个构造函数。如果可以在python中使用,它的外观如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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中,我看到了三个明显的解决方案,但没有一个是漂亮的:
A:使用关键字参数:
1 2 3 4 5 6 7 | 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:使用默认参数:
1 2 3 4 5 6 7 | def __init__(self, file=None, timestamp=None, data=None, metadata=None): if file: ... elif timestamp and data and metadata: ... else: ... create empty container |
C:只提供构造函数来创建空容器。提供用不同来源的数据填充容器的方法。
1 2 3 4 5 6 7 8 9 10 | 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,特别是因为我必须检查是否提供了此方法所需的所有参数。如果用第四种方法扩展代码来添加数据,A比B更灵活。
解决方案C似乎是最好的,但用户必须知道他需要哪种方法。例如:如果他不知道
最能解决Python问题的方法是什么?
在
使用默认参数或
可以使用
我建议你这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
? Never have mutable types as defaults in python. ?
See here.
不能有多个构造函数,但可以有多个适当命名的工厂方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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. |
但是,您不能轻易地阻止用户直接使用构造函数。(如果这很关键,作为开发过程中的安全预防措施,您可以分析构造函数中的调用堆栈,并检查调用是否来自预期的方法之一。)
大多数pythonic都将是python标准库已经做的事情。核心开发人员RaymondHettinger(
使用单独的类级函数初始化实例,比如
我不确定我是否理解正确,但这不管用吗?
1 2 3 4 5 6 7 | def __init__(self, file=None, timestamp=0, data=[], metadata={}): if file: ... else: self.timestamp = timestamp self.data = data self.metadata = metadata |
或者你可以这样做:
1 2 3 4 5 6 7 8 | 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的建议,有一种更好的方法可以避免关于
1 2 3 4 5 6 7 8 9 | 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 {} |
此代码的系统目标是什么?从我的观点来看,您的关键短语是
现在,转向可维护性:哪个解决方案最容易阅读和维护?再次,我觉得解决方案C是劣质的。对于大多数和我一起工作的团队来说,解决方案B比A更可取:虽然两者都很容易分解成小代码块进行治疗,但阅读和理解起来要容易一些。
如果您使用的是python 3.4+,则可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
最简单的方法是确保任何可选参数都有默认值。因此,包括您知道需要的所有参数,并为它们分配适当的默认值。
1 2 | def __init__(self, timestamp=None, data=[], metadata={}): timestamp = time.now() |
需要记住的一件重要的事情是,任何必需的参数都不应该有默认值,因为您希望在不包括这些参数的情况下引发错误。
您可以使用参数列表末尾的
1 2 3 | def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards): if 'something' in kwargs: # do something |