web-dev-qa-db-fra.com

Équivalent de modèle de générateur dans Python

En Java, vous pouvez utiliser le modèle de générateur pour fournir un moyen plus lisible d'instancier une classe avec de nombreux paramètres. Dans le modèle de générateur, on construit un objet de configuration avec des méthodes pour définir des attributs nommés, puis l'utilise pour construire un autre objet.

Quel est l'équivalent en Python? La meilleure façon d'imiter la même implémentation?

44
name_masked

Les modèles de conception peuvent souvent être remplacés par des fonctionnalités de langage intégrées.

Votre cas d'utilisation

Vous dites "je voulais avoir un moyen plus lisible" pour "instancier une classe avec de nombreux paramètres". Dans le cas de Java:

[Un] cas d'utilisation pour le modèle de générateur est lorsque le constructeur de l'objet à construire doit prendre de très nombreux paramètres. Dans de tels cas, il est souvent plus pratique de regrouper ces paramètres de configuration dans un objet de générateur (setMaxTemperature(int t), setMinTemperature(int t), set .., etc.) que de surcharger l'appelant avec une longue liste d'arguments à passer dans le constructeur de la classe. .

Modèle de générateur non nécessaire

Mais Python prend en charge les paramètres nommés , ce n'est donc pas nécessaire. Vous pouvez simplement définir constructeur d'une classe:

class SomeClass(object):
    def __init__(self, foo="default foo", bar="default bar", baz="default baz"):
        # do something

et appelez-le en utilisant des paramètres nommés:

s = SomeClass(bar=1, foo=0)

Notez que vous pouvez réorganiser et omettre librement les arguments, tout comme avec un générateur dans Java vous pouvez omettre ou réorganiser les appels aux méthodes set sur l'objet générateur.

Il convient également de préciser que la nature dynamique de Python vous donne plus de liberté sur la construction d'objets (en utilisant __new__ Etc.), ce qui peut remplacer d'autres utilisations du modèle de générateur.

Mais si vous voulez vraiment l'utiliser

vous pouvez utiliser collections.namedtuple comme objet de configuration. namedtuple() renvoie un nouveau type représentant un Tuple, dont chacun des paramètres a un nom donné, sans avoir à écrire une classe passe-partout. Vous pouvez utiliser des objets du type résultant d'une manière similaire à Java builders. (Merci à Paul McGuire de l'avoir suggéré.)

StringBuilder

Un modèle associé est le StringBuilder de Java, qui est utilisé pour construire efficacement un String (immuable) par étapes. En Python, cela peut être remplacé par str.join. Par exemple:

final StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++)
    sb.append("Hello(" + i + ")");
return sb.toString();

peut être remplacé par

return "".join("Hello({})".format(i) for i in range(100))
87
Mechanical snail

L'OP s'est mis en place pour une chute en lançant le modèle Builder comme Java spécifique. Ce n'est pas le cas. Il est dans le Livre de Gang of Four et est potentiellement pertinent pour tout objet langage orienté.

Malheureusement, même le article Wikipedia sur le modèle Builder ne lui donne pas assez de crédit. Ce n'est pas simplement utile pour l'élégance du code. Les modèles de générateur sont un excellent moyen de créer des objets immuables qui doivent être mutables jusqu'à ce qu'ils soient utilisés. L'état immuable est particulièrement critique dans les paradigmes fonctionnels, ce qui rend le Builder un excellent modèle orienté objet pour python.

J'ai fourni un exemple d'implémentation Builder + ImmutableObject ci-dessous en utilisant collections.namedtuple , emprunté et modifié de " Comment faire un objet immuable en python ". J'ai gardé le Builder assez simple. Cependant, des fonctions de définition peuvent être fournies qui renvoient le générateur lui-même pour permettre le chaînage d'appels. Ou la syntaxe @property peut être utilisée dans le générateur pour fournir des fixateurs d'attributs qui vérifient la validité des attributs avant de les définir.

