web-dev-qa-db-fra.com

Compréhensions OrderedDict

Puis-je étendre la syntaxe dans python pour les compréhensions dict pour d'autres dicts, comme le OrderedDict dans le module collections ou mes propres types qui héritent de dict?

Le simple fait de relier le nom dict ne fonctionne évidemment pas, le {key: value} la syntaxe de compréhension vous donne toujours un vieux dicton simple pour les compréhensions et les littéraux.

>>> from collections import OrderedDict
>>> olddict, dict = dict, OrderedDict
>>> {i: i*i for i in range(3)}.__class__
<type 'dict'>

Donc, si c'est possible, comment pourrais-je procéder? C'est OK si cela ne fonctionne que dans CPython. Pour la syntaxe, je suppose que je l'essayerais avec un O{k: v} préfixe comme nous l'avons sur le r'various' u'string' b'objects'.

note: Bien sûr, nous pouvons utiliser une expression de générateur à la place, mais je suis plus intéressé à voir comment piratable python est en termes de grammaire.

58
wim

Il n'y a aucun moyen direct de changer la syntaxe de Python à partir du langage. Une compréhension de dictionnaire (ou affichage simple) va toujours créer un dict, et vous ne pouvez rien y faire. Si vous utilisez CPython, il utilise des bytecodes spéciaux qui génèrent directement un dict, qui appellent finalement les fonctions de l'API PyDict et/ou les mêmes fonctions sous-jacentes utilisées par cette API. Si vous utilisez PyPy, ces bytecodes sont plutôt implémentés au-dessus d'un objet RPython dict qui à son tour est implémenté au-dessus d'un compilé et optimisé Python dict. Et ainsi de suite.

Il y a une manière indirecte de le faire, mais vous n'aimerez pas ça. Si vous lisez les documents sur le système d'importation , vous verrez que c'est l'importateur qui recherche le code compilé mis en cache ou appelle le compilateur, et le compilateur qui appelle l'analyseur, et ainsi de suite. Dans Python 3.3+, presque tout dans cette chaîne est écrit en Python pur, ou a une implémentation pure Python, ce qui signifie que vous pouvez forker le code et faites votre propre chose. Ce qui inclut l'analyse de la source avec votre propre code PyParsing qui construit les AST, ou la compilation d'un dic compréhension AST node dans votre propre bytecode personnalisé au lieu de la valeur par défaut, ou post-traitement du bytecode , ou…

Dans de nombreux cas, un import hook est suffisant; sinon, vous pouvez toujours écrire un Finder et un chargeur personnalisés.

Si vous n'utilisez pas déjà Python 3.3 ou version ultérieure, je vous suggère fortement de migrer avant de jouer avec ce genre de choses. Dans les anciennes versions, c'est plus difficile et moins bien documenté, et vous finirez par mettez 10 fois plus d'efforts pour apprendre quelque chose qui sera obsolète à chaque migration.

Quoi qu'il en soit, si cette approche vous semble intéressante, vous voudrez peut-être jeter un œil à MacroPy . Vous pouvez lui emprunter du code et, peut-être plus important encore, apprendre comment certaines de ces fonctionnalités (qui n'ont pas de bons exemples dans les documents) sont utilisées.

Ou, si vous êtes prêt à vous contenter de quelque chose de moins cool, vous pouvez simplement utiliser MacroPy pour construire une "macro de compréhension d'odict" et l'utiliser. (Notez que MacroPy ne fonctionne actuellement qu'en Python 2.7, pas 3.x.) Vous ne pouvez pas vraiment obtenir o{…}, mais vous pouvez obtenir, disons, od[{…}], ce qui n'est pas trop mal. Télécharger od.py , realmain.py et main.py et exécutez python main.py pour le voir fonctionner. La clé est ce code, qui prend un DictionaryComp AST, le convertit en un équivalent GeneratorExpr sur la valeur-clé Tuple s, et l'enveloppe dans un Call à collections.OrderedDict:

def od(tree, **kw):
    pair = ast.Tuple(elts=[tree.key, tree.value])
    gx = ast.GeneratorExp(elt=pair, generators=tree.generators)
    odict = ast.Attribute(value=ast.Name(id='collections'), 
                          attr='OrderedDict')
    call = ast.Call(func=odict, args=[gx], keywords=[])
    return call

Une alternative différente est, bien sûr, de modifier l'interpréteur Python.

Je suggère de supprimer le O{…} idée de syntaxe pour votre premier essai, et juste compiler les compréhensions de dict normales en odicts. La bonne nouvelle, c'est que vous n'avez pas vraiment besoin de changer la grammaire (qui est au-delà du poilu…), juste n'importe laquelle de:

  • les bytecodes que dictcomps compile,
  • la façon dont l'interprète exécute ces bytecodes, ou
  • l'implémentation du type PyDict

La mauvaise nouvelle, alors que tout cela est beaucoup plus facile que de changer la grammaire, aucun d'entre eux ne peut être fait à partir d'un module d'extension. (Eh bien, vous pouvez faire le premier en faisant fondamentalement la même chose que vous feriez avec du Python pur ... et vous pouvez faire n'importe lequel en accrochant le .so/.dll/.dylib pour patcher dans vos propres fonctions, mais c'est le même travail que le piratage sur Python plus le travail supplémentaire de hooking à l'exécution.)

Si vous voulez pirater source CPython , le code que vous voulez est dans Python/compile.c, Python/ceval.c, et Objects/dictobject.c, et le dev guide vous indique comment trouver tout ce dont vous avez besoin. Mais vous voudrez peut-être envisager de pirater --- source PyPy à la place, car il est principalement écrit dans (un sous-ensemble de) Python plutôt que C.


En remarque, votre tentative n'aurait pas fonctionné même si tout avait été fait au niveau de la langue Python. olddict, dict = dict, OrderedDict crée une liaison nommée dict dans les globaux de votre module, qui masque le nom dans les commandes internes, mais ne le remplace pas. Vous pouvez remplacer les choses dans les commandes internes (enfin, Python ne garantit pas cela, mais il existe une implémentation/version- des choses spécifiques qui se produisent pour chaque implémentation/version que j'ai essayée…), mais ce que vous avez fait n'est pas la façon de le faire.

29
abarnert

Désolé, pas possible. Les littéraux et les compréhensions de dict correspondent au type de dict intégré, d'une manière codée en dur au niveau C. Cela ne peut pas être ignoré.

Vous pouvez cependant l'utiliser comme alternative:

OrderedDict((i, i * i) for i in range(3))

Addendum: à partir de Python 3.6, tous les dictionnaires Python sont commandés. À partir de 3.7 , cela fait même partie des spécifications de langue). Si vous utilisez ces versions de Python, pas besoin de OrderedDict: la compréhension du dict fonctionnera simplement (TM).

85
Max Noel

En modifiant légèrement la réponse de @Max Noel, vous pouvez utiliser la compréhension de liste au lieu d'un générateur pour créer un OrderedDict de manière ordonnée (ce qui bien sûr n'est pas possible en utilisant la compréhension de dict).

>>> OrderedDict([(i, i * i) for i in range(5)])
OrderedDict([(0, 0), 
             (1, 1), 
             (2, 4), 
             (3, 9), 
             (4, 16)])
15
Alexander