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

  1. taki, który zajmuje kilka takich dziur: parmesan = Cheese(num_holes = 15)
  2. 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?
Author: smci, 2009-03-25

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}

Http://docs.python.org/reference/expressions.html#calls

 693
Author: vartec,
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()
 532
Author: Ber,
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 )
 19
Author: Yes - that Jake.,
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...

 18
Author: Ferdinand Beyer,
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.
 17
Author: Brad C,
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)
 10
Author: mluebke,
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
 10
Author: Andrzej Pronobis,
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.

 8
Author: Devin Jeanpierre,
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) 
 2
Author: Michel Samia,
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.

 0
Author: Elmex80s,
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
 0
Author: Alexey,
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