Pouvez-vous ajouter de nouvelles instructions (comme print
, raise
, with
) à la syntaxe de Python?
Dis, pour permettre ..
mystatement "Something"
Ou,
new_if True:
print "example"
Pas tellement si vous devriez, mais plutôt si c'est possible (à moins de modifier le code d'interprétation python)
Vous pouvez trouver cela utile - Python internes: ajout d'une nouvelle instruction à Python , cité ici:
Cet article est une tentative de mieux comprendre comment le front-end de Python fonctionne. La simple lecture de la documentation et du code source peut être un peu ennuyeuse, donc je prends une approche pratique ici: Je vais ajouter une instruction until
à Python.
Tout le codage de cet article a été effectué par rapport à la branche Py3k de pointe dans le miroir de référentiel Python Mercurial .
until
Certaines langues, comme Ruby, ont une instruction until
, qui est le complément de while
(until num == 0
Est équivalent à 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
Cet article ne tente pas de suggérer l'ajout d'une instruction until
à Python. Bien que je pense qu'une telle déclaration rendrait le code plus clair 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.
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 une arborescence 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] : A partir de là, les références aux fichiers dans la source Python sont données relativement à la racine de la l'arborescence source, 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'instruction until
. J'ai trouvé où l'instruction while
a été définie (while_stmt
), Et j'ai ajouté until_stmt
Ci-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 montre une technique courante que j'utilise lors de la modification du code source que je ne connais pas: fonctionne par similitude. 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 pour while
doit également être fait pour until
, cela sert de très bonne directive.
Notez que j'ai décidé d'exclure la clause else
de ma définition de until
, juste pour la rendre un peu différente (et parce que franchement je n'aime pas la clause else
de boucles et ne pense pas que cela cadre bien avec le Zen de Python).
La deuxième modification consiste à modifier la règle pour que compound_stmt
Inclue until_stmt
, Comme vous pouvez le voir dans l'extrait ci-dessus. C'est juste après while_stmt
, Encore une fois.
Lorsque vous exécutez make
après avoir modifié Grammar/Grammar
, Notez que le programme pgen
est exécuté pour régénérer Include/graminit.h
Et Python/graminit.c
, Puis plusieurs fichiers sont recompilés.
Après que le Python a créé un arbre d'analyse, cet arbre est converti en AST, car les AST sont beaucoup plus simple à utiliser dans les étapes suivantes du processus de compilation.
Nous allons donc visiter Parser/Python.asdl
Qui définit la structure des AST de Python et ajouter un AST pour notre nouvelle instruction until
, toujours juste en dessous du 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.py
Est exécuté pour générer du code C à partir du fichier de définition AST. Cela (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 depuis Parser/asdl_c.py
est un script Python, c'est une sorte de bootstrapping - pour construire Python à partir de zéro, Python doit déjà être disponible.
Alors que Parser/asdl_c.py
A généré le code pour gérer notre nouveau nœud AST node (dans les fichiers Include/Python-ast.h
Et Python/Python-ast.c
)), Nous devons encore écrire le code qui convertit à la main un nœud d'arbre d'analyse pertinent. Cela se fait dans le fichier Python/ast.c
. Là, une fonction nommée ast_for_stmt
convertit les nœuds d'arbre d'analyse pour les instructions en AST. Encore une fois, guidés par notre vieil ami while
, nous sautons directement dans le grand switch
pour 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);
Nous devons maintenant implémenter 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 examinant de près l'équivalent ast_for_while_stmt
, À la différence que pour until
j'ai décidé de ne pas prendre en charge la clause else
. Comme prévu, le AST est créé de manière récursive, en utilisant d'autres AST création de fonctions comme ast_for_expr
Pour l'expression de condition et ast_for_suite
pour le corps de l'instruction until
. Enfin, un nouveau nœud nommé Until
est renvoyé.
Notez que nous accédons au noeud de l'arbre d'analyse n
en utilisant des macros comme NCH
et CHILD
. Cela vaut la peine d'être compris - leur code est dans Include/node.h
.
J'ai choisi de créer un nouveau type de AST pour l'instruction until
, 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 nœuds existants, puisque:
until condition:
# do stuff
Est fonctionnellement équivalent à:
while not condition:
# do stuff
Au lieu de créer le nœud Until
dans ast_for_until_stmt
, J'aurais pu créer un nœud Not
avec un nœud While
comme enfant. Étant donné que le compilateur AST sait déjà comment gérer ces nœuds, les étapes suivantes du processus pourraient être ignorées.
L'étape suivante consiste à compiler le AST en Python bytecode. La compilation a un résultat intermédiaire qui est un CFG (Control Flow Graph), mais depuis le même code gère, je vais ignorer ce détail pour l'instant et le laisser pour un autre article.
Le code que nous examinerons ensuite est Python/compile.c
. 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 qu'est Until_kind
, C'est une constante (en fait une valeur de l'énumération _stmt_kind
) 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_until
Qui, bien sûr, n'existe toujours pas. J'y reviendrai un instant.
Si vous êtes curieux comme moi, vous remarquerez que compiler_visit_stmt
Est particulier. Aucune quantité de grep
- ping dans l'arborescence source ne révèle où elle est appelée. Dans ce cas, une seule option reste - C macro-fu. En effet, une courte enquête nous conduit à la macro VISIT
définie dans Python/compile.c
:
#define VISIT(C, TYPE, V) {\
if (!compiler_visit_ ## TYPE((C), (V))) \
return 0; \
Il est utilisé pour appeler compiler_visit_stmt
Dans 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 de Python bytecode. Comme le reste de l'article, il a été fait à l'imitation des parents compiler_while
. En le lisant attentivement, cependant, en gardant à l'esprit que le Python VM est basé sur la pile, et en jetant un œil dans la documentation du dis
module, qui a ne liste de Python bytecodes avec des descriptions, il est possible de comprendre ce qui se passe.
Après avoir effectué toutes les modifications et exécuté make
, nous pouvons exécuter la nouvelle compilation Python et essayer notre nouvelle instruction until
:
>>> until num == 0:
... print(num)
... num -= 1
...
3
2
1
Voila, ça marche! Voyons le bytecode créé pour la nouvelle instruction en utilisant le module dis
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, nous sautons après la boucle. C'est la 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 à l'opération 35.
Me sentant bien dans ma modification, j'ai ensuite essayé d'exécuter la fonction (en exécutant myfoo(3)
) au lieu d'afficher son bytecode. Le résultat n'a pas été 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 a mal tourné?
L'une des étapes que le compilateur Python effectue lors de la compilation du AST consiste à créer une table de symboles pour le code qu'il compile. L'appel à PySymtable_Build
dans PyAST_Compile
appelle le module de table de symboles (Python/symtable.c
), qui parcourt les AST d'une manière similaire aux fonctions de génération de code. Avoir une table de symboles pour chaque étendue aide le compilateur à comprendre certaines informations clés, telles que les variables qui sont globales et celles qui sont locales à une étendue.
Pour résoudre le problème, nous devons modifier la fonction symtable_visit_stmt
Dans Python/symtable.c
, En ajoutant du code pour la gestion des instructions until
, après le code similaire pour les instructions while
[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] : Soit dit en passant, sans ce code, il y a un avertissement du compilateur pour Python/symtable.c
. Le compilateur remarque que la valeur d'énumération Until_kind
N'est pas gérée dans l'instruction switch de symtable_visit_stmt
Et 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 fait que l'exécution de myfoo(3)
fonctionne comme prévu.
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 morceau de logiciel sophistiqué, et je ne prétends pas être un expert en la matière. Cependant, je suis vraiment intéressé par les composants internes de Python, et en particulier son frontal. Par conséquent, j'ai trouvé cet exercice un compagnon très utile à l'étude théorique des principes et du code source du compilateur. Il servira de base pour de futurs articles qui approfondiront le compilateur.
J'ai utilisé quelques excellentes références pour la construction de cet article. Les voici, sans ordre particulier:
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. Il y a divers problèmes que cette approche apportera, et je ne le recommanderais pas pour une utilisation générale, mais pour l'expérimentation du langage ou la métaprogrammation spécifique, elle peut parfois être utile.
Par exemple, disons que nous voulons introduire une instruction "myprint", qui au lieu d'imprimer à l'écran se connecte à la place à 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 d'expression régulière à 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 les solutions de regex brut de casse. Pour le "myprint" ci-dessus, vous pourriez écrivez 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 de myprint un mot-clé efficace, donc l'utilisation comme variable ailleurs causera probablement des problèmes)
Le problème est alors de savoir comment l'utiliser pour que votre code soit utilisable à partir de python. Une façon serait 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 que vous gériez votre code personnalisé différemment des modules normaux python cependant. Ie "some_mod = myimport("some_mod.py")
" plutôt que "import some_mod
"
Une autre solution assez soignée (quoique hacky) consiste à créer un encodage personnalisé (voir PEP 26 ) comme le montre la recette this . Vous pouvez l'implémenter 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:
L'approche du préprocesseur pose des problèmes, car vous saurez probablement si vous avez travaillé avec le préprocesseur C. Le principal est le débogage. Tout python voit est le fichier prétraité ce qui signifie que le texte imprimé dans la trace de la pile y fera référence. Si vous avez effectué une traduction importante, cela peut être très différent de votre texte source. Le l'exemple ci-dessus ne change pas les numéros de ligne, etc., il ne sera donc pas trop différent, mais plus vous le modifiez, plus il sera difficile à comprendre.
Oui, dans une certaine mesure, c'est possible. Il existe un module qui utilise sys.settrace()
pour implémenter goto
et 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"
À moins de changer et 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. Les fonctionnalités orientées objet de Python facilitent l'obtention de résultats similaires avec le langage en l'état.
Réponse générale: vous devez prétraiter vos fichiers source.
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 va maintenant reconnaître la nouvelle instruction, c'est-à-dire qu'un fichier source le contenant sera analysé. Le compilateur le rejettera cependant car il doit encore être transformé en Python valide.
iii) Maintenant, il faut ajouter la sémantique de l'énoncé. Pour cela, il faut éditer msytmts/langlet.py et ajouter un visiteur de noeud 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 vers langlets/mystmts et tapez
python run_mystmts.py
Maintenant, une session doit être démarrée et la nouvelle instruction 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
Pas mal d'étapes pour arriver à une déclaration banale, non? Il n'y a pas encore d'API qui permette de définir des choses simples sans avoir à se soucier des grammaires. Mais EE est très fiable modulo quelques bugs. Donc, ce n'est qu'une question de temps qu'une API émerge qui permet aux programmeurs de définir des trucs pratiques comme des opérateurs d'infixe ou de petites instructions en utilisant simplement la programmation OO. Pour des choses plus complexes comme l'incorporation de langages entiers dans Python en construisant un langlet, il n'y a aucun moyen de contourner une approche grammaticale complète.
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 des annotations de gènes en utilisant uniquement sys.displayhook, mais juste pour pouvoir répondre à cette question, j'ai ajouté sys.excepthook pour les erreurs de syntaxe également. Ce dernier est vraiment moche, récupérant le code brut à partir du tampon de ligne de lecture. L'avantage est qu'il est très facile 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 __== '__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
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 modifier Python/ast.c
(entre autres) et recompiler le binaire python.
Bien que ce soit possible, ne le faites pas. Vous pouvez réaliser presque tout via des fonctions et des classes (ce qui n'exige pas que les gens recompilent python juste pour exécuter votre script ..)
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 pur Python et intégré à CPython. Le but principal d'EasyExtend est la création de langages d'extension, c'est-à-dire l'ajout de syntaxe et de sémantique personnalisées à Python.
Ce n'est pas exactement l'ajout de nouvelles instructions à la syntaxe du langage, mais les macros sont un outil puissant: https://github.com/lihaoyi/macropy
Il existe un langage basé sur python appelé Logix avec lequel vous POUVEZ faire de telles choses. Il n'a pas été développé depuis un certain temps, mais les fonctionnalités que vous avez demandées pour faire du travail avec la dernière version.
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.
Certaines choses peuvent être faites avec des décorateurs. Voyons par exemple supposons, Python n'avait pas d'instruction with
. Nous pourrions alors implémenter un comportement similaire comme ceci:
# ====== 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())
Cependant, c'est une solution assez impure, comme ici. Surtout le comportement où le décorateur appelle la fonction et définit _
à None
est inattendu. Pour clarification: ce décorateur équivaut à écrire
def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.
et les décorateurs doivent normalement modifier, et non exécuter, les fonctions.
J'ai utilisé une telle méthode auparavant dans un script où je devais définir temporairement le répertoire de travail pour plusieurs fonctions.
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.