80 votes

Python dispose-t-il de classes anonymes ?

Je me demande si Python a quelque chose comme la fonctionnalité des classes anonymes de C#. Pour clarifier, voici un exemple d'extrait C# :

var foo = new { x = 1, y = 2 };
var bar = new { y = 2, x = 1 };
foo.Equals(bar); // "true"

En Python, j'imaginerais quelque chose comme ceci :

foo = record(x = 1, y = 2)
bar = record(y = 2, x = 1)
foo == bar  # true

L'exigence spécifique est de pouvoir créer un objet avec des champs spécifiés dans un contexte d'expression (par exemple, utilisable dans des lambdas et d'autres endroits où les déclarations ne sont pas autorisées), sans déclarations externes supplémentaires, et de pouvoir accéder à des composants individuels par leur nom via la syntaxe normale d'accès aux membres. foo.bar . L'objet créé doit également mettre en œuvre la comparaison structurelle par les noms des composants (et non par position, comme le font les tuples).

En particulier : les tuples ne le sont pas parce que leurs composants ne sont pas nommés ; les classes ne le sont pas parce qu'elles nécessitent une déclaration ; les dicts ne le sont pas parce qu'elles ont des effets indésirables sur la qualité de l'environnement. foo["bar"] pour accéder aux composants.

namedtuple Ce n'est pas le cas, car il faut toujours un nom même si vous définissez le type en ligne, et la comparaison est basée sur la position et non sur le nom. En particulier :

 def foo(): return namedtuple("Foo", "x y")(x = 1, y = 2)
 def bar(): return namedtuple("Foo", "y x")(x = 1, y = 2)
 foo() == bar()   # False because fields are compared in order, and not by name
                  # True would be desired instead

Je sais comment écrire une telle chose en Python si nécessaire. Mais j'aimerais savoir s'il y a quelque chose de ce genre dans la bibliothèque standard de Python, ou dans toute autre bibliothèque tierce populaire.

[EDIT]

Juste pour le plaisir, voici une solution à une seule expression qui combine deux réponses très instructives de Ken et alanlcode, produisant une égalité structurelle sans aucune déclaration extérieure supplémentaire :

type("", (), { \
    "__init__": (lambda self, **kwargs: self.__dict__.update(kwargs)), \
    "__eq__": (lambda self, other: self.__dict__ == other.__dict__) } \
)(x = 1, y = 2)

Techniquement, il répond à toutes les exigences de la question, mais j'espère sincèrement que personne ne l'utilisera jamais (je ne le ferai certainement pas).

2 votes

On dirait que les dictionnaires devraient faire l'affaire. Je trouve que c'est mieux de trouver la façon de faire en python que d'intégrer un autre langage dans python. BTW -- si vous n'aimez pas la méthode d'accès au dictionnaire, foo["bar"] une alternative est d'utiliser la méthode get : foo.get("bar")

1 votes

Étant donné qu'il est trivialement possible de l'implémenter en Python si nécessaire, je ne vois pas de raison particulière de ne pas le faire, et je ne considère absolument pas que c'est "adapter un autre langage à Python". D'autant plus que cela semble assez proche de l'existant namedtuple dans l'intention.

2 votes

Je trouve bizarre de poser la question de savoir si la langue X a la caractéristique de la langue Y, et d'exiger ensuite que tout soit exactement pareil. Les langues ne sont pas exactement les mêmes. Python n'a pas de fonctions anonymes, mais ils ont des dictionnaires, et ils fonctionnent tout aussi bien. Oui, la syntaxe d'accès est différente. La belle affaire.

63voto

Pavel Minaev Points 60647

Il semble que Python 3.3 ait ajouté exactement cette chose sous la forme de types.SimpleNamespace classe.

6 votes

Cette réponse devrait être en tête de liste.

0 votes

Il y a tellement de bits cachés de python qui devraient vraiment être plus proéminents et intégrés dans le langage comme ceci. Il semble qu'il y ait une certaine réticence à moderniser le langage lui-même plutôt que d'y ajouter ces bollards. La réponse a fonctionné pour moi, je vais juste devoir y revenir chaque fois que je voudrai le faire !

1 votes

Y a-t-il un indice de type anonyme pour aller avec SimpleNamespace ? Imaginons que je définisse une fonction qui renvoie un SimpleNamespace(x= ..., y= ...), je veux être en mesure d'indiquer le résultat, de la manière suivante : def func() -> SimpleNamespace(x: int, y: str) (la syntaxe devra toutefois être modifiée). Ceci est similaire à ce que l'on peut faire dans le cas d'un tuple comme type de retour (à savoir, def func() -> Tuple[int, str] ).

53voto

dF. Points 29787

La méthode pythique consisterait à utiliser un fichier dict :

>>> foo = dict(x=1, y=2)
>>> bar = dict(y=2, x=1)
>>> foo == bar
True

Il répond à toutes vos exigences sauf que vous devez encore faire foo['x'] au lieu de foo.x .

Si cela pose un problème, vous pouvez facilement définir une classe telle que :

class Bunch(object):
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

Ou, un joli et court

class Bunch(dict):
    __getattr__, __setattr__ = dict.get, dict.__setitem__

(mais notez que cette deuxième a des problèmes comme le souligne Alex dans son commentaire !)

7 votes

De la question - " ... dicts n'est-ce pas parce qu'ils ont la syntaxe indésirable foo["bar"] pour accéder aux composants."

0 votes

Cela fonctionne, sauf que pour accéder aux choses c'est foo['x'], pas foo.x

0 votes

Comme je l'ai mentionné dans mon commentaire principal, les dictionnaires ont également une méthode "get" pour retirer la valeur d'une clé : foo.get('bar')

48voto

alanlcode Points 2843

1) Voir http://uszla.me.uk/space/blog/2008/11/06 . Vous pouvez créer un objet anonyme avec une syntaxe un peu moche en utilisant la fonction type fonction intégrée :

 anon_object_2 = type("", (), {})()

