我找不到确切的答案。据我所知,在一个Python类中不能有多个__init__函数。那么我该如何解决这个问题呢?

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


一种方法有很多这样的漏洞:parmesan = Cheese(num_holes = 15)

而另一种方法则没有参数并随机化了number_of_holes属性:gouda = Cheese()


我只能想到一种方法,但这似乎很笨拙:

class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # randomize number_of_holes
        else:
            number_of_holes = num_holes


你怎么说?还有另一种方法吗?

评论

我认为init不是构造函数,它是一个初始化程序。 new将是一个构造函数

#1 楼

实际上,None对于“魔术”值要好得多:

class Cheese():
    def __init__(self, num_holes = None):
        if num_holes is None:
            ...


现在,如果要完全自由地添加更多参数,请:

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的概念(您实际上可以更改这些名称):

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

评论


对于那些感兴趣的人,kwargs代表关键字参数(一旦知道,就似乎是逻辑)。 :)

–tleb
16年8月3日在6:29



有时候* args和** kwargs会显得过大。在大多数构造函数中,您想知道您的参数是什么。

–user989762
18年4月28日在9:26

@ user989762是的!当然!

–诺亚·布鲁克斯(Noah Broyles)
4月15日0:30

@ user989762是的,这种方法根本不是自我记录(您尝试使用一个库并尝试从方法签名中了解多少次使用,只是发现您必须进行代码检查才能看到期望的参数/允许吗?)此外,现在您的实现承担了参数检查的额外负担,包括选择是否接受(拒绝)不支持的参数。

– GlenRSmith
7月22日15:36

对于2020年来自google的人们来说,向下滚动一下页面-对于大多数情况,“ Ber”的答案比该方法更可靠,而且更加Python化。

–汤姆·H
11月12日13:40



#2 楼

如果只需要num_holes=None,则将__init__用作默认值是可以的。

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

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))


现在创建这样的对象:

gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()


评论


我认为这也更干净。这是Alex Martelli撰写的comp.lang.python帖子“ Re:Multiple constructors”中指向更清晰的解释的链接,恕我直言。 coding.derkeiler.com/Archive/Python/comp.lang.python/2005-02/…

– ariddell
09-10-25在17:58

@rmbianchi:可接受的答案可能与其他语言更加一致,但也很少使用pythonic:@classmethods是实现多个构造函数的pythonic方法。

– Ethan Furman
2012年3月22日,下午1:34

@Bepetersn有实例方法(普通方法),其实例对象被称为self。然后是类方法(使用@classmethod),它们将对类对象的引用称为cls。最后是静态方法(用@staticmethod声明),没有这些引用。静态方法就像模块级别的函数一样,只不过它们位于类的名称空间中。

–贝尔
13年4月22日在11:10

与公认的解决方案相比,此方法的优势在于它可以轻松地指定抽象构造函数并强制实施它们,尤其是在python 3中,可以在同一工厂函数上使用@abstractmethod和@classmethod并将其内置到语言。我还要指出,这种方法更加明确,与Python的Zen一起使用。

–马赫
2014年9月4日在13:16



@ashu其他构造函数通过cls(...)实例化类,从而调用__init __()方法。因此,number_of_holes始终以相同的方式使用。

–贝尔
18年8月23日在11:37

#3 楼

绝对应该选择已经发布的解决方案,但是由于没有人提到此解决方案,因此我认为值得一提。完整的方法。可以修改@classmethod方法以提供一种不调用的替代构造函数。默认构造函数(__init__)。而是使用__new__创建实例。

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

示例:

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__
        super(MyClass, obj).__init__()  # Don't forget to call any polymorphic base class initializers
        obj._value = load_from_somewhere(somename)
        return obj


评论


这是确实提供独立构造函数的解决方案,而不是摆弄__init__的参数。但是,能否请您提供一些参考,以某种方式正式批准或支持此方法?直接调用__new__方法有多安全可靠?

– Alexey
18-2-20在14:11



我是这样做的,然后来到这里问以上问题,看看我的方法是否正确。您仍然需要调用super,否则这将无法在协作式多重继承中起作用,因此我在您的答案中添加了这一行。

–尼尔G
3月1日23:22

我想知道是否可以定义一个装饰器“ constructor”(包装新的和超级的东西),然后执行:@constructor def other_init(self,stuff):self.stuff = stuff

–汤姆·绞盘
10月30日16:36

#4 楼

如果要使用可选参数,所有这些答案都是非常好的,但是另一种Python的可能性是使用类方法来生成工厂风格的伪构造函数:

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )


