Pouvez-vous ajouter de nouvelles instructions à la syntaxe de Python?


124

Pouvez - vous ajouter de nouvelles déclarations (comme print, raise, with) à la syntaxe de Python?

Dites, pour permettre ...

mystatement "Something"

Ou,

new_if True:
    print "example"

Pas tellement si vous le devriez , mais plutôt si c'est possible (à court de modifier le code des interpréteurs python)


10
Sur une note quelque peu connexe, un cas d'utilisation où il pourrait être pratique de créer de nouvelles déclarations à la volée (par opposition à «étendre» sérieusement le langage) est destiné aux personnes qui utilisent l'interpréteur interactif comme calculatrice, ou même un shell OS . Je crée souvent de petites fonctions jetables à la volée pour faire quelque chose que je vais répéter, et dans ces situations, ce serait bien de créer des commandes très abrégées comme des macros ou des instructions plutôt que de taper les noms longs avec la syntaxe function (). Bien sûr, ce n'est pas vraiment à quoi sert Py ... mais les gens passent beaucoup de temps à l'utiliser de manière interactive.
Kilo

5
@Kilo cela vaut peut-être la peine de regarder ipython - il a beaucoup de fonctionnalités shell, par exemple, vous pouvez utiliser les commandes "ls" et "cd" régulières, la complétion par tabulation, beaucoup de fonctionnalités macro-ish etc
dbr

Certains langages sont extrêmement extensibles, par exemple Forth et Smalltalk, mais leurs paradigmes de langage sont également différents de ceux utilisés par Python. Avec ces deux mots, tous les nouveaux mots (Forth) ou méthodes (Smalltalk) deviennent une partie intégrante et indiscernable du langage de cette installation. Ainsi, chaque installation Forth ou Smalltalk devient une création unique au fil du temps. Forth est également basé sur RPN. Mais en pensant aux DSL, quelque chose comme ça devrait être réalisable en Python. Cependant, comme d'autres l'ont dit ici, pourquoi?

1
En tant que personne maîtrisant à la fois Python et Forth, et qui a implémenté plusieurs compilateurs Forth ces dernières années, je peux contribuer ici avec un certain degré d'autorité. Sans obtenir un accès brut à l'analyseur interne de Python, c'est complètement impossible. Vous pouvez le simuler en prétraitant, comme l'illustrent les réponses (franchement, plutôt astucieuses!) Ci-dessous, mais il n'est pas possible de vraiment mettre à jour la syntaxe et / ou la sémantique du langage dans un interpréteur à chaud. C'est à la fois la malédiction de Python et son avantage sur les langages de type Lisp et Forth.
Samuel A. Falvo II

Réponses:


153

Vous pouvez trouver cela utile - Python internes: ajout d'une nouvelle instruction à Python , citée ici:


Cet article tente de mieux comprendre le fonctionnement du front-end de Python. La simple lecture de la documentation et du code source peut être un peu ennuyeuse, alors je prends une approche pratique ici: je vais ajouter une untildéclaration à Python.

Tout le codage de cet article a été effectué sur la branche de pointe Py3k dans le miroir du référentiel Python Mercurial .

La untildéclaration

Certains langages, comme Ruby, ont une untilinstruction, qui est le complément de while( until num == 0équivaut à while num != 0). En Ruby, je peux écrire:

num = 3
until num == 0 do
  puts num
  num -= 1
end

Et il imprimera:

3
2
1

Donc, je veux ajouter une capacité similaire à Python. Autrement dit, être capable d'écrire:

num = 3
until num == 0:
  print(num)
  num -= 1

Une digression linguistique

Cet article n'essaie pas de suggérer l'ajout d'un until instruction à Python. Bien que je pense qu'une telle déclaration clarifierait le code et que cet article montre à quel point il est facile à ajouter, je respecte complètement la philosophie du minimalisme de Python. Tout ce que j'essaie de faire ici, en réalité, c'est d'avoir un aperçu du fonctionnement interne de Python.

Modifier la grammaire

Python utilise un générateur d'analyseur personnalisé nommé pgen. Il s'agit d'un analyseur LL (1) qui convertit le code source Python en un arbre d'analyse. L'entrée du générateur d'analyseur est le fichier Grammar/Grammar[1] . Il s'agit d'un simple fichier texte qui spécifie la grammaire de Python.

