实现两个构造函数的最具python风格的方法

实现两个构造函数的最具python风格的方法,python,polymorphism,Python,Polymorphism,请原谅Python新手在这里的风格无能 我有一个类,它使用一个参数来建立初始数据。有两种方法可以输入初始数据:一种是字符串列表,另一种是带有字符串键和整数值的字典 现在,我只实现了构造函数的一个版本,即以{}作为默认值的以字典作为参数的版本。列表参数init作为一种方法实现,即 myClass = MyClass() myClass.initList(listVar) 我当然可以接受,但这肯定不是完美的。所以,我决定转向这里,寻求一些python的智慧:应该如何实现这种多态构造函数?我是否应该

请原谅Python新手在这里的风格无能

我有一个类,它使用一个参数来建立初始数据。有两种方法可以输入初始数据:一种是字符串列表,另一种是带有字符串键和整数值的字典

现在,我只实现了构造函数的一个版本,即以{}作为默认值的以字典作为参数的版本。列表参数init作为一种方法实现,即

myClass = MyClass()
myClass.initList(listVar)
我当然可以接受,但这肯定不是完美的。所以,我决定转向这里,寻求一些python的智慧:应该如何实现这种多态构造函数?我是否应该尝试读取initData.keys()失败,以便嗅探这是否是字典?或者嗅探参数类型以实现糟糕的多态性,而这种多态性在设计上并不受欢迎,这被认为是非pythonic的?

Python中没有函数。在
方法中使用
if isinstance(…)
,阅读和理解起来非常简单:

class Foo(object):
    def __init__(self, arg):
        if isinstance(arg, dict):
            ...
        elif isinstance(arg, list):
            ...
        else:
            raise Exception("big bang")

您可以使用
*args
**kwargs
来执行此操作。
但是,如果您想知道参数的类型,您应该使用
type()
isinstance()

,正如@Jan PhilipGehrcke所指出的,
pythonic
可能很难量化。对我来说,这意味着:

  • 易读
  • 易于维护
  • 简单胜于复杂胜于复杂
  • 等等,等等(在解释器中键入
    import this
    可获得完整列表,请参见Python的
    Zen
因此,最具python风格的解决方案取决于您必须为每个受支持的初始值设定项执行哪些操作,以及您拥有多少个初始值设定项。我想说的是,如果您只有少量代码,并且每一行代码都只能由几行代码处理,那么请使用
isinstance
\uuuu init\uuu

class MyClass(object):

    def __init__(self, initializer):
        """
        initialize internal data structures with 'initializer'
        """
        if isinstance(initializer, dict):
            for k, v in itit_dict.items():
                # do something with k & v
                setattr(self, k, v)
        elif isinstance(initializer, (list, tuple)):
            for item in initializer:
                setattr(self, item, None)
class MyClass(object):

    def __init__(self, init_dict={}):
        """
        initialize internal data structures with 'init_dict'
        """
        for k, v in itit_dict.items():
            # do something with k & v
            setattr(self, k, v)

    @classmethod
    def from_sequence(cls, init_list):
        """
        initialize internal  data structures with 'init_list'
        """
        result = cls()
        for item in init_list:
            setattr(result, item, None)
        return result
另一方面,如果您有许多可能的初始值设定项,或者其中任何一个需要处理大量代码,那么对于每个可能的init类型,您都希望有一个
classmethod
构造函数,最常见的用法是在
\uu init\uu
中:

class MyClass(object):

    def __init__(self, initializer):
        """
        initialize internal data structures with 'initializer'
        """
        if isinstance(initializer, dict):
            for k, v in itit_dict.items():
                # do something with k & v
                setattr(self, k, v)
        elif isinstance(initializer, (list, tuple)):
            for item in initializer:
                setattr(self, item, None)
class MyClass(object):

    def __init__(self, init_dict={}):
        """
        initialize internal data structures with 'init_dict'
        """
        for k, v in itit_dict.items():
            # do something with k & v
            setattr(self, k, v)

    @classmethod
    def from_sequence(cls, init_list):
        """
        initialize internal  data structures with 'init_list'
        """
        result = cls()
        for item in init_list:
            setattr(result, item, None)
        return result
这使每个可能的构造函数保持简单、干净和易于理解


作为旁注:使用可变对象作为默认值(就像我在上面的
\uuuu init\uuuu
中所做的那样)需要小心;原因是默认值只计算一次,然后无论结果如何,都将用于后续的每次调用。这只是当您在函数中修改该对象时的问题,因为这些修改将在随后的每次调用中看到——除非您想要创建一个缓存,否则这可能不是您想要的行为。这不是我的示例的问题,因为我不是在修改
init_dict
,而是在它上面迭代(如果调用方没有替换它,因为它是空的,那么这是一个不操作)。

在理想的世界中,您应该编写一个构造函数,它可以接受
列表
dict
,而不知道它们之间的区别(即鸭子类型)。当然,这不是很现实,因为它们是完全不同的鸭子

同样可以理解的是,对于检查实际实例类型的想法,您也有点心痛,因为它与duck类型的想法不符。但是,在Python2.6中引入了一个名为
abc
的有趣模块,它允许定义“抽象基类”。要将其视为抽象基类的实例,实际上不必从中继承,只需实现其所有抽象方法即可

collections
模块包括一些这里感兴趣的抽象基类,即
collections.Sequence
collections.Mapping
。因此,您可以编写
\uuuu init\uuu
函数,如:

def __init__(self, somedata):
    if isinstance(somedata, collections.Sequence):
        # somedata is a list or some other Sequence
    elif isinstance(somedata, collections.Mapping):
        # somedata is a dict or some other Mapping
包含每个ABC提供的具体方法。如果您坚持这些,那么您的代码现在可以接受符合这些抽象基类之一的任何对象。至于内置的
dict
list
类型,您可以看到:

>>> isinstance([], collections.Sequence)
True
>>> isinstance([], collections.Mapping)
False
>>> isinstance({}, collections.Sequence)
False
>>> isinstance({}, collections.Mapping)
True

而且,几乎是偶然的,您刚刚使它也适用于
tuple
。您可能并不关心它是否真的是一个
列表
,只关心您可以从中读取元素。但是,如果您选中了
isinstance(somedata,list)
,您将排除
tuple
。这就是使用ABC给您带来的好处。

为什么不使用
*args
**kwargs
?用户类方法作为替代构造函数呢。请参阅我对这个类似问题的回答:另外,不要在函数或方法中使用“{}”作为默认参数。在一些工作中,它的行为就像一个“C静态”变量——每次函数调用都会看到与以前调用相同的对象。感谢@jsbueno提供的这一宝贵提示,我不知道这个对象在所有实例中都被重用。并不是说我的设计存储了对它的引用,但这可以避免我在其他项目中遇到一些麻烦。@jsbueno:更完整地说:每个函数将看到相同的对象,除非调用方为该参数指定不同的对象。当然,它应该小心使用。这将导致中断,因为不带参数调用您的“cls”不会将强制的
init\u dict
传递给您的
\uuuu init\uuu
让我们不要打
最具python性的解决方案的战争。这很难量化。你介意把这部分从你的答案中删除吗?@Jan PhilipGehrcke:我愿意;不过,我不介意添加一些解释,包括
pythonic
对我意味着什么。希望这会有帮助。这会有用的。然而,如果我完全诚实的话,我并不觉得这很像蟒蛇。我不再喜欢“蟒蛇”这个词了。然而,我很高兴读到你想表达的实际观点:-)——那么,你具体的意思是什么?(没有讽刺意味