Comment puis-je obtenir le code source d'une fonction Python?

407

Supposons que j'ai une fonction Python telle que définie ci-dessous:

def foo(arg1,arg2):
    #do something with args
    a = arg1 + arg2
    return a

Je peux obtenir le nom de la fonction en utilisant foo.func_name. Comment puis-je obtenir par programme son code source, comme je l'ai tapé ci-dessus?

Vertexwahn
la source
1
Remarque, dans Python 3, vous pouvez obtenir le nom de la fonction en utilisantfoo.__name__
MikeyE
Vous pouvez également obtenir beaucoup d'autres choses .
not2qubit

Réponses:

543

Si la fonction provient d'un fichier source disponible sur le système de fichiers, cela inspect.getsource(foo)peut être utile:

Si fooest défini comme:

def foo(arg1,arg2):         
    #do something with args 
    a = arg1 + arg2         
    return a  

Alors:

import inspect
lines = inspect.getsource(foo)
print(lines)

Retour:

def foo(arg1,arg2):         
    #do something with args 
    a = arg1 + arg2         
    return a                

Mais je crois que si la fonction est compilée à partir d'une chaîne, d'un flux ou importée à partir d'un fichier compilé, vous ne pouvez pas récupérer son code source.

Rafał Dowgird
la source
2
Renvoie un tuple; tuple [0] est une liste de chaînes représentant les lignes de code source, et tuple [1] est le numéro de ligne dans le contexte de l'exécution où il a été exécuté. Dans IPython; il s'agit du numéro de ligne dans la cellule et non du bloc
The Red Pea
12
Cette réponse ne le mentionne pas explicitement, mais inspect.getsource (foo) renvoie la source dans une seule chaîne au lieu d'un tuple où tuple [0] est une liste des lignes. getsource sera plus utile si vous avez besoin de jeter un coup d'œil dans la
réponse
cela ne fonctionne pas avec par exemple la fonction len. Où puis-je trouver le code source de la lenfonction?
oaklander114
1
ouinspect.getsourcelines(foo)
Sławomir Lenart
1
@ oaklander113 inspect.getsource ne fonctionne pas avec les modules intégrés comme la plupart des fonctions de la bibliothèque standard. Vous pouvez vérifier le code source de cpython sur leur site Web ou leur Github
Nicolas Abril
184

Le module d'inspection dispose de méthodes pour récupérer le code source à partir d'objets python. Apparemment, cela ne fonctionne que si la source se trouve dans un fichier. Si vous l'aviez, je suppose que vous n'auriez pas besoin d'obtenir la source de l'objet.

runeh
la source
3
Oui, cela semble fonctionner uniquement pour les objets définis dans un fichier. Pas pour ceux définis dans l'interpréteur.
sastanin
3
à ma grande surprise, cela fonctionne aussi dans les cahiers Ipython / Jupyter
Dr. Goulu
1
J'ai essayé d'utiliser inspect dans un python 3.5.3interprète. import inspect+ inspect.getsource(foo)a bien fonctionné.
André C. Andersen
@ AndréChristofferAndersen Oui, mais cela ne devrait pas fonctionner pour les fonctions définies dans l'interpréteur
quelqu'un
86

dis est votre ami si le code source n'est pas disponible:

>>> import dis
>>> def foo(arg1,arg2):
...     #do something with args
...     a = arg1 + arg2
...     return a
...
>>> dis.dis(foo)
  3           0 LOAD_FAST                0 (arg1)
              3 LOAD_FAST                1 (arg2)
              6 BINARY_ADD
              7 STORE_FAST               2 (a)

  4          10 LOAD_FAST                2 (a)
             13 RETURN_VALUE
schlamar
la source
1
Lève une TypeError pour les commandes intégrées.
Noumenon
8
@Noumenon car ils n'ont généralement pas de code source en Python, ils sont écrits en C
schlamar
83

Si vous utilisez IPython, vous devez taper "foo ??"

In [19]: foo??
Signature: foo(arg1, arg2)
Source:
def foo(arg1,arg2):
    #do something with args
    a = arg1 + arg2
    return a