[1] : À partir de là, les références aux fichiers dans la source Python sont données relativement à la racine de l'arborescence des sources, qui est le répertoire dans lequel vous exécutez configure et make pour construire Python.

Deux modifications doivent être apportées au fichier de grammaire. La première consiste à ajouter une définition pour l' untilinstruction. J'ai trouvé où l' whileinstruction a été définie ( while_stmt), et ajouté until_stmtci-dessous [2] :

compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite

[2] : Cela démontre une technique courante que j'utilise lors de la modification du code source que je ne connais pas: le travail par similarité . Ce principe ne résoudra pas tous vos problèmes, mais il peut certainement faciliter le processus. Étant donné que tout ce qui doit être fait whiledoit également l'être until, cela constitue une très bonne ligne directrice.

Notez que j'ai décidé d'exclure la elseclause de ma définition de until, juste pour la rendre un peu différente (et parce que franchement je n'aime pas leelse clause de boucles et ne pense pas qu'elle s'accorde bien avec le Zen de Python).

La deuxième modification consiste à modifier la règle pour compound_stmtinclure until_stmt, comme vous pouvez le voir dans l'extrait de code ci-dessus. C'est juste après while_stmt, encore.

Lorsque vous exécutez makeaprès la modification Grammar/Grammar, notez que le pgenprogramme est exécuté pour regénérer Include/graminit.het Python/graminit.c, puis plusieurs fichiers sont recompilés.

Modifier le code de génération AST

Une fois que l'analyseur Python a créé un arbre d'analyse, cet arbre est converti en AST, car les AST sont beaucoup plus simples à utiliser dans les étapes suivantes du processus de compilation.

Nous allons donc visiter Parser/Python.asdlce qui définit la structure des AST de Python et ajouter un nœud AST pour notre nouvelle untildéclaration, à nouveau juste en dessous de while:

| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)

Si vous exécutez maintenant make, notez qu'avant de compiler un tas de fichiers, Parser/asdl_c.pyest exécuté pour générer du code C à partir du fichier de définition AST. Ceci (comme Grammar/Grammar) est un autre exemple du code source Python utilisant un mini-langage (en d'autres termes, un DSL) pour simplifier la programmation. Notez également que puisqu'il Parser/asdl_c.pys'agit d'un script Python, il s'agit d'une sorte de bootstrapping - pour construire Python à partir de zéro, Python doit déjà être disponible.

Lors de la Parser/asdl_c.pygénération du code pour gérer notre nœud AST nouvellement défini (dans les fichiers Include/Python-ast.het Python/Python-ast.c), nous devons encore écrire le code qui convertit manuellement un nœud d'arbre d'analyse pertinent. Ceci est fait dans le fichier Python/ast.c. Là, une fonction nommée ast_for_stmtconvertit les nœuds d'arbre d'analyse pour les instructions en nœuds AST. Encore une fois, guidés par notre vieil ami while, nous sautons directement dans le grand switchpour gérer les instructions composées et ajoutons une clause pour until_stmt:

case while_stmt:
    return ast_for_while_stmt(c, ch);
case until_stmt:
    return ast_for_until_stmt(c, ch);

Maintenant, nous devons mettre en œuvre ast_for_until_stmt. C'est ici:

static stmt_ty
ast_for_until_stmt(struct compiling *c, const node *n)
{
    /* until_stmt: 'until' test ':' suite */
    REQ(n, until_stmt);

    if (NCH(n) == 4) {
        expr_ty expression;
        asdl_seq *suite_seq;

        expression = ast_for_expr(c, CHILD(n, 1));
        if (!expression)
            return NULL;
        suite_seq = ast_for_suite(c, CHILD(n, 3));
        if (!suite_seq)
            return NULL;
        return Until(expression, suite_seq, LINENO(n), n->n_col_offset, c->c_arena);
    }

    PyErr_Format(PyExc_SystemError,
                 "wrong number of tokens for 'until' statement: %d",
                 NCH(n));
    return NULL;
}

