Что такое чистый, питонический способ иметь несколько конструкторов в Python?
Я не могу найти окончательного ответа на этот вопрос. 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
что скажешь? Есть ли другой способ?
11 ответов
на самом деле 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}
используя 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()
все эти ответы превосходны, если вы хотите использовать дополнительные параметры, но другая возможность Pythonic-использовать classmethod для создания псевдо-конструктора в Заводском стиле:
def __init__(self, num_holes):
# do stuff with the number
@classmethod
def fromRandom(cls):
return cls( # some-random-number )
почему вы думаете, что ваше решение является "неуклюжим"? Лично я предпочел бы один конструктор со значениями по умолчанию над несколькими перегруженными конструкторами в таких ситуациях, как ваша (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 сыра хотя...
Это хорошие идеи для вашей реализации, но если вы представляете интерфейс для создания сыра пользователю. Им все равно, сколько дырок в сыре или какие внутренности идут на производство сыра. Пользователь вашего кода просто хочет "Гауда" или "пармезан", верно?
Так почему бы не сделать так:
# 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)
Это хороший метод инкапсуляции, и я думаю это скорее питон. Для меня этот способ делать вещи больше подходит в соответствии с утиной печатью. Вы просто просите объект гауды, и вам все равно, какой это класс.
лучший ответ-это тот, который выше о аргументах по умолчанию, но мне было весело писать это, и он, безусловно, подходит для "нескольких конструкторов". Используйте на свой страх и риск.
насчет новая метод.
" типичные реализации создают новый экземпляр класса, вызывая суперкласс новая() метод с использованием super (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)
определенно следует предпочесть уже опубликованные решения,но поскольку никто еще не упомянул это решение, я думаю, что стоит упомянуть для полноты.
на @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
использовать num_holes=None
вместо этого по умолчанию. Затем проверьте, есть ли num_holes is None
и если это так, выборочно. Во всяком случае, я обычно это вижу.
более радикально разные методы построения могут гарантировать classmethod, который возвращает экземпляр cls
.
Я бы использовал наследование. Особенно, если будет больше различий, чем количество отверстий. Особенно, если Гауда нужно будет иметь другой набор членов, то пармезан.
class Gouda(Cheese):
def __init__(self):
super(Gouda).__init__(num_holes=10)
class Parmesan(Cheese):
def __init__(self):
super(Parmesan).__init__(num_holes=15)
вот как я решил это для YearQuarter
класс, который я должен был создать. Я создал __init__
С одним параметром называется value
. Код __init__
просто решает, какой тип value
параметр и обрабатывает данные соответственно. Если вам нужно несколько входных параметров, вы просто упаковываете их в один кортеж и тестируете на value
быть кортеж.
вы используете его так:
>>> temp = YearQuarter(datetime.date(2017, 1, 18))
>>> print temp
2017-Q1
>>> temp = YearQuarter((2017, 1))
>>> print temp
2017-Q1
и так __init__
и остальная часть класса выглядит например:
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__
С несколькими сообщениями об ошибках, конечно. Я опустил их для этого примера.
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