#5 楼

您为什么认为您的解决方案“笨拙”?就我个人而言,在像您这样的情况下,我更喜欢一个具有默认值的构造函数,而不是多个重载的构造函数(Python无论如何都不支持方法重载):

def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization


不同的构造函数,使用不同的工厂函数可能更干净:

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

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


在您的奶酪示例中,您可能希望使用奶酪的Gouda子类...

评论


工厂函数使用cls:使用cls代替Cheese。如果不是,使用类方法而不是静态方法有什么意义?

–角色
18年8月2日在11:53



#6 楼

这些是实现的好主意,但是如果您要向用户展示奶酪制作界面。他们不在乎奶酪有多少个孔或制作奶酪的内部因素。您的代码的用户只想正确使用“ gouda”或“ parmesean”吗?

为什么不这样做:

# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()


然后您可以使用上述任何方法来实际实现功能:

# 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)


这是一种很好的封装技术,我认为它更像Pythonic。对我来说,这种做事方式更适合鸭子的打字。您只是在问一个gouda对象,而您实际上并不关心它是什么类。

评论


我倾向于选择这种方法,因为它与工厂方法模式非常相似。

– 2rs2ts
13年5月30日在0:21

make_gouda,make_parmesan应该是Cheese类的分类方法

–smci
18年7月17日在23:11

#7 楼

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

更根本不同的构造方法可能会保证返回cls实例的类方法。

#8 楼

最好的答案是上面关于默认参数的答案,但是我写这个很有趣,它确实适合“多个构造函数”。使用后果自负。

新方法怎么样?

”“典型实现通过使用super(currentclass)调用超类的new()方法来创建该类的新实例。 ,cls).new(cls [,...])与适当的参数,然后在返回之前根据需要修改新创建的实例。“

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

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)


评论


这类代码使我厌恶使用动态语言进行工作-并不是说它固有地存在问题,只是它违反了我对一堂课所做的一些关键假设。

–是的-那个杰克。
09年3月30日在16:07

@javawizard是否容易在注释中解释是什么使它成为非线程安全的,或提供一个指针以便我可以在其他地方阅读它?

– Reti43
2014年12月14日上午9:04

@ Reti43说两个线程尝试同时创建奶酪,一个线程使用Cheese(0),另一个使用Cheese(1)。线程1可能运行cls .__ init__ = cls.foo方法,但是线程2可能运行cls .__ init__ = cls.bar方法,然后线程1进一步运行。然后,两个线程最终都将调用barmethod,这不是您想要的。

–javawizard
2014年12月14日上午9:23

实际上,没有理由仅为了处理类的一个实例的创建而修改类的定义。

–chepner
7月26日下午16:55

#9 楼

我会使用继承。特别是如果孔数比孔数更多的话。特别是如果Gouda需要与帕玛森人拥有不同的成员集。

class Gouda(Cheese):
    def __init__(self):
        super(Gouda).__init__(num_holes=10)


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


评论


继承可能是适当的,但实际上这与所要求的是一个正交的问题。

–chepner
7月26日下午16:57

#10 楼

这就是我为必须创建的YearQuarter类解决的方法。我创建了一个__init__,它对各种各样的输入都非常宽容。
您可以这样使用它:
>>> from datetime import date
>>> temp1 = YearQuarter(year=2017, month=12)
>>> print temp1
2017-Q4
>>> temp2 = YearQuarter(temp1)
>>> print temp2
2017-Q4
>>> temp3 = YearQuarter((2017, 6))
>>> print temp3
2017-Q2 
>>> temp4 = YearQuarter(date(2017, 1, 18))
>>> print temp4
2017-Q1
>>> temp5 = YearQuarter(year=2017, quarter = 3)
>>> print temp5
2017-Q3

这就是__init__和其他班级的样子:
import datetime


class YearQuarter:

    def __init__(self, *args, **kwargs):
        if len(args) == 1:
            [x]     = args

            if isinstance(x, datetime.date):
                self._year      = int(x.year)
                self._quarter   = (int(x.month) + 2) / 3
            elif isinstance(x, tuple):
                year, month     = x

                self._year      = int(year)

                month           = int(month)

                if 1 <= month <= 12:
                    self._quarter   = (month + 2) / 3
                else:
                    raise ValueError

            elif isinstance(x, YearQuarter):
                self._year      = x._year
                self._quarter   = x._quarter

        elif len(args) == 2:
            year, month     = args

            self._year      = int(year)

            month           = int(month)

            if 1 <= month <= 12:
                self._quarter   = (month + 2) / 3
            else:
                raise ValueError

        elif kwargs:

            self._year      = int(kwargs["year"])

            if "quarter" in kwargs:
                quarter     = int(kwargs["quarter"])

                if 1 <= quarter <= 4:
                    self._quarter     = quarter
                else:
                    raise ValueError
            elif "month" in kwargs:
                month   = int(kwargs["month"])

                if 1 <= month <= 12:
                    self._quarter     = (month + 2) / 3
                else:
                    raise ValueError

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