File:      ~/Desktop/<ipython-input-18-3174e3126506>
Type:      function
prashanth
la source
9
Très utile dans le bloc-notes IPython et Jupyter si / quand vous supprimez accidentellement plus d'une cellule qui contient des fonctions que vous venez de passer la journée à créer et à tester ....
AGS
À qui, qui a perdu toute la classe: vous pouvez la restaurer méthode par méthode:, dir(MyClass)puis MyClass.__init__??et ainsi de suite.
Valerij
62

Bien que je convienne généralement que inspectc'est une bonne réponse, je ne suis pas d'accord pour dire que vous ne pouvez pas obtenir le code source des objets définis dans l'interpréteur. Si vous utilisez dill.source.getsourcefrom dill, vous pouvez obtenir la source des fonctions et des lambdas, même s'ils sont définis de manière interactive. Il peut également obtenir le code des méthodes et fonctions de classe liées ou non définies définies dans les currys ... cependant, vous ne pourrez peut-être pas compiler ce code sans le code de l'objet englobant.

>>> from dill.source import getsource
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> squared = lambda x:x**2
>>> 
>>> print getsource(add)
def add(x,y):
  return x+y

>>> print getsource(squared)
squared = lambda x:x**2

>>> 
>>> class Foo(object):
...   def bar(self, x):
...     return x*x+x
... 
>>> f = Foo()
>>> 
>>> print getsource(f.bar)
def bar(self, x):
    return x*x+x

>>> 
Mike McKerns
la source
1
@ Ant6n: eh bien, c'est juste être sournois. dill.source.getsourceinspecte l'historique de l'interpréteur pour les fonctions, classes, lambdas, etc. - il n'inspecte pas le contenu des chaînes passées à exec.
Mike McKerns
Cela semble très intéressant. Est-il possible d'utiliser dillpour répondre à cette question: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ArtOfWarfare: partiellement, oui. dill.sourcea des fonctions comme getnameet importableet getsourcequi se concentrent sur l'obtention du code source (ou d'un importable qui donne l'objet) pour tout objet donné. Pour des choses simples comme an intil n'y a pas de source, donc cela ne fonctionne pas comme prévu (c'est-à-dire que pour 'a = 10' il retourne '10').
Mike McKerns
Cela fonctionne cependant pour les mondiaux: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns
@MikeMcKerns: J'ai fait de mon mieux pour répondre à cette question sans utiliser dill, mais ma réponse laisse un peu à désirer (IE, si vous avez plusieurs noms pour la même valeur, il ne peut pas déterminer lequel a été utilisé. Si vous passer une expression, il ne peut pas dire ce que cette expression était. Heck, si vous passez une expression qui équivaut à un nom, il donnera ce nom à la place.) Peut dillrésoudre l'un de ces défauts de ma réponse ici: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Pour développer la réponse de runeh:

>>> def foo(a):
...    x = 2
...    return x + a

>>> import inspect

>>> inspect.getsource(foo)
u'def foo(a):\n    x = 2\n    return x + a\n'

print inspect.getsource(foo)
def foo(a):
   x = 2
   return x + a

EDIT: Comme indiqué par @ 0sh, cet exemple fonctionne en utilisant ipythonmais pas en clair python. Cependant, cela devrait convenir dans les deux cas lors de l'importation de code à partir de fichiers source.

TomDotTom
la source
2
Cela ne fonctionnera pas, car l'interpréteur compilerait foo en bytecode et jetterait le code source, lançant une OSError si vous essayez de courir getsource(foo).
Milo Wielondek
@ 0sh bon point en ce qui concerne l'interpréteur vanilla python. Cependant, l'exemple de code ci-dessus fonctionne lors de l'utilisation d'IPython.
TomDotTom
11

Vous pouvez utiliser le inspectmodule pour obtenir le code source complet pour cela. Vous devez utiliser la getsource()méthode pour cela à partir du inspectmodule. Par exemple:

import inspect

def get_my_code():
    x = "abcd"
    return x

print(inspect.getsource(get_my_code))

Vous pouvez le vérifier plus d'options sur le lien ci-dessous. récupérer votre code python

Krutarth Gor
la source
7

Étant donné que ce message est marqué comme le double de cet autre message , je réponds ici pour le cas "lambda", bien que l'OP ne concerne pas les lambdas.

