多重构造函数:勾股定理?(复制)

时间:2022-03-16 22:08:24

This question already has an answer here:

这个问题已经有了答案:

I have a container class that holds data. When the container is created, there are different methods to pass data.

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

  1. Pass a file which contains the data
  2. 传递包含数据的文件
  3. Pass the data directly via arguments
  4. 通过参数直接传递数据
  5. Don't pass data; just create an empty container
  6. 不要传递数据;只需创建一个空容器

In Java, I would create three constructors. Here's how it would look like if it were possible in Python:

在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

In Python, I see three obvious solutions, but none of them is pretty:

在Python中,我看到了三个明显的解决方案,但是没有一个是漂亮的:

A: Using keyword arguments:

答:使用关键字参数:

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: Using default arguments:

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: Only provide constructor to create empty containers. Provide methods to fill containers with data from different sources.

C:只提供构造函数来创建空容器。提供方法将来自不同来源的数据填充到容器中。

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

def add_data_from_file(file):
    ...

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

Solutions A and B are basically the same. I don't like doing the if/else, especially since I have to check if all arguments required for this method were provided. A is a bit more flexible than B if the code is ever to be extended by a fourth method to add data.

解A和B基本上是一样的。我不喜欢做if/else,特别是因为我必须检查是否提供了该方法所需的所有参数。如果用第四种方法扩展代码以添加数据,A比B灵活一些。

Solution C seems to be the nicest, but the user has to know which method he requires. For example: he cant do c = Container(args) if he doesn't know what args is.

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

Whats the most Pythonic solution?

最python化的解是什么?

7 个解决方案

#1


69  

You can't have multiple methods with same name in Python. Function overloading - unlike in Java - isn't supported.

在Python中不能有多个名称相同的方法。与Java不同的是,不支持函数重载。

Use default parameters or **kwargs and *args arguments.

使用默认参数或**kwargs和*args参数。

You can make static methods or class methods with the @staticmethod or @classmethod decorator to return an instance of your class, or to add other constructors.

可以使用@staticmethod或@classmethod decorator创建静态方法或类方法,以返回类的实例,或者添加其他构造函数。

I advise you to do:

我建议你这样做:

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.

在python中⚠从来没有可变类型默认值。⚠看到。

#2


25  

You can't have multiple constructors, but you can have multiple aptly-named factory methods.

不能有多个构造函数,但可以有多个名称合适的工厂方法。

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.

You can't easily prevent the user from using the constructor directly, though. (If it is critical, as a safety precaution during development, you can analyze the call stack in the constructor and check that the call is made from one of the expected methods.)

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

#3


16  

Most Pythonic would be what the Python standard library already does. Core developer Raymond Hettinger (the collections guy) gave a talk on this, plus general guidelines for how to write classes.

大多数Python标准库都是这样做的。核心开发人员Raymond Hettinger(集合人员)对此进行了讨论,并给出了如何编写类的一般指导方针。

Use separate, class-level functions to initialize instances, like how dict.fromkeys() isn't the class initializer but still returns an instance of dict. This allows you to be flexible toward the arguments you need without changing method signatures as requirements change.

使用单独的类级函数来初始化实例,比如dict.fromkeys()不是类初始化器,但仍然返回一个命令的实例。这允许您在不需要更改方法签名的情况下灵活地对待您需要的参数,因为需求会发生变化。

#4


4  

What are the system goals for this code? From my standpoint, your critical phrase is but the user has to know which method he requires. What experience do you want your users to have with your code? That should drive the interface design.

这段代码的系统目标是什么?从我的观点来看,你的批评是,但是用户必须知道他需要哪种方法。您希望您的用户对您的代码有什么体验?这将驱动接口设计。

Now, move to maintainability: which solution is easiest to read and maintain? Again, I feel that solution C is inferior. For most of the teams with whom I've worked, solution B is preferable to A: it's a little easier to read and understand, although both readily break into small code blocks for treatment.

现在,转向可维护性:阅读和维护哪一种解决方案最容易?再一次,我觉得解C是次等的。对于与我共事过的大多数团队来说,解决方案B比A更可取:它更容易阅读和理解,尽管两者都很容易分解成小的代码块进行处理。

#5


4  

I'm not sure if I understood right but wouldn't this work?

我不确定我是否理解对了,但这不是有效的吗?

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

Or you could even do:

或者你甚至可以这样做:

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

Thank to Jon Kiparsky advice theres a better way to avoid global declarations on data and metadata so this is the new way:

感谢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 {}

#6


3  

If you are on Python 3.4+ you can use the functools.singledispatch decorator to do this (with a little extra help from the methoddispatch decorator that @ZeroPiraeus wrote for his answer):

