Je ne trouve pas de réponse définitive à cela. Autant que je sache, vous ne pouvez pas avoir plusieurs fonctions __init__
dans une classe Python. Alors, comment puis-je résoudre ce problème?
Supposons que j'ai une classe appelée Cheese
avec la propriété number_of_holes
. Comment puis-je avoir deux façons de créer des objets de fromage ...
parmesan = Cheese(num_holes = 15)
number_of_holes
: gouda = Cheese()
Je ne peux penser qu'à une seule façon de faire cela, mais cela semble un peu maladroit:
class Cheese():
def __init__(self, num_holes = 0):
if (num_holes == 0):
# randomize number_of_holes
else:
number_of_holes = num_holes
Que dis-tu? Y a-t-il un autre moyen?
En fait, None
est bien meilleur pour les valeurs "magiques":
class Cheese():
def __init__(self, num_holes = None):
if num_holes is None:
...
Maintenant, si vous voulez la liberté complète d'ajouter plus de paramètres:
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())
Pour mieux expliquer le concept de *args
et **kwargs
(vous pouvez réellement changer ces noms):
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}
Utiliser num_holes=None
par défaut est acceptable si vous n’avez que __init__
.
Si vous voulez plusieurs "constructeurs" indépendants, vous pouvez les fournir en tant que méthodes de classe. Celles-ci sont généralement appelées méthodes d'usine. Dans ce cas, la valeur par défaut pour num_holes
serait 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))
Maintenant, créez un objet comme ceci:
gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()
Toutes ces réponses sont excellentes si vous souhaitez utiliser des paramètres facultatifs, mais une autre possibilité Pythonic consiste à utiliser une méthode de classe pour générer un pseudo-constructeur de style usine:
def __init__(self, num_holes):
# do stuff with the number
@classmethod
def fromRandom(cls):
return cls( # some-random-number )
Pourquoi pensez-vous que votre solution est "maladroite"? Personnellement, je préférerais un constructeur avec des valeurs par défaut à plusieurs constructeurs surchargés dans des situations comme la vôtre (Python ne supporte pas la surcharge de méthodes de toute façon):
def __init__(self, num_holes=None):
if num_holes is None:
# Construct a gouda
else:
# custom cheese
# common initialization
Pour les cas vraiment complexes avec beaucoup de constructeurs différents, il pourrait être plus simple d'utiliser des fonctions d'usine différentes à la place:
@classmethod
def create_gouda(cls):
c = Cheese()
# ...
return c
@classmethod
def create_cheddar(cls):
# ...
Dans votre exemple de fromage, vous voudrez peut-être utiliser une sous-classe de fromage Gouda ...
Ce sont de bonnes idées pour votre mise en œuvre, mais si vous présentez une interface de fabrication de fromage à un utilisateur. Ils se moquent du nombre de trous dans le fromage ou de ce que les internes entrent dans la fabrication du fromage. L'utilisateur de votre code veut juste "gouda" ou "parmesean" non?
Alors pourquoi ne pas faire ceci:
# cheese_user.py
from cheeses import make_gouda, make_parmesean
gouda = make_gouda()
paremesean = make_parmesean()
Et ensuite, vous pouvez utiliser l’une des méthodes ci-dessus pour implémenter les fonctions:
# 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)
C'est une bonne technique d'encapsulation, et je pense que c'est plus Pythonic. Pour moi, cette façon de faire correspond davantage à la dactylographie du canard. Vous demandez simplement un objet de gouda et vous ne vous souciez pas vraiment de la classe.
Il faut absolument préférer les solutions déjà publiées, mais comme personne n’a encore mentionné cette solution, je pense que cela vaut la peine d’être mentionné pour l’exhaustivité.
L'approche @classmethod
peut être modifiée pour fournir un constructeur alternatif qui n'invoque pas le constructeur par défaut (__init__
). Au lieu de cela, une instance est créée à l'aide de __new__
.
Cela peut être utilisé si le type d'initialisation ne peut pas être sélectionné en fonction du type de l'argument constructeur et que les constructeurs ne partagent pas le code.
Exemple:
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
La meilleure réponse est celle ci-dessus concernant les arguments par défaut, mais je me suis amusé à l'écrire, et cela convient certainement pour "plusieurs constructeurs". À utiliser à vos risques et périls.
Qu'en est-il de la méthode new .
"Les implémentations typiques créent une nouvelle instance de la classe en appelant la méthode new () de la superclasse à l'aide de super (currentclass, cls) .new (cls [ ...]) avec les arguments appropriés, puis l'instance nouvellement créée selon les besoins avant de la renvoyer. "
Vous pouvez donc faire en sorte que la méthode new modifie la définition de votre classe en attachant la méthode du constructeur appropriée.
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 __== "__main__":
parm = Cheese(num_holes=5)
Utilisez plutôt num_holes=None
par défaut. Ensuite, vérifiez si num_holes is None
et, le cas échéant, de manière aléatoire. C'est ce que je vois généralement, de toute façon.
Des méthodes de construction plus radicalement différentes peuvent justifier une méthode de classe qui retourne une instance de cls
.
J'utiliserais l'héritage. Surtout s'il y aura plus de différences que de nombres de trous. Surtout si Gouda devra avoir un ensemble de membres différent de celui de Parmesan.
class Gouda(Cheese):
def __init__(self):
super(Gouda).__init__(num_holes=10)
class Parmesan(Cheese):
def __init__(self):
super(Parmesan).__init__(num_holes=15)
Puisque ma réponse initiale a été critiquée sur la base que mes constructeurs spéciaux n'ont pas appelé le constructeur (unique) par défaut, je publie ici une version modifiée qui respecte les souhaits de tous les constructeurs d'appeler le constructeur un:
class Cheese:
def __init__(self, *args, _initialiser="_default_init", **kwargs):
"""A multi-initialiser.
"""
getattr(self, _initialiser)(*args, **kwargs)
def _default_init(self, ...):
"""A user-friendly smart or general-purpose initialiser.
"""
...
def _init_parmesan(self, ...):
"""A special initialiser for Parmesan cheese.
"""
...
def _init_gauda(self, ...):
"""A special initialiser for Gauda cheese.
"""
...
@classmethod
def make_parmesan(cls, *args, **kwargs):
return cls(*args, **kwargs, _initialiser="_init_parmesan")
@classmethod
def make_gauda(cls, *args, **kwargs):
return cls(*args, **kwargs, _initialiser="_init_gauda")
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
Voici comment je l'ai résolu pour une classe YearQuarter
que je devais créer. J'ai créé un __init__
avec un seul paramètre appelé value
. Le code pour le __init__
décide simplement du type de paramètre value
et traite les données en conséquence. Si vous voulez plusieurs paramètres d’entrée, il vous suffit de les regrouper dans un seul Tuple et d’essayer de définir value
comme Tuple.
Vous l'utilisez comme ceci:
>>> temp = YearQuarter(datetime.date(2017, 1, 18))
>>> print temp
2017-Q1
>>> temp = YearQuarter((2017, 1))
>>> print temp
2017-Q1
Et voici à quoi ressemblent le __init__
et le reste de la classe:
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)
Vous pouvez bien sûr développer le __init__
avec plusieurs messages d'erreur. Je les ai omis pour cet exemple.
C'est assez propre je suppose et difficile
class A(object):
def __init__(self,e,f,g):
self.__dict__.update({k: v for k,v in locals().items() if k!='self'})
def bc(self):
print(self.f)
k=A(e=5,f=6,g=12)
k.bc() # >>>6