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.
- podaj plik zawierający dane
- przekazać dane bezpośrednio przez argumenty
- 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?
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 .
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.)
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ń.
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.
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 {}
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
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
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