多重构造函数:Pythonic的方式?
我有一个持有数据的容器类。 当容器被创build时,有不同的方法来传递数据。
- 传递一个包含数据的文件
- 直接通过parameter passing数据
- 不要传递数据; 只需创build一个空容器
在Java中,我会创build三个构造函数。 下面是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中,我看到了三个明显的解决scheme,但都不是很漂亮:
答 :使用关键字参数:
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 :只提供构造函数来创build空容器。 提供方法来填充来自不同来源的数据的容器。
def __init__(self): self.timestamp = 0 self.data = [] self.metadata = {} def add_data_from_file(file): ... def add_data(timestamp, data, metadata): ...
解决schemeA和B基本相同。 我不喜欢做if / else,尤其是因为我必须检查是否提供了此方法所需的所有参数。 如果代码被第四种方法扩展来添加数据,则A比B更灵活。
解决schemeC似乎是最好的,但用户必须知道他需要哪种方法。 例如:如果他不知道args
是什么,他不能做c = Container(args)
。
什么是最Python的解决scheme?
在Python
不能有多个同名的方法。 函数重载 – 不像在Java
– 不支持。
使用默认参数或者**kwargs
和*args
参数。
您可以使用@staticmethod
或@classmethod
修饰器来创build静态方法或类方法,以返回您的类的实例,或添加其他构造函数。
我build议你做:
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中永远不要有可变types的默认值。 ⚠看到这里 。
你不能有多个构造函数,但是你可以有多个适当命名的工厂方法。
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标准库已经做的。 核心开发者Raymond Hettinger( collections
) 就此进行了一次演讲 ,以及如何编写类的一般指导。
使用单独的类级函数来初始化实例,比如dict.fromkeys()
不是类初始化程序,但仍然返回dict
的实例。 这使您可以灵活地处理所需的参数,而不会随着需求更改而更改方法签名。
这个代码的系统目标是什么? 从我的angular度来看,你的关键词是, but the user has to know which method he requires.
你想让你的用户使用你的代码有什么经验? 这应该推动界面devise。
现在,转向可维护性:哪种解决scheme最容易阅读和维护? 再次,我觉得解决schemeC是低劣的。 对于我工作过的大多数团队来说,解决schemeB比A更可取:虽然这两种解决scheme都易于分解成小代码块进行处理,但它更容易阅读和理解。
我不确定我是否理解正确,但是不行?
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 Kiparskybuild议他们更好地避免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
最pythonic的方法是确保任何可选参数具有默认值。 因此,包括您知道您需要的所有参数并为其分配适当的默认值。
def __init__(self, timestamp=None, data=[], metadata={}): timestamp = time.now()
一个重要的事情要记住的是,任何必需的参数不应该有默认值,因为如果不包含它们,你想要引发一个错误。
你可以在参数列表的末尾使用*args
和**kwargs
来接受更多的可选参数。
def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards): if 'something' in kwargs: # do something