où le 3ème paramètre est le dict qui contiendra les champs de votre objet.

 foo = type("", (), dict(y=1))()
 foo.y == 1

2) Une autre variante est proposée par Peter Norvig à l'adresse suivante http://norvig.com/python-iaq.html . Elle est également similaire à la réponse postée par Ken.

class Struct:
    def __init__(self, **entries): self.__dict__.update(entries)

>>> options = Struct(answer=42, linelen = 80, font='courier')
>>> options.answer
42

L'avantage de cette méthode est que vous pouvez implémenter l'égalité par le contenu du dict, ce que la première option ne permet pas.

0 votes

J'aime bien la classe "struct". Elle m'a été très utile, car mon problème ne nécessitait pas de méthodes ou d'opérateurs particuliers. En bref, j'utilise suds/soap et la plupart du temps, suds construit pour moi un objet 'reply' dont la structure est définie par un WSDL. Si vous obtenez un mauvais XML, l'analyseur syntaxique de Saxe lève une exception et ne vous laisse pas d'objet de réponse. Je 'simule' un objet de réponse avec la classe Struct ci-dessus (en définissant seulement les propriétés 'error' et 'message' selon mon application) et je le passe en aval. Si la gestion des erreurs attend plus de propriétés (ou de méthodes ?), il est facile d'ajouter (étendre ?) cette classe pour y répondre. WIN.

6voto

ars Points 35803

La forme type(...) ne répond pas à l'exigence de comparaison structurelle (sans devenir vraiment laide). La forme dict(...) ne répond pas à l'exigence relative aux accesseurs d'attributs.

El attrdict semble se situer quelque part au milieu :

class attrdict(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.__dict__ = self

a = attrdict(x=1, y=2)
b = attrdict(y=2, x=1)

print a.x, a.y
print b.x, b.y
print a == b

Mais cela implique de définir une classe spéciale.

OK, je viens de remarquer la mise à jour de la question. Je note juste que vous pouvez spécifier dict pour le paramètre de base et il suffit alors de spécifier le constructeur (dans l'expression de type icky). Je préfère attrdict :-)

1 votes

Définir self.__dict__ = self provoque une fuite de mémoire, donc je vous le déconseille. bugs.python.org/issue1469629

0 votes

N'est-ce pas le __init__ L'appel est censé ressembler à ceci super(attrdict, self).__init__(*args, **kwargs) ?

6voto

Ken Points 1584

Je ne me souviens pas de mémoire s'il y a un intégré mais l'écrire soi-même est plus court que de taper sa question :-)

class record(object):
  def __init__(self, **kwargs): self.__dict__ = kwargs
  def __eq__(self, r2): return self.__dict__ == r2.__dict__
  def __ne__(self, r2): return self.__dict__ != r2.__dict__

foo = record(x=1, y=2)
bar = record(y=2, x=1)
foo == bar  # => true

1 votes

Neat (je savais comment faire en général, mais je n'avais pas réalisé que c'était aussi simple). Maintenant, passons à la vraie question : seriez-vous prêt à soumettre un PEP pour ce qui précède ? :)

2 votes

Au fait, est-ce que __neq__ vraiment nécessaire ? La définition par défaut n'est-elle pas not __eq__ fournis automatiquement ?

1 votes

Pavel : c'est ce que j'ai pensé au départ, mais lorsque j'ai essayé, cela ne semblait pas fonctionner de cette façon (mais il est tout à fait possible que je me sois trompé).

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