Encore une fois, cela a été codé en regardant de près l'équivalent ast_for_while_stmt, à la différence que untilj'ai décidé de ne pas appuyer l' elsearticle. Comme prévu, l'AST est créé de manière récursive, en utilisant d'autres fonctions de création AST comme ast_for_exprpour l'expression de condition et ast_for_suitepour le corps de l' untilinstruction. Enfin, un nouveau nœud nommé Untilest renvoyé.

Notez que nous accédons au nœud de l'arbre d'analyse en nutilisant des macros comme NCHet CHILD. Celles-ci méritent d'être comprises - leur code est en place Include/node.h.

Digression: composition AST

J'ai choisi de créer un nouveau type d'AST pour l' untilinstruction, mais en fait ce n'est pas nécessaire. J'aurais pu économiser du travail et implémenter la nouvelle fonctionnalité en utilisant la composition des nœuds AST existants, depuis:

until condition:
   # do stuff

Est fonctionnellement équivalent à:

while not condition:
  # do stuff

Au lieu de créer le Untilnœud dans ast_for_until_stmt, j'aurais pu créer un Notnœud avec un Whilenœud en tant qu'enfant. Étant donné que le compilateur AST sait déjà comment gérer ces nœuds, les étapes suivantes du processus peuvent être ignorées.

Compilation des AST en bytecode

L'étape suivante consiste à compiler l'AST en bytecode Python. La compilation a un résultat intermédiaire qui est un CFG (Control Flow Graph), mais comme le même code le gère, je vais ignorer ce détail pour le moment et le laisser pour un autre article.

Le code que nous examinerons ensuite est Python/compile.c. En suivant l'exemple de while, nous trouvons la fonction compiler_visit_stmt, qui est responsable de la compilation des instructions en bytecode. Nous ajoutons une clause pour Until:

case While_kind:
    return compiler_while(c, s);
case Until_kind:
    return compiler_until(c, s);

