Plusieurs constructeurs: le Pythonic? [dupliquer]
cette question a déjà une réponse ici:
j'ai une classe de conteneur qui contient des données. Lorsque le conteneur est créé, il existe différentes méthodes pour transmettre des données.
- Passer un fichier qui contient les données
- passer les données directement via les arguments
- Ne pas transmettre les données; il suffit de créer un conteneur vide
en Java, je créerais trois constructeurs. Voici à quoi ça ressemblerait si C'était possible en Python:
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
en Python, je vois trois solutions évidentes, mais aucune n'est jolie:
A : utilisation d'arguments de mots clés:
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 : utilisant les arguments par défaut:
def __init__(self, file=None, timestamp=None, data=None, metadata=None):
if file:
...
elif timestamp and data and metadata:
...
else:
... create empty container
C : fournir seulement un constructeur pour créer des conteneurs vides. Fournir des méthodes pour remplir les contenants à l'aide de données provenant de différentes sources.
def __init__(self):
self.timestamp = 0
self.data = []
self.metadata = {}
def add_data_from_file(file):
...
def add_data(timestamp, data, metadata):
...
les Solutions A et B sont essentiellement les mêmes. Je n'aime pas faire le SI / AUTREMENT, d'autant plus que je dois vérifier si tous les arguments requis pour cette méthode ont été fournis. A est un peu plus flexible que B si le code doit jamais être étendu par une quatrième méthode pour ajouter des données.
Solution C semble être le plus beau, mais l'utilisateur doit savoir quelle méthode il exige. Par exemple: il ne peut pas faire c = Container(args)
s'il ne sait pas ce qu'est args
.
Quelle est la solution la plus pythonique?
7 réponses
vous ne pouvez pas avoir plusieurs méthodes avec le même nom dans Python
. La surcharge de la fonction-contrairement à Java
- n'est pas supportée.
utiliser les paramètres par défaut ou **kwargs
et *args
arguments.
vous pouvez faire des méthodes statiques ou des méthodes de classe avec le décorateur @staticmethod
ou @classmethod
pour retourner une instance de votre classe, ou pour ajouter d'autres constructeurs.
je vous conseille de faire:
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
փ N'ont jamais de type mutable comme par défaut en python. ⚠ Voir ici .
vous ne pouvez pas avoir plusieurs constructeurs, mais vous pouvez avoir plusieurs méthodes d'usine correctement nommées.
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.
Vous ne pouvez pas facilement empêcher l'utilisateur d'utiliser le constructeur directement. (Si c'est critique, comme précaution de sécurité pendant le développement, vous pouvez analyser la pile d'appels dans le constructeur et vérifier que l'appel est fait à partir de l'une des méthodes attendues.)
la plupart de Pythonic serait ce que la bibliothèque standard de Python fait déjà. Le développeur principal Raymond Hettinger (le collections
guy) a donné un exposé sur ce , plus des directives générales pour la façon d'écrire des classes.
utilise des fonctions séparées au niveau de la classe pour initialiser les instances, comme dict.fromkeys()
n'est pas la classe initialiseur, mais retourne quand même une instance de dict
. Cela vous permet d'être flexible vers les arguments dont vous avez besoin sans changer les signatures de la méthode au fur et à mesure que les exigences changent.
Quels sont les objectifs du système pour ce code? De mon point de vue, votre phrase critique est but the user has to know which method he requires.
quelle expérience voulez-vous que vos utilisateurs aient avec votre code? Ça devrait être le moteur de la conception de l'interface.
maintenant, passez à la maintenabilité: quelle solution est la plus facile à lire et à maintenir? Encore une fois, je pense que la solution C est inférieure. Pour la plupart des équipes avec lesquelles j'ai travaillé, la solution B est préférable à la solution A: elle est un peu plus facile à lire et à comprendre, bien que tous deux cassent facilement en petits blocs de code pour le traitement.
Je ne sais pas si j'ai bien compris, mais ça ne marcherait pas?
def __init__(self, file=None, timestamp=0, data=[], metadata={}):
if file:
...
else:
self.timestamp = timestamp
self.data = data
self.metadata = metadata
Ou vous pouvez même le faire:
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
merci à Jon Kiparsky Conseil Il ya une meilleure façon d'éviter les déclarations globales sur data
et metadata
donc c'est la nouvelle façon:
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 {}
si vous êtes sur Python 3.4+ vous pouvez utiliser le functools.singledispatch
décorateur pour faire ceci (avec un peu d'aide supplémentaire du methoddispatch
décorateur que @ZeroPiraeus a écrit pour sa réponse ):
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
la façon la plus pythonique est de s'assurer que tous les arguments optionnels ont des valeurs par défaut. Incluez donc tous les arguments dont vous savez que vous avez besoin et assignez-leur les valeurs par défaut appropriées.
def __init__(self, timestamp=None, data=[], metadata={}):
timestamp = time.now()
une chose importante à retenir est que tous les arguments requis devraient et non avoir des défauts puisque vous voulez qu'une erreur soit soulevée si elles ne sont pas incluses.
vous pouvez accepter encore plus d'arguments optionnels en utilisant *args
et **kwargs
à la fin de votre liste d'arguments.
def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards):
if 'something' in kwargs:
# do something