from collections import namedtuple

IMMUTABLE_OBJECT_FIELDS = ['required_function_result', 'required_parameter', 'default_parameter']

class ImmutableObjectBuilder(object):
    def __init__(self, required_function, required_parameter, default_parameter="foo"):
        self.required_function = required_function
        self.required_parameter = required_parameter
        self.default_parameter = default_parameter

    def build(self):
        return ImmutableObject(self.required_function(self.required_parameter),
                               self.required_parameter,
                               self.default_parameter)

class ImmutableObject(namedtuple('ImmutableObject', IMMUTABLE_OBJECT_FIELDS)):
    __slots__ = ()

    @property
    def foo_property(self):
        return self.required_function_result + self.required_parameter

    def foo_function(self):
        return self.required_function_result - self.required_parameter

    def __str__(self):
        return str(self.__dict__)

Exemple d'utilisation:

my_builder = ImmutableObjectBuilder(lambda x: x+1, 2)
obj1 = my_builder.build()
my_builder.default_parameter = "bar"
my_builder.required_parameter = 1
obj2 = my_builder.build()
my_builder.required_function = lambda x: x-1
obj3 = my_builder.build()

print obj1
# prints "OrderedDict([('required_function_result', 3), ('required_parameter', 2), ('default_parameter', 'foo')])"
print obj1.required_function_result
# prints 3
print obj1.foo_property
# prints 5
print obj1.foo_function()
# prints 1
print obj2
# prints "OrderedDict([('required_function_result', 2), ('required_parameter', 1), ('default_parameter', 'bar')])"
print obj3
# prints "OrderedDict([('required_function_result', 0), ('required_parameter', 1), ('default_parameter', 'bar')])"

Dans cet exemple, j'ai créé trois ImmutableObjects, tous avec des paramètres différents. J'ai donné à l'appelant la possibilité de copier, modifier et faire circuler une configuration modifiable sous la forme du générateur tout en garantissant l'immuabilité des objets construits. La définition et la suppression d'attributs sur les ImmutableObjects entraîneront des erreurs.

Conclusion: Les constructeurs sont un excellent moyen de faire circuler quelque chose avec un état mutable qui fournit un objet avec un état immuable lorsque vous êtes prêt à l'utiliser. Ou, autrement dit, les constructeurs sont un excellent moyen de fournir des régleurs d'attributs tout en garantissant un état immuable. Ceci est particulièrement précieux dans les paradigmes fonctionnels.

34
Malina

Je ne suis pas d'accord avec @MechanicalSnail. Je pense qu'une implémentation de générateur similaire à celle référencée par l'affiche est toujours très utile dans certains cas. Les paramètres nommés vous permettront uniquement de définir simplement des variables membres. Si vous voulez faire quelque chose d'un peu plus compliqué, vous n'avez pas de chance. Dans mon exemple, j'utilise le modèle de générateur classique pour créer un tableau.

class Row_Builder(object):
  def __init__(self):
    self.row = ['' for i in range(170)]

  def with_fy(self, fiscal_year):
    self.row[FISCAL_YEAR] = fiscal_year
    return self

  def with_id(self, batch_id):
    self.row[BATCH_ID] = batch_id
    return self

  def build(self):
    return self.row

En l'utilisant:

row_FY13_888 = Row_Builder().with_fy('FY13').with_id('888').build()
13
CowboyBebop

Le modèle de générateur dans Java peut être facilement réalisé dans python en utilisant une variante de:

MyClass(self, required=True, someNumber=<default>, *args, **kwargs)

required et someNumber sont un exemple pour afficher les paramètres requis avec une valeur par défaut, puis lire pour les arguments variables tout en gérant le cas où il pourrait y avoir None

Si vous n'avez jamais utilisé d'arguments variables auparavant, reportez-vous à this

4
Pratik Mandrekar