Si vous vous demandez ce que Until_kindc'est, c'est une constante (en fait une valeur de l' _stmt_kindénumération) générée automatiquement à partir du fichier de définition AST dans Include/Python-ast.h. Quoi qu'il en soit, nous appelons compiler_untilce qui, bien sûr, n'existe toujours pas. J'y reviendrai un instant.

Si vous êtes curieux comme moi, vous remarquerez que c'est étrange compiler_visit_stmt. Aucune partie de grepl'arborescence source ne révèle où il est appelé. Lorsque c'est le cas, il ne reste qu'une seule option - C macro-fu. En effet, une brève enquête nous conduit à la VISITmacro définie dans Python/compile.c:

#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
        return 0; \

Il est utilisé pour invoquer compiler_visit_stmtdans compiler_body. Mais revenons à nos affaires ...

Comme promis, voici compiler_until:

static int
compiler_until(struct compiler *c, stmt_ty s)
{
    basicblock *loop, *end, *anchor = NULL;
    int constant = expr_constant(s->v.Until.test);

    if (constant == 1) {
        return 1;
    }
    loop = compiler_new_block(c);
    end = compiler_new_block(c);
    if (constant == -1) {
        anchor = compiler_new_block(c);
        if (anchor == NULL)
            return 0;
    }
    if (loop == NULL || end == NULL)
        return 0;

    ADDOP_JREL(c, SETUP_LOOP, end);
    compiler_use_next_block(c, loop);
    if (!compiler_push_fblock(c, LOOP, loop))
        return 0;
    if (constant == -1) {
        VISIT(c, expr, s->v.Until.test);
        ADDOP_JABS(c, POP_JUMP_IF_TRUE, anchor);
    }
    VISIT_SEQ(c, stmt, s->v.Until.body);
    ADDOP_JABS(c, JUMP_ABSOLUTE, loop);

    if (constant == -1) {
        compiler_use_next_block(c, anchor);
        ADDOP(c, POP_BLOCK);
    }
    compiler_pop_fblock(c, LOOP, loop);
    compiler_use_next_block(c, end);

    return 1;
}

J'ai une confession à faire: ce code n'a pas été écrit sur la base d'une compréhension approfondie du bytecode Python. Comme le reste de l'article, cela a été fait à l'imitation de la compiler_whilefonction parentale . En le lisant attentivement, cependant, en gardant à l'esprit que la machine virtuelle Python est basée sur la pile et en jetant un coup d'œil à la documentation du dismodule, qui contient une liste de bytecodes Python avec des descriptions, il est possible de comprendre ce qui se passe.

Ça y est, nous avons fini ... N'est-ce pas?

Après avoir effectué toutes les modifications et exécuté make, nous pouvons exécuter le Python nouvellement compilé et essayer notre nouvelle untilinstruction:

>>> until num == 0:
...   print(num)
...   num -= 1
...
3
2
1

Voila, ça marche! Voyons le bytecode créé pour la nouvelle instruction en utilisant le dismodule comme suit:

import dis

def myfoo(num):
    until num == 0:
        print(num)
        num -= 1

dis.dis(myfoo)

Voici le résultat:

4           0 SETUP_LOOP              36 (to 39)
      >>    3 LOAD_FAST                0 (num)
            6 LOAD_CONST               1 (0)
            9 COMPARE_OP               2 (==)
           12 POP_JUMP_IF_TRUE        38

5          15 LOAD_NAME                0 (print)
           18 LOAD_FAST                0 (num)
           21 CALL_FUNCTION            1
           24 POP_TOP

6          25 LOAD_FAST                0 (num)
           28 LOAD_CONST               2 (1)
           31 INPLACE_SUBTRACT
           32 STORE_FAST               0 (num)
           35 JUMP_ABSOLUTE            3
      >>   38 POP_BLOCK
      >>   39 LOAD_CONST               0 (None)
           42 RETURN_VALUE

L'opération la plus intéressante est le numéro 12: si la condition est vraie, on saute après la boucle. C'est une sémantique correcte pour until. Si le saut n'est pas exécuté, le corps de la boucle continue de fonctionner jusqu'à ce qu'il revienne à la condition de l'opération 35.

Me sentant bien dans mon changement, j'ai ensuite essayé d'exécuter la fonction (exécution myfoo(3)) au lieu d'afficher son bytecode. Le résultat était loin d'être encourageant:

Traceback (most recent call last):
  File "zy.py", line 9, in
    myfoo(3)
  File "zy.py", line 5, in myfoo
    print(num)
SystemError: no locals when loading 'print'

Whoa ... ça ne peut pas être bon. Alors qu'est-ce qui ne va pas?

Le cas de la table des symboles manquante

L'une des étapes que le compilateur Python effectue lors de la compilation de l'AST est de créer une table de symboles pour le code qu'il compile. L'appel à PySymtable_Buildin PyAST_Compileappelle le module de table de symboles ( Python/symtable.c), qui parcourt l'AST d'une manière similaire aux fonctions de génération de code. Le fait d'avoir une table de symboles pour chaque étendue aide le compilateur à trouver certaines informations clés, telles que les variables globales et locales à une étendue.

Pour résoudre le problème, nous devons modifier la symtable_visit_stmtfonction dans Python/symtable.c, en ajoutant du code pour la gestion des untilinstructions, après le code similaire pour les whileinstructions [3] :

case While_kind:
    VISIT(st, expr, s->v.While.test);
    VISIT_SEQ(st, stmt, s->v.While.body);
    if (s->v.While.orelse)
        VISIT_SEQ(st, stmt, s->v.While.orelse);
    break;
case Until_kind:
    VISIT(st, expr, s->v.Until.test);
    VISIT_SEQ(st, stmt, s->v.Until.body);
    break;

[3] : Au fait, sans ce code, il y a un avertissement du compilateur pour Python/symtable.c. Le compilateur remarque que la Until_kindvaleur d'énumération n'est pas gérée dans l'instruction switch de symtable_visit_stmtet se plaint. Il est toujours important de vérifier les avertissements du compilateur!

Et maintenant, nous avons vraiment terminé. La compilation de la source après cette modification rend l'exécution du myfoo(3)travail comme prévu.

Conclusion

Dans cet article, j'ai montré comment ajouter une nouvelle instruction à Python. Bien que nécessitant un peu de bricolage dans le code du compilateur Python, le changement n'a pas été difficile à implémenter, car j'ai utilisé une instruction similaire et existante comme guide.

Le compilateur Python est un logiciel sophistiqué et je ne prétends pas en être un expert. Cependant, je suis vraiment intéressé par les internes de Python, et en particulier son front-end. Par conséquent, j'ai trouvé cet exercice un compagnon très utile pour l'étude théorique des principes et du code source du compilateur. Il servira de base pour de futurs articles qui approfondiront le compilateur.

Références

J'ai utilisé quelques excellentes références pour la construction de cet article. Les voici, sans ordre particulier:

  • PEP 339: Conception du compilateur CPython - probablement la documentation officielle la plus importante et la plus complète pour le compilateur Python. Étant très court, il affiche douloureusement la rareté de la bonne documentation des internes de Python.
  • "Python Compiler Internals" - un article de Thomas Lee
  • "Python: Design and Implementation" - une présentation de Guido van Rossum
  • Machine virtuelle Python (2.5), une visite guidée - une présentation de Peter Tröger

source primaire


7
Excellent article (/ blog), merci! Accepter puisque cela répond parfaitement à la question, et que les réponses "ne faites pas ça" / "codage: mylang" sont déjà fortement votées, donc apparaîtront bien dans l'ordre \ o /
dbr

1
Mais malheureusement, ce n'est pas une réponse. L'article lié est, mais que vous ne pouvez pas voter pour ou accepter. Les réponses consistant uniquement en un lien sont déconseillées.
Alfe

6
@Alfe: ceci a été publié il y a deux ans, accepté et attribué +1 par 16 lecteurs. Notez qu'il renvoie à mon propre article de blog et que je n'ai pas l'intention de copier un article volumineux dans StackOverflow. N'hésitez pas à le faire dans un montage utile, plutôt que de jouer à la police.
Eli Bendersky

2
@EliBendersky Useful est un euphémisme pour cet article. Merci d'avoir tellement expliqué comment ces choses fonctionnent réellement en python. Cela m'a vraiment aidé à comprendre l'AST, qui est pertinent pour mon travail actuel. ** aussi, au cas où vous seriez curieux, ma version de untilis isa/ isanas in if something isa dict:orif something isan int:
Inversus

5
Soo, cette réponse est "Écrivez et compilez votre propre langage à partir des sources, dérivé de python"
ThorSummoner

53

Une façon de faire des choses comme celle-ci est de prétraiter la source et de la modifier, en traduisant votre instruction ajoutée en python. Cette approche posera divers problèmes, et je ne la recommanderais pas pour un usage général, mais pour l'expérimentation avec le langage ou la métaprogrammation à des fins spécifiques, elle peut parfois être utile.

Par exemple, disons que nous voulons introduire une instruction "myprint" qui, au lieu d'imprimer à l'écran, se connecte à un fichier spécifique. c'est à dire:

myprint "This gets logged to file"

serait équivalent à

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

Il existe différentes options pour effectuer le remplacement, de la substitution de regex à la génération d'un AST, en passant par l'écriture de votre propre analyseur en fonction de la proximité de votre syntaxe avec le python existant. Une bonne approche intermédiaire consiste à utiliser le module tokenizer. Cela devrait vous permettre d'ajouter de nouveaux mots-clés, des structures de contrôle, etc. tout en interprétant la source de la même manière que l'interpréteur python, évitant ainsi la rupture des solutions regex grossières. Pour le "myprint" ci-dessus, vous pouvez écrire le code de transformation suivant:

import tokenize

LOGFILE = '/tmp/log.txt'
def translate(readline):
    for type, name,_,_,_ in tokenize.generate_tokens(readline):
        if type ==tokenize.NAME and name =='myprint':
            yield tokenize.NAME, 'print'
            yield tokenize.OP, '>>'
            yield tokenize.NAME, "open"
            yield tokenize.OP, "("
            yield tokenize.STRING, repr(LOGFILE)
            yield tokenize.OP, ","
            yield tokenize.STRING, "'a'"
            yield tokenize.OP, ")"
            yield tokenize.OP, ","
        else:
            yield type,name

(Cela fait effectivement de myprint un mot-clé, donc l'utiliser comme variable ailleurs causera probablement des problèmes)

Le problème est alors de savoir comment l'utiliser pour que votre code soit utilisable depuis python. Une façon serait simplement d'écrire votre propre fonction d'importation et de l'utiliser pour charger du code écrit dans votre langage personnalisé. c'est à dire:

import new
def myimport(filename):
    mod = new.module(filename)
    f=open(filename)
    data = tokenize.untokenize(translate(f.readline))
    exec data in mod.__dict__
    return mod

Cela nécessite cependant que vous gériez votre code personnalisé différemment des modules python normaux. c'est-à-dire " some_mod = myimport("some_mod.py")" plutôt que " import some_mod"

Une autre solution assez soignée (quoique hacky) est de créer un encodage personnalisé (voir PEP 263 ) comme le montre cette recette. Vous pouvez implémenter ceci comme:

import codecs, cStringIO, encodings
from encodings import utf_8

class StreamReader(utf_8.StreamReader):
    def __init__(self, *args, **kwargs):
        codecs.StreamReader.__init__(self, *args, **kwargs)
        data = tokenize.untokenize(translate(self.stream.readline))
        self.stream = cStringIO.StringIO(data)

def search_function(s):
    if s!='mylang': return None
    utf8=encodings.search_function('utf8') # Assume utf8 encoding
    return codecs.CodecInfo(
        name='mylang',
        encode = utf8.encode,
        decode = utf8.decode,
        incrementalencoder=utf8.incrementalencoder,
        incrementaldecoder=utf8.incrementaldecoder,
        streamreader=StreamReader,
        streamwriter=utf8.streamwriter)

codecs.register(search_function)

Maintenant, après l'exécution de ce code (par exemple, vous pouvez le placer dans votre .pythonrc ou site.py), tout code commençant par le commentaire «# coding: mylang» sera automatiquement traduit par l'étape de prétraitement ci-dessus. par exemple.

# coding: mylang
myprint "this gets logged to file"
for i in range(10):
    myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax" 
  "and line continuations")

Mises en garde:

Il y a des problèmes avec l'approche du préprocesseur, comme vous le saurez probablement si vous avez travaillé avec le préprocesseur C. Le principal est le débogage. Tout ce que Python voit est le fichier prétraité, ce qui signifie que le texte imprimé dans la trace de la pile, etc. y fera référence. Si vous avez effectué une traduction importante, cela peut être très différent de votre texte source. L'exemple ci-dessus ne change pas les numéros de ligne, etc., donc ne sera pas trop différent, mais plus vous le changez, plus il sera difficile à comprendre.


12
Joli! Au lieu de dire «ne peut pas être dun», vous donnez en fait quelques bonnes réponses (qui se résument à «vous ne voulez vraiment pas faire ça»).
c0m4

Je ne suis pas sûr de comprendre comment fonctionne le premier exemple - essayer de l'utiliser myimportsur un module qui contient simplement print 1car il ne s'agit que d'une ligne de code=1 ... SyntaxError: invalid syntax
olamundo

@noam: je ne suis pas sûr de ce qui échoue pour vous - ici, je reçois juste "1" imprimé comme prévu. (C'est avec les 2 blocs commençant par "import tokenize" et "import new" ci-dessus mis dans le fichier a.py, ainsi que " b=myimport("b.py")", et b.py contenant juste " print 1". Y a-t-il autre chose à propos de l'erreur (trace de pile etc)?
Brian

3
Python3 ne semble pas le permettre, mais pas nécessairement exprès; J'obtiens une erreur de nomenclature.
Tobu

notez que importutilise le builtin __import__, donc si vous écrasez cela ( avant d' importer le module qui nécessite l'importation modifiée), vous n'avez pas besoin d'un autremyimport
Tobias Kienzler

21

Oui, dans une certaine mesure, c'est possible. Il existe un module qui utilise sys.settrace()pour implémenter gotoet des comefrom"mots clés":

from goto import goto, label
for i in range(1, 10):
  for j in range(1, 20):
    print i, j
    if j == 3:
      goto .end # breaking out from nested loop
label .end
print "Finished"

4
Ce n'est pas vraiment une nouvelle syntaxe ... ça y ressemble.
Hans Nowak

3
-1: La page liée a cette rubrique: "Le module 'goto' était une blague du poisson d'avril, publiée le 1er avril 2004. Oui, ça marche, mais c'est quand même une blague. Merci de ne pas l'utiliser en vrai code!"
Jim

6
@Jim pourrait reconsidérer un -1. il vous indique le mécanisme de mise en œuvre. bonne chose pour commencer.
n611x007

14

À moins de changer et de recompiler le code source (ce qui est possible avec l'open source), changer la langue de base n'est pas vraiment possible.

Même si vous recompilez la source, ce ne serait pas python, juste votre version modifiée piratée dans laquelle vous devez faire très attention à ne pas introduire de bogues.

Cependant, je ne sais pas pourquoi vous voudriez le faire. Les fonctionnalités orientées objet de Python permettent d'obtenir des résultats similaires assez facilement avec le langage tel qu'il est.


2
Je ne suis pas d'accord sur un point. Si vous ajoutez de nouveaux mots clés, je pense que ce serait toujours Python. Si vous modifiez des mots-clés existants, c'est simplement piraté, comme vous le dites.
Bill the Lizard

9
Si vous ajoutez de nouveaux mots clés, ce serait un langage dérivé de Python. Si vous changez de mots-clés, ce serait un langage incompatible avec Python.
tzot

1
Si vous ajoutez des mots-clés, vous risquez de manquer le point de «syntaxe simple facile à apprendre» et de «bibliothèques étendues». Je pense que les fonctionnalités du langage sont presque toujours une erreur (les exemples incluent COBOL, Perl et PHP).
S.Lott

5
De nouveaux mots clés briseraient le code Python qui les utilise comme identifiants.
akaihola

12

Réponse générale: vous devez prétraiter vos fichiers sources.

Réponse plus spécifique: installez EasyExtend et suivez les étapes suivantes

i) Créer un nouveau langlet (langue d'extension)

import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")

Sans spécification supplémentaire, un tas de fichiers doit être créé sous EasyExtend / langlets / mystmts /.

ii) Ouvrez mystmts / parsedef / Grammar.ext et ajoutez les lignes suivantes

small_stmt: (expr_stmt | print_stmt  | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | exec_stmt | assert_stmt | my_stmt )

my_stmt: 'mystatement' expr

Cela suffit pour définir la syntaxe de votre nouvelle instruction. Le non-terminal small_stmt fait partie de la grammaire Python et c'est l'endroit où la nouvelle instruction est accrochée. L'analyseur reconnaîtra maintenant la nouvelle instruction, c'est-à-dire qu'un fichier source la contenant sera analysé. Le compilateur le rejettera cependant car il doit encore être transformé en Python valide.

iii) Il faut maintenant ajouter la sémantique de l'instruction. Pour cela, il faut éditer msytmts / langlet.py et ajouter un visiteur du nœud my_stmt.

 def call_my_stmt(expression):
     "defines behaviour for my_stmt"
     print "my stmt called with", expression

 class LangletTransformer(Transformer):
       @transform
       def my_stmt(self, node):
           _expr = find_node(node, symbol.expr)
           return any_stmt(CST_CallFunc("call_my_stmt", [_expr]))

 __publish__ = ["call_my_stmt"]

iv) cd en langlets / mystmts et tapez

