906 votes

Qu'est-ce qu'une façon propre et pythonique d'avoir plusieurs constructeurs en Python?

Je ne peux pas trouver une réponse définitive à cette. Autant que je sache, vous ne pouvez pas avoir plusieurs __init__ fonctions dans une classe Python. Donc, ce est une bonne façon de résoudre ce problème?

Supposons que j'ai une classe appelée Cheese avec l' number_of_holes de la propriété. Comment puis-je avoir deux manières de créer fromage-objets...

  • celui qui prend un certain nombre de trous comme ceci: parmesan = Cheese(num_holes = 15)
  • et celui qui ne prend pas d'arguments et juste rend aléatoire l' number_of_holes bien: gouda = Cheese()

Je peux penser qu'à une seule façon de le faire, mais qui 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

Qu'en dites-vous? Est-il un meilleur moyen?

944voto

vartec Points 53382

En fait, None est beaucoup mieux pour les valeurs "magiques":

 class Cheese():
    def __init__(self, num_holes = None):
        if(num_holes is None):
            ...
 

Maintenant, si vous voulez une liberté totale 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 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}
 

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

905voto

Ber Points 10364

Utiliser num_holes=None comme valeur par défaut est correct si vous allez avoir juste __init__ .

Si vous voulez plusieurs "constructeurs" indépendants, vous pouvez les fournir en tant que méthodes de classe. Ils sont généralement appelés méthodes d'usine. Dans ce cas, vous pouvez avoir la valeur par défaut pour 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(random(100))

    @classmethod
    def slightly_holey(cls):
        return cls(random(33))

    @classmethod
    def very_holey(cls):
        return cls(random(66, 100))
 

Maintenant, créez un objet comme celui-ci:

 gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()
 

31voto

Yes - that Jake. Points 9184

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 )
 

21voto

Ferdinand Beyer Points 27723

Pourquoi pensez-vous que votre solution est "maladroit"? Personnellement, je préférerais un constructeur avec des valeurs par défaut sur plusieurs constructeurs surchargés dans des situations comme la vôtre (Python ne prend pas en charge la surcharge de méthode, 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 complexes avec beaucoup de différents constructeurs, il pourrait être plus propre d'utiliser différents d'usine des fonctions à la place:

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

Dans votre fromage exemple, vous pourriez vouloir utiliser un Gouda sous-classe de Fromage...

19voto

Brad C Points 427

Ce sont de bonnes idées pour votre mise en œuvre, mais si vous êtes à la présentation d'une fabrication de fromage d'une interface utilisateur. Ils ne se soucient pas combien de trous du 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 ça:

# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()

Et puis, vous pouvez utiliser l'une des méthodes ci-dessus pour mettre en œuvre effectivement 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 encapsulation technique, et je pense que c'est plus Pythonic. Pour moi, cette façon de faire s'inscrit davantage dans la ligne de plus avec le duck-typing. Vous êtes tout simplement demander un gouda objet et vous n'avez pas vraiment dans quelle classe il est.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X