Co to jest czysty, pythoniczny sposób na posiadanie wielu konstruktorów w Pythonie?
Nie mogę znaleźć na to ostatecznej odpowiedzi. AFAIK, nie możesz mieć wielu __init__
funkcji w klasie Pythona. Jak więc rozwiązać ten problem?
Załóżmy, że mam klasę o nazwie Cheese
z właściwością number_of_holes
. Jak Mogę mieć dwa sposoby tworzenia przedmiotów serowych...
- taki, który zajmuje kilka takich dziur:
parmesan = Cheese(num_holes = 15)
- i taki, który nie pobiera argumentów i po prostu randomizuje Właściwość
number_of_holes
:gouda = Cheese()
Mogę wymyślić tylko jeden sposób, aby to zrobić, ale to wydaje się trochę niezgrabne:
class Cheese():
def __init__(self, num_holes = 0):
if (num_holes == 0):
# randomize number_of_holes
else:
number_of_holes = num_holes
Co ty na to? Jest inny sposób? 11 answers
Właściwie None
jest dużo lepiej dla" magicznych " wartości:
class Cheese():
def __init__(self, num_holes = None):
if num_holes is None:
...
Teraz jeśli chcesz mieć pełną swobodę dodawania kolejnych parametrów:
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())
Aby lepiej wyjaśnić pojęcie *args
i **kwargs
(można faktycznie zmienić te nazwy):
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}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-07 00:41:58
Używanie num_holes=None
jako domyślnego jest w porządku, jeśli masz zamiar mieć tylko __init__
.
Jeśli chcesz mieć wiele niezależnych "konstruktorów", możesz podać je jako metody klasowe. Są one zwykle nazywane metodami fabrycznymi. W tym przypadku możesz mieć domyślną wartość num_holes
be 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))
Teraz Utwórz obiekt w ten sposób:
gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-01 15:29:51
Wszystkie te odpowiedzi są doskonałe, jeśli chcesz użyć opcjonalnych parametrów, ale inną możliwością Pythoniczną jest użycie classmethod do wygenerowania fabrycznego pseudo-konstruktora:
def __init__(self, num_holes):
# do stuff with the number
@classmethod
def fromRandom(cls):
return cls( # some-random-number )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-25 17:16:04
Dlaczego uważasz, że Twoje rozwiązanie jest "niezgrabne"? Osobiście wolałbym jeden konstruktor z domyślnymi wartościami niż wiele przeciążonych konstruktorów w sytuacjach takich jak Twój (Python i tak nie obsługuje metody przeciążania):
def __init__(self, num_holes=None):
if num_holes is None:
# Construct a gouda
else:
# custom cheese
# common initialization
Dla naprawdę złożonych przypadków z wieloma różnymi konstruktorami, może być czystsze użycie różnych funkcji fabrycznych zamiast tego:
@classmethod
def create_gouda(cls):
c = Cheese()
# ...
return c
@classmethod
def create_cheddar(cls):
# ...
W twoim przykładzie serowym możesz użyć podklasy sera Gouda...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-25 17:11:35
To dobre pomysły na Twoją implementację, ale jeśli prezentujesz użytkownikowi interfejs do produkcji sera. Nie dbają o to, ile dziur ma ser, ani jakie wewnętrzne elementy go wytwarzają. Użytkownik Twojego kodu chce tylko "gouda" lub "parmezan", prawda?
Więc dlaczego nie zrobić tego:
# cheese_user.py
from cheeses import make_gouda, make_parmesean
gouda = make_gouda()
paremesean = make_parmesean()
I wtedy możesz użyć dowolnej z powyższych metod, aby faktycznie zaimplementować funkcje:
# 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)
Jest to dobra technika enkapsulacji i myślę, że jest bardziej Pythoniczna. Dla mnie ten sposób robienia rzeczy bardziej pasuje do pisania kaczek. Po prostu prosisz o przedmiot gouda i nie obchodzi cię, jaka to klasa.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-25 15:06:03
Najlepsza odpowiedź jest ta powyżej o domyślnych argumentach, ale dobrze się bawiłem pisząc to, i na pewno pasuje do rachunku dla "wielu konstruktorów". Używaj na własne ryzyko.
A co z Nowe metoda.
"Typowe implementacje tworzą nową instancję klasy, wywołując metodę superclass new za pomocą super (currentclass, cls).new (cls [,...]) z odpowiednimi argumentami, a następnie modyfikując nowo utworzoną instancję jako konieczne przed zwrotem."
Więc możesz mieć nową metodę zmodyfikować definicję klasy poprzez dołączenie odpowiedniej metody konstruktora.
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-25 19:55:04
Zdecydowanie należy preferować rozwiązania już opublikowane, ale ponieważ nikt jeszcze nie wspomniał o tym rozwiązaniu, myślę, że warto wspomnieć o kompletności.
Podejście @classmethod
można zmodyfikować, aby zapewnić alternatywny konstruktor, który nie wywołuje konstruktora domyślnego (__init__
). Zamiast tego instancja jest tworzona za pomocą __new__
.
To może być użyte, jeśli Typ inicjalizacji nie może być wybrany na podstawie typu argumentu konstruktora, a konstruktory tak nie udostępniaj kodu.
Przykład:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-11 00:20:55
Zamiast tego użyj num_holes=None
jako domyślnego. Następnie sprawdź, czy num_holes is None
, a jeśli tak, randomizuj. Tak ogólnie to widzę.
Bardziej radykalnie różne metody konstrukcyjne mogą uzasadniać użycie classmethod, która zwraca instancję cls
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-25 17:03:56
Użyłbym dziedziczenia. Zwłaszcza jeśli będzie więcej różnic niż liczba otworów. Zwłaszcza jeśli Gouda będzie musiał mieć inny zestaw członków, a następnie parmezan.
class Gouda(Cheese):
def __init__(self):
super(Gouda).__init__(num_holes=10)
class Parmesan(Cheese):
def __init__(self):
super(Parmesan).__init__(num_holes=15)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 12:33:56
Tak to rozwiązałem dla klasy, którą musiałem stworzyć. Stworzyłem {[3] } z jednym parametrem o nazwie value
. Kod __init__
decyduje o typie parametru value
i odpowiednio przetwarza dane. Jeśli chcesz mieć wiele parametrów wejściowych, po prostu spakuj je do jednej krotki i przetestuj, czy value
jest krotką.
Używasz go w ten sposób:
>>> temp = YearQuarter(datetime.date(2017, 1, 18))
>>> print temp
2017-Q1
>>> temp = YearQuarter((2017, 1))
>>> print temp
2017-Q1
I tak wygląda __init__
i reszta klasy:
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)
Możesz rozwinąć __init__
z wieloma komunikatami o błędach oczywiście. Pominąłem je w tym przykładzie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-18 12:55:35
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-25 21:52:40