python run_mystmts.py

Maintenant, une session doit être lancée et l'instruction nouvellement définie peut être utilisée:

__________________________________________________________________________________

 mystmts

 On Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
 __________________________________________________________________________________

 my> mystatement 40+2
 my stmt called with 42

Quelques étapes pour arriver à une déclaration triviale, non? Il n'existe pas encore d'API permettant de définir des choses simples sans avoir à se soucier des grammaires. Mais EE est modulo très fiable pour certains bugs. Ce n'est donc qu'une question de temps qu'une API émerge qui permet aux programmeurs de définir des éléments pratiques tels que des opérateurs d'infixe ou de petites instructions en utilisant simplement une programmation OO pratique. Pour des choses plus complexes comme l'intégration de langages entiers dans Python au moyen de la construction d'un langlet, il n'y a aucun moyen de contourner une approche grammaticale complète.


11

Voici une façon très simple mais merdique d'ajouter de nouvelles déclarations, en mode interprétatif uniquement . Je l'utilise pour de petites commandes à 1 lettre pour éditer les annotations de gènes en utilisant uniquement sys.displayhook, mais pour pouvoir répondre à cette question, j'ai également ajouté sys.excepthook pour les erreurs de syntaxe. Ce dernier est vraiment moche, récupérant le code brut du tampon readline. L'avantage est qu'il est très simple d'ajouter de nouvelles instructions de cette façon.


