win*_*ith 660 python constructor
我无法找到明确的答案.AFAIK,你不能__init__在Python类中拥有多个函数.那么我该如何解决这个问题呢?
假设我有一个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
Run Code Online (Sandbox Code Playgroud)
你说什么?还有另外一种方法吗?
var*_*tec 780
实际上None对于"神奇"的价值要好得多:
class Cheese():
def __init__(self, num_holes = None):
if num_holes is None:
...
Run Code Online (Sandbox Code Playgroud)
现在,如果您想完全自由地添加更多参数:
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())
Run Code Online (Sandbox Code Playgroud)
为了更好地解释*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}
Run Code Online (Sandbox Code Playgroud)
http://docs.python.org/reference/expressions.html#calls
Ber*_*Ber 631
num_holes=None如果你想要的话,使用默认是很好的__init__.
如果您需要多个独立的"构造函数",则可以将它们作为类方法提供.这些通常称为工厂方法.在这种情况下,你可以有默认num_holesBE 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))
Run Code Online (Sandbox Code Playgroud)
现在创建这样的对象:
gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()
Run Code Online (Sandbox Code Playgroud)
Yes*_*ke. 24
如果你想使用可选参数,所有这些答案都非常好,但另一种Pythonic可能是使用classmethod来生成工厂式伪构造函数:
def __init__(self, num_holes):
# do stuff with the number
@classmethod
def fromRandom(cls):
return cls( # some-random-number )
Run Code Online (Sandbox Code Playgroud)
Fer*_*yer 18
为什么你认为你的解决方案"笨重"?就个人而言,我个人更喜欢一个默认值超过多个重载构造函数的构造函数(Python不支持方法重载):
def __init__(self, num_holes=None):
if num_holes is None:
# Construct a gouda
else:
# custom cheese
# common initialization
Run Code Online (Sandbox Code Playgroud)
对于具有许多不同构造函数的非常复杂的情况,使用不同的工厂函数可能更简洁:
@classmethod
def create_gouda(cls):
c = Cheese()
# ...
return c
@classmethod
def create_cheddar(cls):
# ...
Run Code Online (Sandbox Code Playgroud)
在您的奶酪示例中,您可能想要使用奶酪的Gouda子类...
Bra*_*d C 17
这些是您实施的好主意,但如果您要向用户展示奶酪制作界面.他们不关心奶酪有多少孔或者是什么内部制作奶酪.你的代码的用户只想要"gouda"或"parmesean"吗?
那么为什么不这样做:
# cheese_user.py
from cheeses import make_gouda, make_parmesean
gouda = make_gouda()
paremesean = make_parmesean()
Run Code Online (Sandbox Code Playgroud)
然后你可以使用上面的任何方法来实际实现这些功能:
# 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)
Run Code Online (Sandbox Code Playgroud)
这是一个很好的封装技术,我认为它更像Pythonic.对我来说,这种做事方式更适合鸭子打字.你只是要求一个gouda对象而你并不关心它是什么类.
And*_*bis 16
人们肯定更喜欢已发布的解决方案,但由于还没有人提到这个解决方案,我认为值得一提的是完整性.
@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__
obj._value = load_from_somewhere(somename)
return obj
Run Code Online (Sandbox Code Playgroud)
use*_*389 15
对于特定的奶酪示例,我同意许多其他关于使用默认值来表示随机初始化或使用静态工厂方法的答案。然而,您可能也想到了一些相关的场景,在这些场景中,采用替代的、简洁的方式来调用构造函数而不损害参数名称或类型信息的质量是有价值的。
从Python 3.8开始,functools.singledispatchmethod在很多情况下可以帮助完成这一点(而且更灵活,multimethod可以适用于更多场景)。(这篇相关文章描述了如何在没有库的情况下在 Python 3.4 中完成相同的任务。)我还没有在其中任何一个的文档中看到具体显示__init__您所询问的重载的示例,但似乎重载的原则相同任何会员方法均适用(如下所示)。
“单一调度”(标准库中提供)要求至少有一个位置参数,并且第一个参数的类型足以区分可能的重载选项。对于特定的 Cheese 示例,这并不成立,因为在没有给出参数的情况下您想要随机孔,但multidispatch确实支持完全相同的语法,并且只要可以根据所有方法的数量和类型区分每个方法版本就可以使用一起争论。
下面是如何使用任一方法的示例(一些细节是为了取悦mypy,这是我第一次将其放在一起时的目标):
from functools import singledispatchmethod as overload
# or the following more flexible method after `pip install multimethod`
# from multimethod import multidispatch as overload
class MyClass:
@overload # type: ignore[misc]
def __init__(self, a: int = 0, b: str = 'default'):
self.a = a
self.b = b
@__init__.register
def _from_str(self, b: str, a: int = 0):
self.__init__(a, b) # type: ignore[misc]
def __repr__(self) -> str:
return f"({self.a}, {self.b})"
print([
MyClass(1, "test"),
MyClass("test", 1),
MyClass("test"),
MyClass(1, b="test"),
MyClass("test", a=1),
MyClass("test"),
MyClass(1),
# MyClass(), # `multidispatch` version handles these 3, too.
# MyClass(a=1, b="test"),
# MyClass(b="test", a=1),
])
Run Code Online (Sandbox Code Playgroud)
输出:
[(1, test), (1, test), (0, test), (1, test), (1, test), (0, test), (1, default)]
Run Code Online (Sandbox Code Playgroud)
笔记:
overload,但它有助于使使用这两种方法之间的差异仅取决于您使用哪个导入。# type: ignore[misc]注释不是运行所必需的,但我把它们放在那里是为了取悦它mypy,它不喜欢装饰__init__也不喜欢直接调用__init__。@overload定义之前__init__只是 的糖衣__init__ = overload(the original definition of __init__)。在本例中,overload是一个类,因此结果__init__是一个对象,该对象具有一个__call__方法,因此它看起来像一个函数,但也有一个.register方法,稍后将调用该方法以添加另一个重载版本的__init__. 这有点混乱,但它很高兴 mypy,因为没有方法名称被定义两次。如果您不关心 mypy 并且计划使用外部库,那么multimethod还可以使用更简单的替代方法来指定重载版本。__repr__只是为了使打印输出有意义(通常不需要它)。multidispatch能够处理三个没有任何位置参数的附加输入组合。mlu*_*bke 10
最好的答案就是上面关于默认参数的答案,但我写这篇文章很有意思,它确实符合"多个构造函数"的要求.使用风险由您自己承担.
新方法怎么样?
"典型的实现通过调用超类的创建类的新实例新使用超级(currentclass,CLS)()方法.新(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)
Run Code Online (Sandbox Code Playgroud)
num_holes=None而是默认使用.然后检查是否num_holes is None,如果是,随机化.无论如何,这就是我通常看到的.
更完全不同的构造方法可能需要一种返回实例的类方法cls.