实现两个构造函数的最pythonic方法

时间:2013-03-15 13:56:14

标签: python polymorphism

请原谅Python新手的风格无能。

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

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

myClass = MyClass()
myClass.initList(listVar)

我当然可以忍受这个,但这当然不是完美的。所以,我决定转向一些Pythonic的智慧:应该如何实现这样的多态构造函数?我是否应该尝试无法读取initData.keys()以便嗅探这是否是字典?或者也许嗅探参数类型以实现糟糕的多态性,其中设计不受欢迎被认为是非pythonic?

4 个答案:

答案 0 :(得分:3)

在一个理想的世界中,你会编写一个构造函数,可以在不知道差异的情况下使用listdict(即鸭子类型)。当然,这不太现实,因为这些是非常不同的鸭子。

可以理解的是,你对检查实际实例类型的想法有些不满,因为它打破了鸭子打字的想法。但是,在python 2.6中引入了一个名为abc的有趣模块,它允许定义“抽象基类”。要被视为抽象基类的实例,实际上不必从它继承,而只需实现其所有抽象方法。

collections模块包含一些在这里感兴趣的抽象基类,即collections.Sequencecollections.Mapping。因此,您可以编写__init__函数,如:

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

http://docs.python.org/2/library/collections.html#collections-abstract-base-classes包含每个ABC提供哪些方法的细节。如果您坚持这些,那么您的代码现在可以接受任何适合这些抽象基类之一的对象。而且,就采用内置dictlist类型而言,您可以看到:

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

而且,几乎是偶然的,你只是让它适用于tuple。你可能不在乎它是否真的是list,只是你可以读出它的元素。但是,如果您选中了isinstance(somedata, list),那么您将排除tuple。这就是使用ABC买你的东西。

答案 1 :(得分:2)

Python中没有函数overloading。在if isinstance(...)方法中使用__init__()可以非常简单地阅读和理解:

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

答案 2 :(得分:2)

正如@ Jan-PhilipGehrcke所说,pythonic很难量化。对我而言,这意味着:

  • 易于阅读
  • 易于维护
  • 简单比复杂优于复杂
  • etcetera,etcetera等等(请参阅Zen of Python获取完整列表,您可以通过在解释器中键入import this获得)

因此,最pythonic解决方案取决于您必须为每个支持的初始化程序做什么,以及您拥有多少个。我会说,如果你只有少数几个,只需几行代码即可处理,然后使用isinstance__init__

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)

另一方面,如果你有许多可能的初始化器,或者如果其中任何一个需要大量代码来处理,那么你将希望为每个可能的init类型都有一个classmethod构造函数,最常见的用法是__init__

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__中所做的那样)需要谨慎处理;原因是默认值只被评估一次,然后结果将用于每次后续调用。当您在函数中修改该对象时,这只是一个问题,因为每次后续调用都会看到这些修改 - 除非您想创建一个可能不是您正在寻找的行为的缓存。这不是我的例子的问题,因为我没有修改init_dict,只是迭代它(如果调用者没有替换它,这是一个无操作,因为它是空的)。

答案 3 :(得分:-1)

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