jcomeau@intrepid:~/$ cat demo.py; ./demo.py
#!/usr/bin/python -i
'load everything needed under "package", such as package.common.normalize()'
import os, sys, readline, traceback
if __name__ == '__main__':
    class t:
        @staticmethod
        def localfunction(*args):
            print 'this is a test'
            if args:
                print 'ignoring %s' % repr(args)

    def displayhook(whatever):
        if hasattr(whatever, 'localfunction'):
            return whatever.localfunction()
        else:
            print whatever

    def excepthook(exctype, value, tb):
        if exctype is SyntaxError:
            index = readline.get_current_history_length()
            item = readline.get_history_item(index)
            command = item.split()
            print 'command:', command
            if len(command[0]) == 1:
                try:
                    eval(command[0]).localfunction(*command[1:])
                except:
                    traceback.print_exception(exctype, value, tb)
        else:
            traceback.print_exception(exctype, value, tb)

    sys.displayhook = displayhook
    sys.excepthook = excepthook
>>> t
this is a test
>>> t t
command: ['t', 't']
this is a test
ignoring ('t',)
>>> ^D

4

J'ai trouvé un guide sur l'ajout de nouvelles déclarations:

https://troeger.eu/files/teaching/pythonvm08lab.pdf

Fondamentalement, pour ajouter de nouvelles instructions, vous devez éditer Python/ast.c(entre autres) et recompiler le binaire python.

