Konstruktory wielokrotne: sposób Pythoniczny? [duplikat]

To pytanie ma już odpowiedź tutaj:

Mam klasę kontenera, która przechowuje dane. Podczas tworzenia kontenera istnieją różne metody przekazywania danych.

  1. podaj plik zawierający dane
  2. przekazać dane bezpośrednio przez argumenty
  3. nie przekazuj danych; po prostu utwórz pusty kontener

W Javie stworzyłbym trzy konstruktory. Oto jak to by wyglądało, gdyby było to możliwe w Pythonie:

class Container:

    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

W Pythonie widzę trzy oczywiste rozwiązania, ale żadne z nich nie jest ładne:

A : użycie argumentów słów kluczowych:

def __init__(self, **kwargs):
    if 'file' in kwargs:
        ...
    elif 'timestamp' in kwargs and 'data' in kwargs and 'metadata' in kwargs:
        ...
    else:
        ... create empty container

B : użycie domyślnych argumentów:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        ...
    elif timestamp and data and metadata:
        ...
    else:
        ... create empty container

C : dostarcza konstruktor tylko do tworzenia pustych kontenerów. Podaj metody wypełnienia kontenery z danymi z różnych źródeł.

def __init__(self):
    self.timestamp = 0
    self.data = []
    self.metadata = {}

def add_data_from_file(file):
    ...

def add_data(timestamp, data, metadata):
    ...

Rozwiązania A i B są w zasadzie takie same. Nie lubię robić if / else, zwłaszcza, że muszę sprawdzić, czy wszystkie argumenty wymagane dla tej metody zostały podane. A jest nieco bardziej elastyczne niż B, jeśli kod ma być kiedykolwiek rozszerzony o czwartą metodę dodawania danych.

Rozwiązanie C wydaje się być najładniejsze, ale użytkownik musi wiedzieć, jakiej metody potrzebuje. Na przykład: he cant do c = Container(args) if he doesn ' t know what args jest.

Jakie jest najbardziej Pythoniczne rozwiązanie?

Author: smci, 2017-06-26

7 answers

Nie możesz mieć wielu metod o tej samej nazwie w Python. Funkcja overloading-inaczej niż w Java - nie jest obsługiwana.

Użyj domyślnych parametrów lub argumentów **kwargs i *args.

Możesz tworzyć statyczne metody lub metody klas za pomocą dekoratora @staticmethod lub @classmethod, aby zwrócić instancję klasy lub dodać inne konstruktory.

Radzę ci zrobić:

class F:

    def __init__(self, timestamp=0, data=None, metadata=None):
        self.timestamp = timestamp
        self.data = list() if data is None else data
        self.metadata = dict() if metadata is None else metadata

    @classmethod
    def from_file(cls, path):
       _file = cls.get_file(path)
       timestamp = _file.get_timestamp()
       data = _file.get_data()
       metadata = _file.get_metadata()       
       return cls(timestamp, data, metadata)

    @classmethod
    def from_metadata(cls, timestamp, data, metadata):
        return cls(timestamp, data, metadata)

    @staticmethod
    def get_file(path):
        # ...
        pass

⚠ nigdy nie ma mutowalnych typów jako domyślnych w Pythonie. ⚠ Zobacz tutaj .

 72
Author: glegoux,
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-06-26 23:40:32

Nie możesz mieć wielu konstruktorów, ale możesz mieć wiele trafnie nazwanych metod fabrycznych.

class Document(object):

    def __init__(self, whatever args you need):
        """Do not invoke directly. Use from_NNN methods."""
        # Implementation is likely a mix of A and B approaches. 

    @classmethod
    def from_string(cls, string):
        # Do any necessary preparations, use the `string`
        return cls(...)

    @classmethod
    def from_json_file(cls, file_object):
        # Read and interpret the file as you want
        return cls(...)

    @classmethod
    def from_docx_file(cls, file_object):
        # Read and interpret the file as you want, differently.
        return cls(...)

    # etc.

Nie można jednak łatwo uniemożliwić użytkownikowi bezpośredniego użycia konstruktora. (Jeśli jest to krytyczne, jako zabezpieczenie podczas programowania, możesz przeanalizować stos wywołań w konstruktorze i sprawdzić, czy wywołanie zostało wykonane z jednej z oczekiwanych metod.)

 25
Author: 9000,
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-07-17 12:01:59

Większość Pythoniczna byłaby tym, co już robi standardowa biblioteka Pythona. Główny programista Raymond Hettinger (the collections guy) wygłosił wykład na ten temat , plus ogólne wskazówki jak pisać klasy.

Używaj oddzielnych funkcji na poziomie klasy do inicjalizacji instancji, jak np. dict.fromkeys() nie jest inicjalizatorem klasy, ale nadal zwraca instancję dict. Pozwala to na elastyczny dobór argumentów bez zmiany podpisów metod w miarę zmiany wymagań.

 16
Author: Arya McCarthy,
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-07-17 14:29:31

Jakie są cele systemowe tego kodu? Z mojego punktu widzenia, twoje krytyczne zdanie to but the user has to know which method he requires. jakie doświadczenie chcesz, aby Twoi użytkownicy mieli z Twoim kodem? To powinno napędzać projekt interfejsu.

Teraz przejdźmy do możliwości konserwacji: które rozwiązanie jest najłatwiejsze do odczytania i utrzymania? Ponownie, czuję, że rozwiązanie C jest gorsze. Dla większości zespołów, z którymi pracowałem, rozwiązanie B jest lepsze niż a: jest trochę łatwiejsze do odczytania i zrozumienia, chociaż oba łatwo włamują się do małych kody do leczenia.

 4
Author: Prune,
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-06-26 17:46:39

Nie jestem pewien, czy dobrze zrozumiałem, ale czy to nie zadziała?

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        ...
    else:
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

Lub możesz nawet zrobić:

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        # Implement get_data to return all the stuff as a tuple
        timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp
    self.data = data
    self.metadata = metadata

Dzięki radom Jona Kiparsky ' ego jest lepszy sposób na uniknięcie globalnych deklaracji na data imetadata więc jest to nowy sposób:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        # Implement get_data to return all the stuff as a tuple
        with open(file) as f:
            timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp or 0
    self.data = data or []
    self.metadata = metadata or {}
 4
Author: Gabriel Ecker,
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-06-26 18:21:02

Jeśli korzystasz z Pythona 3.4+ możesz użyć functools.singledispatch dekorator do tego (z małą dodatkową pomocą od methoddispatch dekoratora, który @ZeroPiraeus napisał dla jego odpowiedź):

class Container:

    @methoddispatch
    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    @__init__.register(File)
    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    @__init__.register(Timestamp)
    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata
 3
Author: Sean Vieira,
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-06-28 04:52:11

Najbardziej pythonicznym sposobem jest upewnienie się, że dowolne opcjonalne argumenty mają wartości domyślne. Więc dołącz wszystkie argumenty, które wiesz, że potrzebujesz i przypisać im odpowiednie wartości domyślne.

def __init__(self, timestamp=None, data=[], metadata={}):
    timestamp = time.now()

Ważną rzeczą do zapamiętania jest to, że wszelkie wymagane argumenty powinny nie mieć wartości domyślne, ponieważ chcesz, aby błąd został podniesiony, jeśli nie są uwzględnione.

Możesz zaakceptować jeszcze więcej opcjonalnych argumentów używając *args i **kwargs na końcu listy argumentów.

def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards):
    if 'something' in kwargs:
        # do something
 0
Author: Soviut,
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-06-26 17:53:42