Donc, pour les fonctions lambda qui ne sont pas définies dans leurs propres lignes: en plus de la réponse de marko.ristin , vous pouvez utiliser mini-lambda ou utiliser SymPy comme suggéré dans cette réponse .

  • mini-lambda est plus léger et prend en charge tout type d'opération, mais ne fonctionne que pour une seule variable
  • SymPyest plus lourd mais beaucoup plus équipé d'opérations mathématiques / calcul. En particulier, cela peut simplifier vos expressions. Il prend également en charge plusieurs variables dans la même expression.

Voici comment vous pouvez le faire en utilisant mini-lambda:

from mini_lambda import x, is_mini_lambda_expr
import inspect

def get_source_code_str(f):
    if is_mini_lambda_expr(f):
        return f.to_string()
    else:
        return inspect.getsource(f)

# test it

def foo(arg1, arg2):
    # do something with args
    a = arg1 + arg2
    return a

print(get_source_code_str(foo))
print(get_source_code_str(x ** 2))

Il donne correctement

def foo(arg1, arg2):
    # do something with args
    a = arg1 + arg2
    return a

x ** 2

Voir la mini-lambda documentation pour plus de détails. Je suis l'auteur d'ailleurs;)

smarie
la source
5

Veuillez noter que les réponses acceptées ne fonctionnent que si le lambda est indiqué sur une ligne distincte. Si vous le transmettez en tant qu'argument à une fonction et que vous souhaitez récupérer le code du lambda en tant qu'objet, le problème devient un peu délicat car inspectil vous donnera la ligne entière.

Par exemple, considérons un fichier test.py:

import inspect

def main():
    x, f = 3, lambda a: a + 1
    print(inspect.getsource(f))

if __name__ == "__main__":
    main()

L'exécuter vous donne (attention à l'indentation!):

    x, f = 3, lambda a: a + 1

Pour récupérer le code source du lambda, mon meilleur pari, à mon avis, est de ré-analyser le fichier source entier (en utilisant f.__code__.co_filename) et de faire correspondre le nœud lambda AST par le numéro de ligne et son contexte.

Nous avons dû faire précisément dans notre bibliothèque de conception par contrat IContract puisque nous devions analyser les fonctions lambda , on passe comme arguments aux décorateurs. C'est trop de code à coller ici, alors jetez un œil à l'implémentation de cette fonction .

marko.ristin
la source
4

Si vous définissez strictement la fonction vous-même et que sa définition est relativement courte, une solution sans dépendances serait de définir la fonction dans une chaîne et d'assigner l'eval () de l'expression à votre fonction.

Par exemple

funcstring = 'lambda x: x> 5'
func = eval(funcstring)

puis éventuellement attacher le code d'origine à la fonction:

func.source = funcstring
cherplunk
la source
2
L'utilisation de eval () me semble vraiment, vraiment mauvaise, à moins que vous n'écriviez une sorte d'interpréteur Python interactif. Eval ouvre des problèmes de sécurité drastiques. Si vous adoptez une politique qui n'évalue que les littéraux de chaîne, vous perdez tout de même un certain nombre de comportements utiles, allant de la mise en évidence de la syntaxe à la bonne réflexion des classes qui contiennent des membres évalués.
Mark E. Haase
2
Upvoting. @mehaase: la sécurité n'est évidemment pas un problème ici. Vos autres commentaires sont cependant très pertinents, même si je dirais que le manque de mise en évidence de la syntaxe est une combinaison de la faute de l'IDE et du fait que le python n'est pas un langage homoiconique.
ninjagecko
7
@ninjagecko La sécurité est toujours un problème lorsque vous donnez des conseils au grand public. La plupart des lecteurs viennent ici parce qu'ils recherchent des questions sur Google. Je ne pense pas que beaucoup de gens vont copier cette réponse mot pour mot; au lieu de cela, ils vont prendre le concept qu'ils ont appris et l'appliquer à leur propre problème.
Mark E. Haase
4

résumer :

import inspect
print( "".join(inspect.getsourcelines(foo)[0]))
douardo
la source
0

Je crois que les noms de variables ne sont pas stockés dans des fichiers pyc / pyd / pyo, vous ne pouvez donc pas récupérer les lignes de code exactes si vous n'avez pas de fichiers source.

Abgan
la source