Tant que c'est possible, ne le faites pas. Vous pouvez presque tout réaliser via des fonctions et des classes (ce qui n'obligera pas les gens à recompiler python juste pour exécuter votre script ..)


Le vrai lien vers PDF - cette "autonversion" est rompue et a été rompue pour que Dieu le sache depuis longtemps: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

Il est possible de le faire en utilisant EasyExtend :

EasyExtend (EE) est un générateur de préprocesseur et un cadre de métaprogrammation écrit en Python pur et intégré à CPython. L'objectif principal d'EasyExtend est la création de langages d'extension, c'est-à-dire l'ajout d'une syntaxe et d'une sémantique personnalisées à Python.


1
Suivre ce lien donne maintenant une page: "EasyExtend est mort. Pour ceux qui s'intéressent à l'EE, il existe un projet successeur appelé Langscape Nom différent, refonte complète, même voyage." Puisqu'il y a un risque que cette page d'information disparaisse, c'est peut-être une bonne idée de mettre à jour la réponse.
celtschk


1

Non sans modifier l'interprète. Je sais que beaucoup de langues au cours des dernières années ont été décrites comme «extensibles», mais pas de la manière que vous décrivez. Vous étendez Python en ajoutant des fonctions et des classes.



1

Certaines choses peuvent être faites avec des décorateurs. Supposons par exemple que Python n'avait aucune withinstruction. Nous pourrions alors implémenter un comportement similaire comme celui-ci:

# ====== Implementation of "mywith" decorator ======

def mywith(stream):
    def decorator(function):
        try: function(stream)
        finally: stream.close()
    return decorator

# ====== Using the decorator ======

@mywith(open("test.py","r"))
def _(infile):
    for l in infile.readlines():
        print(">>", l.rstrip())

C'est une solution assez impure cependant comme cela est fait ici. Surtout le comportement où le décorateur appelle la fonction et ensembles _à Noneest inattendu. Pour clarification: ce décorateur équivaut à l'écriture

def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.

et les décorateurs sont normalement censés modifier les fonctions et non les exécuter.

J'ai déjà utilisé une telle méthode dans un script où je devais définir temporairement le répertoire de travail pour plusieurs fonctions.


0

Il y a dix ans, vous ne pouviez pas, et je doute que cela ait changé. Cependant, il n'était pas si difficile de modifier la syntaxe à l'époque si vous étiez prêt à recompiler python, et je doute que cela ait changé non plus.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.