什么是Python中有多个构造函数的简洁的Python方法?

我找不到一个明确的答案。AFAIK,在Python类中不能有多个__init__函数。那么我怎么解决这个问题呢?

假设我有一个名为Cheese的类,它具有number_of_holes属性。我怎么能有两种方法来创建奶酪对象…

一个像这样有很多洞的:parmesan = Cheese(num_holes = 15)它不接受参数,只是随机化number_of_holes属性:gouda = Cheese()

我只能想到一种方法来做这件事,但这似乎有点笨拙:

1
2
3
4
5
6
class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # randomize number_of_holes
        else:
            number_of_holes = num_holes

你说呢?还有别的办法吗?


实际上None对于"魔法"值更好:

1
2
3
4
class Cheese():
    def __init__(self, num_holes = None):
        if num_holes is None:
            ...

现在,如果你想要添加更多参数的完全自由:

1
2
3
4
5
class Cheese():
    def __init__(self, *args, **kwargs):
        #args -- tuple of anonymous arguments
        #kwargs -- dictionary of named arguments
        self.num_holes = kwargs.get('num_holes',random_holes())

为了更好地解释*args**kwargs的概念(实际上可以更改这些名称):

1
2
3
4
5
6
7
8
9
def f(*args, **kwargs):
   print 'args: ', args, ' kwargs: ', kwargs

>>> f('a')
args:  ('a',)  kwargs:  {}
>>> f(ar='a')
args:  ()  kwargs:  {'ar': 'a'}
>>> f(1,2,param=3)
args:  (1, 2)  kwargs:  {'param': 3}

http://docs.python.org/reference/expressions.html#calls


如果只使用__init__,则可以使用num_holes=None作为默认值。

如果您想要多个独立的"构造函数",可以将它们作为类方法提供。这些通常称为工厂方法。在这种情况下,您可以将num_holes的缺省值设置为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Cheese(object):
    def __init__(self, num_holes=0):
       "defaults to a solid cheese"
        self.number_of_holes = num_holes

    @classmethod
    def random(cls):
        return cls(randint(0, 100))

    @classmethod
    def slightly_holey(cls):
        return cls(randint(0, 33))

    @classmethod
    def very_holey(cls):
        return cls(randint(66, 100))

现在创建这样的对象:

1
2
3
gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()


如果你想使用可选参数,所有这些答案都很好,但是python的另一种可能性是使用classmethod生成工厂风格的伪构造函数:

1
2
3
4
5
6
7
8
def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )

对于您的实现来说,这些都是好主意,但是如果您要向用户呈现一个cheese制作界面的话。他们不关心奶酪有多少洞,也不关心制作奶酪的内部材料。代码的用户只想要"gouda"或"parmesean",对吗?

所以为什么不这样做:

1
2
3
4
5
# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()

然后你可以使用上面的任何方法来实现函数:

1
2
3
4
5
6
7
8
9
10
11
12
# cheeses.py
class Cheese(object):
    def __init__(self, *args, **kwargs):
        #args -- tuple of anonymous arguments
        #kwargs -- dictionary of named arguments
        self.num_holes = kwargs.get('num_holes',random_holes())

def make_gouda():
    return Cheese()

def make_paremesean():
    return Cheese(num_holes=15)

这是一种很好的封装技术,我认为它更像python。对我来说,这种做事方式更符合鸭子打字的风格。您只是在请求一个gouda对象,并不真正关心它是什么类。


为什么你认为你的解决方案是"笨拙的"?我个人更喜欢一个默认值的构造函数,而不是像你的情况下的多个重载构造函数(Python不支持方法重载):

1
2
3
4
5
6
def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization

对于有许多不同构造函数的非常复杂的情况,使用不同的工厂函数可能会更干净:

1
2
3
4
5
6
7
8
9
@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

在您的cheese示例中,您可能想使用cheese的Gouda子类……


人们当然应该更喜欢已经发布的解决方案,但是因为还没有人提到这个解决方案,所以我认为为了完整性值得一提。

可以修改@classmethod方法,以提供一个不调用默认构造函数(__init__)的替代构造函数。相反,使用__new__创建一个实例。

如果不能根据构造函数参数的类型选择初始化类型,并且构造函数不共享代码,则可以使用这种方法。

例子:

1
2
3
4
5
6
7
8
9
10
class MyClass(set):

    def __init__(self, filename):
        self._value = load_from_file(filename)

    @classmethod
    def from_somewhere(cls, somename):
        obj = cls.__new__(cls)  # Does not call __init__
        obj._value = load_from_somewhere(somename)
        return obj


最好的答案是上面关于默认参数的那个,但是我写这篇文章很开心,而且它确实符合"多个构造函数"的要求。使用方法自负风险。

新方法怎么样?

典型的实现使用super(currentclass, cls)调用超类的new()方法来创建类的新实例。然后根据需要修改新创建的实例,然后返回它。"

因此,您可以让新方法通过附加适当的构造函数方法来修改类定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Cheese(object):
    def __new__(cls, *args, **kwargs):

        obj = super(Cheese, cls).__new__(cls)
        num_holes = kwargs.get('num_holes', random_holes())

        if num_holes == 0:
            cls.__init__ = cls.foomethod
        else:
            cls.__init__ = cls.barmethod

        return obj

    def foomethod(self, *args, **kwargs):
        print"foomethod called as __init__ for Cheese"

    def barmethod(self, *args, **kwargs):
        print"barmethod called as __init__ for Cheese"

if __name__ =="__main__":
    parm = Cheese(num_holes=5)


而是使用num_holes=None作为默认值。然后检查是否num_holes is None,如果是,则随机化。不管怎样,这就是我通常看到的。

更根本的不同构造方法可能需要返回cls实例的类方法。


我使用继承。特别是如果有比洞的数量更多的不同。特别是如果豪达需要不同的成员,那么帕尔马干酪。

1
2
3
4
5
6
7
8
class Gouda(Cheese):
    def __init__(self):
        super(Gouda).__init__(num_holes=10)


class Parmesan(Cheese):
    def __init__(self):
        super(Parmesan).__init__(num_holes=15)

由于我最初的答案被批评为我的特殊用途构造函数没有调用(唯一的)默认构造函数,所以我在这里发布了一个修改后的版本,它满足了所有构造函数调用默认构造函数的愿望:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Cheese:
    def __init__(self, *args, _initialiser="_default_init", **kwargs):
       """A multi-initialiser.
       """

        getattr(self, _initialiser)(*args, **kwargs)

    def _default_init(self, ...):
       """A user-friendly smart or general-purpose initialiser.
       """

        ...

    def _init_parmesan(self, ...):
       """A special initialiser for Parmesan cheese.
       """

        ...

    def _init_gauda(self, ...):
       """A special initialiser for Gauda cheese.
       """

        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_parmesan")

    @classmethod
    def make_gauda(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_gauda")

我想这是一种很干净的方法,也很巧妙

1
2
3
4
5
6
7
8
9
10
class A(object):
    def __init__(self,e,f,g):

        self.__dict__.update({k: v for k,v in locals().items() if k!='self'})
    def bc(self):
        print(self.f)


k=A(e=5,f=6,g=12)
k.bc() # >>>6


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Cheese:
    def __init__(self, *args, **kwargs):
       """A user-friendly initialiser for the general-purpose constructor.
       """

        ...

    def _init_parmesan(self, *args, **kwargs):
       """A special initialiser for Parmesan cheese.
       """

        ...

    def _init_gauda(self, *args, **kwargs):
       """A special initialiser for Gauda cheese.
       """

        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_parmesan(*args, **kwargs)
        return new

    @classmethod
    def make_gauda(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_gauda(*args, **kwargs)
        return new


这就是我如何为我必须创建的YearQuarter类解出它的。我用一个名为value的参数创建了一个__init____init__的代码只决定value参数的类型,并相应地处理数据。如果您想要多个输入参数,只需将它们打包到一个元组中,并测试value是否是一个元组。

你可以这样用:

1
2
3
4
5
6
>>> temp = YearQuarter(datetime.date(2017, 1, 18))
>>> print temp
2017-Q1
>>> temp = YearQuarter((2017, 1))
>>> print temp
2017-Q1

这就是__init__和其他类的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import datetime


class YearQuarter:

    def __init__(self, value):
        if type(value) is datetime.date:
            self._year = value.year
            self._quarter = (value.month + 2) / 3
        elif type(value) is tuple:              
            self._year = int(value[0])
            self._quarter = int(value[1])          

    def __str__(self):
        return '{0}-Q{1}'.format(self._year, self._quarter)

当然,您可以使用多个错误消息展开__init__。在这个例子中我省略了它们。