如果您使用的是Python 3.4+,您可以使用functools。singledispatch decorator实现这一功能(@ZeroPiraeus为其答案编写的methoddispatch decorator):

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

#7


0  

The most pythonic way is to make sure any optional arguments have default values. So include all arguments that you know you need and assign them appropriate defaults.

最python化的方法是确保任何可选参数都具有默认值。包括所有你知道你需要的参数并为它们分配适当的默认值。

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

An important thing to remember is that any required arguments should not have defaults since you want an error to be raised if they're not included.

需要记住的一件重要的事情是,任何必需的参数都不应该具有默认值,因为如果不包含这些参数,则希望引发错误。

You can accept even more optional arguments using *args and **kwargs at the end of your arguments list.

您可以在参数列表的末尾使用*args和**kwargs接受更多可选参数。

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

#1


69  

You can't have multiple methods with same name in Python. Function overloading - unlike in Java - isn't supported.

在Python中不能有多个名称相同的方法。与Java不同的是,不支持函数重载。

Use default parameters or **kwargs and *args arguments.

使用默认参数或**kwargs和*args参数。

You can make static methods or class methods with the @staticmethod or @classmethod decorator to return an instance of your class, or to add other constructors.

可以使用@staticmethod或@classmethod decorator创建静态方法或类方法,以返回类的实例,或者添加其他构造函数。

I advise you to do:

我建议你这样做:

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.

在python中⚠从来没有可变类型默认值。⚠看到。

#2


25  

You can't have multiple constructors, but you can have multiple aptly-named factory methods.

不能有多个构造函数,但可以有多个名称合适的工厂方法。

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.

You can't easily prevent the user from using the constructor directly, though. (If it is critical, as a safety precaution during development, you can analyze the call stack in the constructor and check that the call is made from one of the expected methods.)

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

#3


16  

Most Pythonic would be what the Python standard library already does. Core developer Raymond Hettinger (the collections guy) gave a talk on this, plus general guidelines for how to write classes.

大多数Python标准库都是这样做的。核心开发人员Raymond Hettinger(集合人员)对此进行了讨论,并给出了如何编写类的一般指导方针。

Use separate, class-level functions to initialize instances, like how dict.fromkeys() isn't the class initializer but still returns an instance of dict. This allows you to be flexible toward the arguments you need without changing method signatures as requirements change.

使用单独的类级函数来初始化实例,比如dict.fromkeys()不是类初始化器,但仍然返回一个命令的实例。这允许您在不需要更改方法签名的情况下灵活地对待您需要的参数,因为需求会发生变化。

#4


4  

What are the system goals for this code? From my standpoint, your critical phrase is but the user has to know which method he requires. What experience do you want your users to have with your code? That should drive the interface design.

这段代码的系统目标是什么?从我的观点来看,你的批评是,但是用户必须知道他需要哪种方法。您希望您的用户对您的代码有什么体验?这将驱动接口设计。

Now, move to maintainability: which solution is easiest to read and maintain? Again, I feel that solution C is inferior. For most of the teams with whom I've worked, solution B is preferable to A: it's a little easier to read and understand, although both readily break into small code blocks for treatment.

现在,转向可维护性:阅读和维护哪一种解决方案最容易?再一次,我觉得解C是次等的。对于与我共事过的大多数团队来说,解决方案B比A更可取:它更容易阅读和理解,尽管两者都很容易分解成小的代码块进行处理。

#5


4  

I'm not sure if I understood right but wouldn't this work?

我不确定我是否理解对了,但这不是有效的吗?

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

Or you could even do:

或者你甚至可以这样做:

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

Thank to Jon Kiparsky advice theres a better way to avoid global declarations on data and metadata so this is the new way:

感谢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 {}

#6


3  

If you are on Python 3.4+ you can use the functools.singledispatch decorator to do this (with a little extra help from the methoddispatch decorator that @ZeroPiraeus wrote for his answer):

如果您使用的是Python 3.4+,您可以使用functools。singledispatch decorator实现这一功能(@ZeroPiraeus为其答案编写的methoddispatch decorator):

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

#7


0  

The most pythonic way is to make sure any optional arguments have default values. So include all arguments that you know you need and assign them appropriate defaults.

最python化的方法是确保任何可选参数都具有默认值。包括所有你知道你需要的参数并为它们分配适当的默认值。

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

An important thing to remember is that any required arguments should not have defaults since you want an error to be raised if they're not included.

需要记住的一件重要的事情是,任何必需的参数都不应该具有默认值,因为如果不包含这些参数,则希望引发错误。

You can accept even more optional arguments using *args and **kwargs at the end of your arguments list.

您可以在参数列表的末尾使用*args和**kwargs接受更多可选参数。

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