多重构造函数:python方式?

多重构造函数:python方式?,python,constructor,initialization,initializer,idioms,Python,Constructor,Initialization,Initializer,Idioms,我有一个保存数据的容器类。创建容器时,有不同的方法传递数据 传递包含数据的文件 通过参数直接传递数据 不要传递数据;只需创建一个空容器 在Java中,我将创建三个构造函数。下面是在Python中实现的情况: class Container: def __init__(self): self.timestamp = 0 self.data = [] self.metadata = {} def __init__(self, fil

我有一个保存数据的容器类。创建容器时,有不同的方法传递数据

  • 传递包含数据的文件
  • 通过参数直接传递数据
  • 不要传递数据;只需创建一个空容器
  • 在Java中,我将创建三个构造函数。下面是在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中,我看到了三个显而易见的解决方案,但没有一个是漂亮的:

    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
    
    def __init__(self, file=None, timestamp=None, data=None, metadata=None):
        if file:
            ...
        elif timestamp and data and metadata:
            ...
        else:
            ... create empty container
    
    B:使用默认参数:

    def __init__(self, **kwargs):
        if 'file' in kwargs:
            ...
        elif 'timestamp' in kwargs and 'data' in kwargs and 'metadata' in kwargs:
            ...
        else:
            ... create empty container
    
    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,尤其是因为我必须检查是否提供了此方法所需的所有参数。如果代码要通过第四种方法进行扩展以添加数据,那么A比B灵活一点

    解决方案C似乎是最好的,但是用户必须知道他需要哪种方法。例如:如果他不知道什么是
    args
    ,他就不能做
    c=Container(args)


    什么是最具Python风格的解决方案?

    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中,从不将可变类型作为默认类型。⚠ 看


    此代码的系统目标是什么?从我的观点来看,您的关键短语是
    ,但用户必须知道他需要哪种方法。
    您希望用户对您的代码有什么体验?这将推动界面设计


    现在,转到可维护性:哪种解决方案最容易阅读和维护?同样,我觉得解决方案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的建议,有一种更好的方法可以避免对
    数据
    元数据
    进行全局声明,因此这是一种新方法:

    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风格的方法是确保任何可选参数都有默认值。因此,请包含您知道需要的所有参数,并为它们指定适当的默认值

    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
    

    大多数Pythonic都是Python标准库已经做过的事情。核心开发人员Raymond Hettinger(集合collectionsguy),以及如何编写类的一般指导原则


    使用单独的类级函数初始化实例,例如
    dict.fromkeys()
    不是类初始值设定项,但仍然返回
    dict
    的实例。这使您能够灵活地处理所需的参数,而无需随着需求的变化而更改方法签名。

    您不能有多个构造函数,但可以有多个适当命名的工厂方法

    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.
    

    但是,您不能轻易地阻止用户直接使用构造函数。(如果是关键性的,作为开发过程中的安全预防措施,您可以在构造函数中分析调用堆栈,并检查调用是否来自预期的方法之一。)

    如果您使用的是Python 3.4+,您可以使用decorator来做这件事(需要为其编写的
    methoddispatch
    decorator的一些额外帮助):


    相关:。还有其他选项。此外,我总是试图使我的代码符合我的需要,而不是围绕它们编写代码,以使我的代码更纯粹。虽然这里的所有答案都集中在提供一个解决方案上,但关于为什么函数重载在动态语言中没有意义。
    @classmethod
    会更简洁;这种方法很好,请永远不要在python中使用可变类型作为默认值。这是初学者需要在python中学习的第一个(也是少数几个)奇怪的边缘案例之一。试着做
    x=F();x、 数据。追加(5);y=F();打印y.数据
    。你会有惊喜的。惯用的方法是默认为
    None
    ,并在条件运算符或三元运算符中分配给
    self.data
    self.metadata
    。Johannes,如果我错了,其他人可以纠正我(对Python还是新手),但我认为这是因为继承。假设一个新类,
    G
    ,继承类
    F
    。使用
    @classmethod
    ,调用
    G.from_file
    给出了
    G
    的一个实例。使用
    @staticmethod
    ,类名被硬编码到方法中,因此
    G.from_file
    将给出
    F
    的一个实例,除非
    G
    重写该方法。@标记,然后如果有人用一个他们打算与其他人共享的空dict调用构造函数,它将被一个新的空dict替换?这可能会导致一些令人讨厌的头颅破坏者:
    my_dict={};f=f(元数据=my_dict);我的字典[1]=2;f、 元数据=>{}
    。这里,
    f.metadata
    当然应该是
    {1:2}
    @Mark您的评论引发了另一个问题:这里有一个微妙的bug。由于参数列表是在首次创建函数时计算的,因此数据和元数据的列表和dict将有效地作为全局变量。不过基本上是合理的,除了那一点,明白了