Come posso ottenere il codice sorgente di una funzione Python?


407

Supponiamo che io abbia una funzione Python come definita di seguito:

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

Posso ottenere il nome della funzione utilizzando foo.func_name. Come posso ottenere a livello di codice il suo codice sorgente, come ho digitato sopra?



1
Nota, in Python 3 puoi ottenere il nome della funzione usandofoo.__name__
MikeyE

Puoi ottenere anche molte altre cose .
not2qubit,

Risposte:


542

Se la funzione proviene da un file sorgente disponibile sul filesystem, inspect.getsource(foo)potrebbe essere di aiuto:

Se fooè definito come:

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

Poi:

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

Ritorna:

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

Ma credo che se la funzione viene compilata da una stringa, flusso o importata da un file compilato, non è possibile recuperare il suo codice sorgente.


2
Restituisce una tupla; tuple [0] è un elenco di stringhe che rappresentano le righe del codice sorgente e tuple [1] è il numero di riga nel contesto dell'esecuzione in cui è stato eseguito. In IPython; questo è il numero di riga all'interno della cella, non il blocco note
The Red Pea,

12
Questa risposta non la menziona esplicitamente, ma inspect.getsource (pippo) restituisce l'origine in una singola stringa anziché in una tupla in cui la tupla [0] è un elenco delle righe. getsource sarà più utile se hai bisogno di sbirciare nel
sostituzione

non funziona ad es. con la funzione len. Dove posso trovare il codice sorgente per la lenfunzione?
Oaklander114,

1
oppureinspect.getsourcelines(foo)
Sławomir Lenart,

1
@ oaklander113 inspect.getsource non funziona con i built-in come la maggior parte delle funzioni della libreria standard. Puoi controllare il codice sorgente di cpython sul loro sito web o sul loro Github
Nicolas Abril,

183

Il modulo inspect ha metodi per recuperare il codice sorgente dagli oggetti Python. Apparentemente funziona solo se l'origine si trova in un file. Se lo avessi immaginato non avresti bisogno di ottenere la fonte dall'oggetto.


3
Sì, sembra funzionare solo per oggetti definiti in un file. Non per quelli definiti nell'interprete.
sastanin,

3
con mia sorpresa, funziona anche con i notebook Ipython / Jupyter
Dr. Goulu

1
Ho provato a usare inspect in un python 3.5.3interprete. import inspect+ ha inspect.getsource(foo)funzionato bene.
André C. Andersen,

@ AndréChristofferAndersen Sì, ma non dovrebbe funzionare per le funzioni definite nell'interprete
qualcuno

86

dis è tuo amico se il codice sorgente non è disponibile:

>>> 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

1
Genera un TypeError per i builtin.
Noumenon,

8
@Noumenon perché di solito non hanno codice sorgente in Python, sono scritti in C
schlamar il

83

Se si utilizza IPython, è necessario digitare "pippo ??"

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

9
Molto utile nel notebook IPython e Jupyter se / quando elimini accidentalmente più di una cella che contiene funzioni che hai appena trascorso la giornata a creare e testare ....
AGS,

A chi, che ha perso l'intera classe: è possibile ripristinarlo metodo per metodo:, dir(MyClass)quindi MyClass.__init__??e così via.
Valerij

61

Anche se generalmente sono d'accordo che inspectsia una buona risposta, non sono d'accordo sul fatto che non è possibile ottenere il codice sorgente degli oggetti definito nell'interprete. Se lo usi dill.source.getsourceda dill, puoi ottenere la fonte di funzioni e lambda, anche se sono definite in modo interattivo. Può anche ottenere il codice per metodi e funzioni di classe associati o non definiti definiti nei curry ... tuttavia, potresti non essere in grado di compilare quel codice senza il codice dell'oggetto che lo racchiude.

>>> 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

>>> 

1
@ Ant6n: beh, è ​​solo subdolo. dill.source.getsourcecontrolla la cronologia dell'interprete per funzioni, classi, lambda, ecc. non controlla il contenuto delle stringhe passate a exec.
Mike McKerns

Questo sembra molto interessante. E 'possibile utilizzare dillper rispondere a questa domanda: stackoverflow.com/questions/13827543/...
ArtOfWarfare

@ArtOfWarfare: parzialmente, sì. dill.sourceha funzioni come getnamee importablee getsourceche si concentrano su come ottenere il codice sorgente (o un importabile che i rendimenti l'oggetto) per ogni oggetto. Per cose semplici come un intnon esiste una fonte, quindi non funziona come previsto (cioè per 'a = 10' restituisce '10').
Mike McKerns,

Questo funziona comunque per i globali: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns,

@MikeMcKerns: ho fatto del mio meglio per rispondere a questa domanda senza usare dill, ma la mia risposta lascia un po 'a desiderare (IE, se hai più nomi per lo stesso valore, non riesco a capire quale sia stato usato. passare un'espressione, non può dire quale fosse quell'espressione. Diamine, se passi un'espressione che valuta lo stesso di un nome, ti darà invece quel nome.) Può dillrisolvere nessuna di quelle mancanze della mia risposta qui: stackoverflow.com/a/28634996/901641
ArtOfWarfare

