Wie kann ich den Quellcode einer Python-Funktion erhalten?

406

Angenommen, ich habe eine Python-Funktion wie unten definiert:

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

Ich kann den Namen der Funktion mit abrufen foo.func_name. Wie kann ich den Quellcode programmgesteuert abrufen, wie ich oben eingegeben habe?

Vertexwahn
quelle
1
Beachten Sie, dass Sie in Python 3 den Funktionsnamen mitfoo.__name__
MikeyE
Sie können auch viele andere Dinge bekommen .
not2qubit

Antworten:

541

Wenn die Funktion aus einer im Dateisystem verfügbaren Quelldatei stammt, inspect.getsource(foo)kann dies hilfreich sein:

If fooist definiert als:

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

Dann:

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

Kehrt zurück:

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

Ich glaube jedoch, dass Sie den Quellcode nicht abrufen können, wenn die Funktion aus einer Zeichenfolge kompiliert, gestreamt oder aus einer kompilierten Datei importiert wird.

Rafał Dowgird
quelle
2
Gibt ein Tupel zurück. Tupel [0] ist eine Liste von Zeichenfolgen, die die Zeilen des Quellcodes darstellen, und Tupel [1] ist die Zeilennummer im Kontext der Ausführung, in der sie ausgeführt wurde. In IPython; Dies ist die Zeilennummer in der Zelle, nicht das gesamte Notizbuch
The Red Pea
12
Diese Antwort erwähnt es nicht explizit, aber inspect.getsource (foo) gibt die Quelle in einer einzelnen Zeichenfolge anstelle eines Tupels zurück, wobei tuple [0] eine Liste der Zeilen ist. getource wird nützlicher sein, wenn Sie in den repl
whaley
es funktioniert nicht mit zB der Funktion len. Wo finde ich den Quellcode für die lenFunktion?
Oaklander114
1
oderinspect.getsourcelines(foo)
Sławomir Lenart
1
@ oaklander113 inspect.getsource funktioniert nicht mit integrierten Funktionen wie die meisten Funktionen aus der Standardbibliothek. Sie können den Quellcode für cpython auf ihrer Website oder ihrem Github
Nicolas Abril
183

Das Inspect-Modul verfügt über Methoden zum Abrufen von Quellcode aus Python-Objekten. Scheinbar funktioniert es nur, wenn sich die Quelle in einer Datei befindet. Wenn Sie das hätten, müssten Sie wahrscheinlich nicht die Quelle vom Objekt abrufen.

runeh
quelle
3
Ja, es scheint nur für Objekte zu funktionieren, die in einer Datei definiert sind. Nicht für diejenigen, die im Dolmetscher definiert sind.
Sastanin
3
Zu meiner Überraschung funktioniert es auch in Ipython / Jupyter-Notizbüchern
Dr. Goulu
1
Ich habe versucht, inspect in einem python 3.5.3Dolmetscher zu verwenden. import inspect+ hat gut inspect.getsource(foo)funktioniert.
André C. Andersen
@ AndréChristofferAndersen Ja, aber es sollte nicht für Funktionen funktionieren, die im Interpreter definiert sind
jemand
86

dis ist dein Freund, wenn der Quellcode nicht verfügbar ist:

>>> 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
quelle
1
Wirft einen TypeError für integrierte Funktionen.
Noumenon
8
@ Noumenon, weil sie normalerweise keinen Quellcode in Python haben, sind sie in C
schlamar
83

Wenn Sie IPython verwenden, müssen Sie "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
quelle
9
Sehr hilfreich in IPython- und Jupyter-Notizbüchern, wenn Sie versehentlich mehr als eine Zelle löschen, die Funktionen enthält, die Sie gerade erstellt und getestet haben ....
AGS
An wen, der die ganze Klasse verloren hat: Sie können sie Methode für Methode wiederherstellen: dir(MyClass)dann MyClass.__init__??und so weiter.
Valerij
61

Obwohl ich im Allgemeinen zustimmen würde, dass dies inspecteine gute Antwort ist, würde ich nicht zustimmen, dass Sie den Quellcode der im Interpreter definierten Objekte nicht erhalten können. Wenn Sie dill.source.getsourcevon verwenden dill, können Sie die Quelle von Funktionen und Lambdas abrufen, auch wenn diese interaktiv definiert sind. Es kann auch den Code für gebundene oder ungebundene Klassenmethoden und -funktionen abrufen, die in Currys definiert sind. Möglicherweise können Sie diesen Code jedoch nicht ohne den Code des umschließenden Objekts kompilieren.

>>> 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
quelle
1
@ Ant6n: Nun, das ist nur hinterhältig. dill.source.getsourceÜberprüft den Verlauf des Interpreters auf Funktionen, Klassen, Lambdas usw. - Der Inhalt der an exec übergebenen Zeichenfolgen wird nicht überprüft.
Mike McKerns
Das scheint sehr interessant zu sein. Ist es möglich, dilldiese Frage zu beantworten: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ ArtOfWarfare: teilweise ja. dill.sourcehat Funktionen wie getnameund importableund getsource, die sich darauf konzentrieren, den Quellcode (oder einen importierbaren Code, der das Objekt liefert) für ein bestimmtes Objekt abzurufen. Für einfache Dinge wie a intgibt es keine Quelle, daher funktioniert es nicht wie erwartet (dh für 'a = 10' wird '10' zurückgegeben).
Mike McKerns
Dies funktioniert jedoch für Globals: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns
@MikeMcKerns: Ich habe mein Bestes getan, um diese Frage ohne Verwendung zu beantworten dill, aber meine Antwort lässt etwas zu wünschen übrig (IE, wenn Sie mehrere Namen für denselben Wert haben, kann es nicht herausfinden, welche verwendet wurden. Wenn Sie Wenn Sie einen Ausdruck übergeben, kann er nicht sagen, was dieser Ausdruck war. Wenn Sie einen Ausdruck übergeben, der den gleichen Wert wie ein Name hat, wird stattdessen dieser Name angegeben.) Kann dilleinen dieser Mängel meiner Antwort beheben hier: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Um die Antwort von runeh zu erweitern:

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