评论


我已经有效地使用了它,但是使用了自己的类而不是Python类型。给定__init __(self,obj),我在__init__内测试是否str(obj .__ class __.__ name__)=='NameOfMyClass':... elif等。

–迈克·奥康纳(Mike O'Connor)
19/12/30在22:40

这真的不是Python风格。 __init__应该直接花费一年零四分之一,而不是一个未知类型的值。类方法from_date可以处理从datetime.date值中提取年份和季度,然后调用YearQuarter(y,q)。您可以从from_tuple定义类似的类方法,但这似乎不值得做,因为您可以简单地调用YearQuarter(* t)。

–chepner
7月26日16:59

@chepner我给了它很大的更新。请告诉我你的想法。

– Elmex80s
7月27日10:16

仍然是一团糟(甚至比以前更糟)的特殊情况。 __init__不应该负责分析您可能用于创建实例的每组值。 def __init __(self,year,Quarter):self._year =年; self._quarter =季度:就这样(尽管可能需要对季度进行一些范围检查)。其他类方法则负责将一个或多个不同的参数映射到可以传递给__init__的一年和一个季度的工作。

–chepner
7月27日14:18

例如,from_year_month占用一个月m,将其映射到四分之一q,然后调用YearQuarter(y,q)。 from_date从日期实例中提取年份和月份,然后调用YearQuarter._from_year_month。没有重复,并且每种方法都负责产生传递给__init__的一年零四分之一的一种特定方式。

–chepner
7月27日14:34

#11 楼

由于我的最初答案是基于我的专用构造函数未调用(唯一)默认构造函数而受到批评的,因此我在此处发布了一个修改版本,以兑现所有构造函数均应调用默认构造函数的愿望:

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_gouda(self, ...):
        """A special initialiser for Gouda cheese.
        """
        ...

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

    @classmethod
    def make_gouda(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_gouda")


评论


类方法的想法是将创建一个特殊的实例分为两个独立的部分:首先,定义一个通用的__init__,它可以处理奶酪的初始化而不必了解特殊种类的奶酪。其次,您定义一个类方法,以针对某些特殊情况为通用__init__生成适当的参数。在这里,您基本上是在重塑继承的一部分。

–chepner
7月26日17:05

#12 楼

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


评论


不。这是完全不符合Python的,就像Java伪装在Python语法后面一样。您只需要一个__init__方法,其他的类方法要么按原样(最干净)调用它,要么通过所需的任何辅助类方法和设置器处理特殊的初始化操作(理想情况下没有)。

–smci
18年7月17日在23:18

当我有多个具有不同初始化例程的构造函数时,我不希望使用__init__方法。我不明白为什么有人会想要它。 “其他类方法要么原样调用”-调用什么? __init__方法?明确地将__init__称为IMO会很奇怪。

– Alexey
18年7月18日在8:35

阿列克谢,拥有多个构造函数,就像在多个_init ...方法中一样,完全是不符合Python的(请参见此问题的其他答案。)更糟糕的是,在这种情况下,您甚至不需要:您还没有展示代码的方式对于_init_parmesan和_init_gouda不同,因此零理由不对它们进行普通处理。无论如何,Pythonic的方法是向* args或** kwargs提供非默认的args(例如Cheese(...,type ='gouda'...),或者如果不能处理所有事情,则放__init__中的通用代码以及classmethod make_whatever ...中不常用的代码,并将其设置为set

–smci
18年7月18日在21:42



“拥有多个构造函数是完全不符合Python的” –最初的问题仍然是“在Python中具有多个构造函数的一种干净的pythonic方法是什么?”。我只展示了如何拥有它们,而不是为什么我想要它们。

– Alexey
18年7月19日在6:48

至于为什么我要它们的问题,即使是namedtuple生成的类也有两个构造函数:默认的一个和_make。从内置类继承时,还有其他示例,其中使用多个构造函数将是唯一的选择,因为在__init__内部进行分派不是一个选择。

– Alexey
18年7月19日在6:55



#13 楼

我猜这很干净,很棘手

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