21

Per espandere la risposta di 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: Come sottolineato da @ 0sh questo esempio funziona usando ipythonma non in modo chiaro python. Dovrebbe andare bene in entrambi, tuttavia, quando si importa il codice dai file di origine.


2
Questo non funzionerà, poiché l'interprete compilerà foo in bytecode e eliminerebbe il codice sorgente, generando un OSError se si tenta di eseguire getsource(foo).
Milo Wielondek,

@ 0sh buon punto per quanto riguarda l'interprete di pitone alla vaniglia. Tuttavia, l'esempio di codice sopra riportato funziona quando si utilizza IPython.
TomDotTom il

11

È possibile utilizzare il inspectmodulo per ottenere il codice sorgente completo per quello. Devi usare il getsource()metodo per questo dal inspectmodulo. Per esempio:

import inspect

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

print(inspect.getsource(get_my_code))

Puoi dare un'occhiata a più opzioni sul link qui sotto. recuperare il codice Python


7

Poiché questo post è contrassegnato come duplicato di questo altro post , rispondo qui per il caso "lambda", sebbene il PO non riguardi lambdas.

Quindi, per le funzioni lambda che non sono definite nelle loro stesse linee: oltre alla risposta di marko.ristin , potresti voler usare mini-lambda o usare SymPy come suggerito in questa risposta .

  • mini-lambda è più leggero e supporta qualsiasi tipo di operazione, ma funziona solo per una singola variabile
  • SymPyè più pesante ma molto più dotato di operazioni matematiche / di calcolo. In particolare può semplificare le tue espressioni. Supporta anche diverse variabili nella stessa espressione.

Ecco come puoi farlo usando 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))

Si cede correttamente

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

x ** 2

Vedere la mini-lambda documentazione per i dettagli. Sono l'autore tra l'altro;)


5

Tieni presente che le risposte accettate funzionano solo se il lambda viene fornito su una riga separata. Se lo passi come argomento a una funzione e desideri recuperare il codice del lambda come oggetto, il problema diventa un po 'complicato poiché inspectti darà l'intera riga.

Ad esempio, considera un file test.py:

import inspect

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

if __name__ == "__main__":
    main()

Eseguirlo ti dà (attenzione l'indentazione!):

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

Per recuperare il codice sorgente di lambda, la mia scommessa migliore, secondo me, è di analizzare nuovamente l'intero file sorgente (usando f.__code__.co_filename) e abbinare il nodo lambda AST per il numero di riga e il suo contesto.

Abbiamo dovuto fare esattamente questo nella nostra icona della biblioteca design-by-contract poiché abbiamo dovuto analizzare le funzioni lambda che passiamo come argomenti ai decoratori. È troppo codice da incollare qui, quindi dai un'occhiata all'implementazione di questa funzione .


4

Se stai definendo rigorosamente la funzione da solo ed è una definizione relativamente breve, una soluzione senza dipendenze sarebbe quella di definire la funzione in una stringa e assegnare l'eval () dell'espressione alla tua funzione.

Per esempio

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

quindi facoltativamente per allegare il codice originale alla funzione:

func.source = funcstring

2
L'uso di eval () mi sembra davvero, VERAMENTE male, a meno che tu non stia scrivendo una specie di interprete interattivo di Python. Eval apre drastici problemi di sicurezza. Se adotti una politica di valutazione dei soli letterali di stringhe, perdi comunque una varietà di comportamenti utili, che vanno dall'evidenziazione della sintassi alla corretta riflessione delle classi che contengono membri valutati.
Mark E. Haase,

2
Upvoting. @mehaase: la sicurezza non è ovviamente un problema qui. I tuoi altri commenti sono piuttosto rilevanti, anche se direi che la mancanza di evidenziazione della sintassi è una combinazione della colpa dell'IDE e del fatto che Python non è un linguaggio omoiconico.
ninjagecko,

7
@ninjagecko La sicurezza è sempre un problema quando dai consigli al grande pubblico. La maggior parte dei lettori viene qui perché fa domande su Google. Non penso che molte persone copieranno questa risposta alla lettera; invece, prenderanno il concetto che hanno appreso e lo applicheranno al proprio problema.
Mark E. Haase,

4

riassumere :

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

0

Io credo che i nomi delle variabili non vengono memorizzati in pyc / PYD / Pyo file, in modo non è possibile recuperare le righe di codice esatto, se non si dispone di file di origine.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.