__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
你怎么说?还有另一种方法吗?
#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
评论
我认为init不是构造函数,它是一个初始化程序。 new将是一个构造函数