BEARBEITEN: Wie von @ 0sh hervorgehoben, funktioniert dieses Beispiel mit, ipythonaber nicht einfach python. Es sollte jedoch in beiden Fällen in Ordnung sein, wenn Code aus Quelldateien importiert wird.

TomDotTom
quelle
2
Dies funktioniert nicht, da der Interpreter foo zu Bytecode kompiliert und den Quellcode wegwirft und einen OSError auslöst, wenn Sie versuchen, ihn auszuführen getsource(foo).
Milo Wielondek
@ 0sh guter Punkt für den Vanille-Python-Interpreter. Das obige Codebeispiel funktioniert jedoch bei Verwendung von IPython.
TomDotTom
11

Sie können das inspectModul verwenden, um den vollständigen Quellcode dafür zu erhalten. Sie müssen dafür die getsource()Methode aus dem inspectModul verwenden. Zum Beispiel:

import inspect

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

print(inspect.getsource(get_my_code))

Weitere Optionen finden Sie unter dem folgenden Link. Rufen Sie Ihren Python-Code ab

Krutarth Gor
quelle
7

Da dieser Beitrag als Duplikat dieses anderen Beitrags markiert ist , antworte ich hier für den Fall "Lambda", obwohl es im OP nicht um Lambdas geht.

Für Lambda-Funktionen, die nicht in eigenen Zeilen definiert sind: Zusätzlich zur Antwort von marko.ristin möchten Sie möglicherweise Mini-Lambda oder SymPy verwenden, wie in dieser Antwort vorgeschlagen .

  • mini-lambda ist leichter und unterstützt jede Art von Operation, funktioniert aber nur für eine einzelne Variable
  • SymPyist schwerer, aber viel besser mit mathematischen / Kalküloperationen ausgestattet. Insbesondere kann es Ihre Ausdrücke vereinfachen. Es werden auch mehrere Variablen im selben Ausdruck unterstützt.

So können Sie es machen mit 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))

Es gibt richtig nach

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

x ** 2

Einzelheiten finden Sie in der mini-lambda Dokumentation . Ich bin übrigens der Autor;)

smarie
quelle
5

Bitte beachten Sie, dass die akzeptierten Antworten nur funktionieren, wenn das Lambda in einer separaten Zeile angegeben wird. Wenn Sie es als Argument an eine Funktion übergeben und den Code des Lambda als Objekt abrufen möchten, wird das Problem etwas schwierig, da inspectSie die gesamte Zeile erhalten.

Betrachten Sie beispielsweise eine Datei test.py:

import inspect

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

if __name__ == "__main__":
    main()

Wenn Sie es ausführen, erhalten Sie (beachten Sie die Einrückung!):

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

Um den Quellcode des Lambda abzurufen, ist es meiner Meinung nach am besten, die gesamte Quelldatei (mithilfe von f.__code__.co_filename) erneut zu analysieren und den Lambda-AST-Knoten anhand der Zeilennummer und seines Kontexts abzugleichen.

Genau das mussten wir in unserer Design-by-Contract-Bibliothek icontract tun, da wir die Lambda-Funktionen analysieren mussten, die wir als Argumente an Dekorateure übergeben. Es ist zu viel Code, um ihn hier einzufügen. Schauen Sie sich also die Implementierung dieser Funktion an .

marko.ristin
quelle
4

Wenn Sie die Funktion streng selbst definieren und es sich um eine relativ kurze Definition handelt, besteht eine Lösung ohne Abhängigkeiten darin, die Funktion in einer Zeichenfolge zu definieren und Ihrer Funktion das eval () des Ausdrucks zuzuweisen.

Z.B

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

dann optional, um den Originalcode an die Funktion anzuhängen:

func.source = funcstring
Cherplunk
quelle
2
Die Verwendung von eval () scheint mir wirklich WIRKLICH schlecht zu sein, es sei denn, Sie schreiben eine Art interaktiven Python-Interpreter. Eval eröffnet drastische Sicherheitsprobleme. Wenn Sie eine Richtlinie anwenden, bei der nur Zeichenfolgenliterale ausgewertet werden, verlieren Sie immer noch eine Reihe hilfreicher Verhaltensweisen, die von der Hervorhebung der Syntax bis zur korrekten Reflexion von Klassen reichen, die ausgewertete Mitglieder enthalten.
Mark E. Haase
2
Upvoting. @mehaase: Sicherheit ist hier offensichtlich kein Thema. Ihre anderen Kommentare sind jedoch ziemlich relevant, obwohl ich sagen würde, dass das Fehlen einer Syntaxhervorhebung eine Kombination aus dem Fehler der IDE und der Tatsache ist, dass Python keine homoikonische Sprache ist.
Ninjagecko
7
@ninjagecko Sicherheit ist immer ein Problem, wenn Sie der Öffentlichkeit Ratschläge geben. Die meisten Leser kommen hierher, weil sie Fragen googeln. Ich glaube nicht, dass viele Leute diese Antwort wörtlich kopieren werden. Stattdessen werden sie das erlernte Konzept auf ihr eigenes Problem anwenden.
Mark E. Haase
4

zusammenfassen :

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

Ich glaube, dass Variablennamen nicht in pyc / pyd / pyo-Dateien gespeichert sind, so dass Sie die genauen Codezeilen nicht abrufen können, wenn Sie keine Quelldateien haben.

Abgan
quelle