Jak mogę uzyskać kod źródłowy funkcji Python?


406

Załóżmy, że mam funkcję Python, jak zdefiniowano poniżej:

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

Mogę uzyskać nazwę funkcji za pomocą foo.func_name . Jak mogę programowo uzyskać kod źródłowy, jak napisałem powyżej?



1
Zauważ, że w Pythonie 3 możesz uzyskać nazwę funkcji używającfoo.__name__
MikeyE,

Możesz także zdobyć wiele innych rzeczy .
not2qubit

Odpowiedzi:


541

Jeśli funkcja pochodzi z pliku źródłowego dostępnego w systemie plików, inspect.getsource(foo)może być pomocna:

Jeśli foojest zdefiniowany jako:

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

Następnie:

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

Zwroty:

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

Uważam jednak, że jeśli funkcja jest skompilowana z ciągu, strumienia lub zaimportowana ze skompilowanego pliku, nie można pobrać jej kodu źródłowego.


2
Zwraca krotkę; krotka [0] to lista ciągów znaków reprezentujących linie kodu źródłowego, a krotka [1] to numer linii w kontekście wykonania, w którym został uruchomiony. W IPython; jest to numer linii w komórce, a nie ogólny notatnik
The Red Pea

12
Ta odpowiedź nie wspomina o tym wprost, ale inspect.getsource (foo) zwraca źródło w jednym ciągu zamiast krotki, gdzie krotka [0] jest listą linii. getsource będzie bardziej użyteczny, jeśli
zajrzysz do repliki

nie działa np. z funkcją len. Gdzie mogę znaleźć kod źródłowy lenfunkcji?
oaklander114

1
lubinspect.getsourcelines(foo)
Sławomir Lenart

1
@ oaklander113 inspect.getsource nie działa z wbudowanymi funkcjami, jak większość funkcji ze standardowej biblioteki. Możesz sprawdzić kod źródłowy cpython na ich stronie internetowej lub w Github
Nicolas Abril,

183

Sprawdzić moduł ma metody pobierania kodu źródłowego z obiektów Pythona. Pozornie działa to tylko wtedy, gdy źródło znajduje się w pliku. Gdybyś to miał, myślę, że nie musiałbyś pobierać źródła z obiektu.


3
Tak, wydaje się, że działa tylko w przypadku obiektów zdefiniowanych w pliku. Nie dla osób zdefiniowanych w tłumaczu.
sastanin

3
ku mojemu zaskoczeniu, działa również w zeszytach Ipython / Jupyter
Dr. Goulu

1
Próbowałem użyć inspekcji w python 3.5.3tłumaczu. import inspect+ inspect.getsource(foo)działało dobrze.
André C. Andersen

@ AndréChristofferAndersen Tak, ale to nie powinno działać dla funkcji zdefiniowanych w tłumaczu
ktoś

86

dis jest twoim przyjacielem, jeśli kod źródłowy nie jest dostępny:

>>> 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
Zgłasza błąd typu dla błędów wbudowanych.
Noumenon,

8
@Noumenon, ponieważ zwykle nie mają kodu źródłowego w Pythonie, są napisane w C
schlamar

83

Jeśli używasz IPython, musisz wpisać „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

9
Bardzo pomocny w notesie IPython i Jupyter, jeśli przypadkowo usuniesz więcej niż jedną komórkę zawierającą funkcje, które właśnie spędziłeś dzień tworząc i testując ....
AGS

Do kogo, kto stracił całą klasę: możesz przywrócić ją metodą: metoda dir(MyClass), potem MyClass.__init__??i tak dalej.
Valerij

61

Chociaż ogólnie zgadzam się, że inspectto dobra odpowiedź, nie zgadzam się z tym, że nie można uzyskać kodu źródłowego obiektów zdefiniowanych w tłumaczu. Jeśli używasz dill.source.getsourcez dill, możesz uzyskać źródło funkcji i lambd, nawet jeśli są one zdefiniowane interaktywnie. Może również uzyskać kod z metod klasy i funkcji powiązanych lub niezwiązanych zdefiniowanych w curry ... jednak kompilacja tego kodu może nie być możliwa bez kodu obiektu otaczającego.

>>> 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: cóż, to po prostu podstępne. dill.source.getsourcesprawdza historię interpretera pod kątem funkcji, klas, lambd itp. - nie sprawdza zawartości ciągów przekazywanych do exec.
Mike McKerns

To wydaje się bardzo interesujące. Czy można użyć dillodpowiedzi na to pytanie: stackoverflow.com/questions/13827543/…
ArtOfWarfare

@ArtOfWarfare: częściowo, tak. dill.sourcePosiada funkcje jak getnamei importablei getsourcektóre koncentrują się na uzyskaniu kodu źródłowego (lub importable że plony obiektu) dla danego obiektu. Dla prostych rzeczy, takich jak an, intnie ma źródła, więc nie działa zgodnie z oczekiwaniami (tzn. Dla „a = 10” zwraca „10”).
Mike McKerns,

>>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Działa to

@MikeMcKerns: Zrobiłem co w mojej mocy, aby odpowiedzieć na to pytanie bez użycia dill, ale moja odpowiedź pozostawia trochę do życzenia (IE, jeśli masz wiele nazw dla tej samej wartości, nie może ustalić, która została użyta. Jeśli przekazać wyrażenie, nie można powiedzieć, co to było wyrażenie. Cholera, jeśli podasz wyrażenie, które jest takie samo jak imię, nada mu to nazwę.) Może dillrozwiązać dowolne z tych niedociągnięć mojej odpowiedzi tutaj: stackoverflow.com/a/28634996/901641
ArtOfWarfare

21

Aby rozwinąć odpowiedź Runeha:

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

EDYCJA: Jak wskazano przez @ 0sh, ten przykład działa przy użyciu, ipythonale nie zwykły python. Jednak w przypadku importowania kodu z plików źródłowych powinno być dobrze w obu przypadkach.


2
To nie zadziała, ponieważ interpreter skompiluje foo do kodu bajtowego i wyrzuci kod źródłowy, podnosząc błąd OSErrr, jeśli spróbujesz uruchomić getsource(foo).
Milo Wielondek,

@ 0sh dobry punkt, jeśli chodzi o waniliowego interpretera python. Jednak powyższy przykład kodu działa podczas korzystania z IPython.
TomDotTom

11

Możesz użyć inspectmodułu, aby uzyskać pełny kod źródłowy. Musisz użyć getsource()do tego metody z inspectmodułu. Na przykład:

import inspect

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

print(inspect.getsource(get_my_code))

Możesz sprawdzić więcej opcji pod poniższym linkiem. pobierz swój kod python


7

Ponieważ ten post jest oznaczony jako duplikat tego drugiego postu , odpowiadam tutaj w sprawie „lambda”, chociaż OP nie dotyczy lambdas.

Tak więc, dla funkcji lambda, które nie są zdefiniowane we własnych liniach: oprócz odpowiedzi marko.ristin , możesz chcieć użyć mini-lambda lub użyć SymPy, jak sugerowano w tej odpowiedzi .

  • mini-lambda jest lżejszy i obsługuje wszelkie operacje, ale działa tylko dla jednej zmiennej
  • SymPyjest cięższy, ale o wiele bardziej wyposażony w operacje matematyczne / rachunku różniczkowego. W szczególności może uprościć twoje wyrażenia. Obsługuje również kilka zmiennych w tym samym wyrażeniu.

Oto jak możesz to zrobić za pomocą 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))

Prawidłowo daje

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

x ** 2

Szczegółowe informacje można znaleźć w mini-lambda dokumentacji . Nawiasem mówiąc, jestem autorem;)


5

Pamiętaj, że zaakceptowane odpowiedzi działają tylko wtedy, gdy lambda jest podana w osobnym wierszu. Jeśli przekażesz go jako argument funkcji i chcesz pobrać kod lambda jako obiekt, problem stanie się nieco trudny, ponieważ inspectda ci całą linię.

Rozważmy na przykład plik test.py:

import inspect

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

if __name__ == "__main__":
    main()

Wykonanie go daje (uwaga na wcięcie!):

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

Aby pobrać kod źródłowy lambda, moim zdaniem najlepiej jest ponownie przeanalizować cały plik źródłowy (przy użyciu f.__code__.co_filename) i dopasować węzeł AST lambda według numeru linii i jego kontekstu.

Musieliśmy zrobić dokładnie, że w naszym design-by-umowy biblioteki icontract ponieważ mieliśmy do analizowania funkcje lambda Mijamy się jako argumenty dekoratorów. Jest tu za dużo kodu do wklejenia, więc spójrz na implementację tej funkcji .


4

Jeśli ściśle definiujesz funkcję samodzielnie i jest to stosunkowo krótka definicja, rozwiązaniem bez zależności byłoby zdefiniowanie funkcji w ciągu i przypisanie funkcji eval () wyrażeniu do funkcji.

Na przykład

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

następnie opcjonalnie, aby dołączyć oryginalny kod do funkcji:

func.source = funcstring

2
Użycie eval () wydaje mi się naprawdę, NAPRAWDĘ złe, chyba że piszesz jakiś interaktywny interpreter Pythona. Eval stwarza drastyczne problemy bezpieczeństwa. Jeśli zastosujesz politykę polegającą wyłącznie na ewaluacji literałów łańcuchowych, nadal tracisz różnorodność pomocnych zachowań, od podświetlania składni po właściwe odzwierciedlenie klas zawierających ewaluowane elementy.
Mark E. Haase

2
Upvoting. @mehaase: bezpieczeństwo oczywiście nie stanowi tutaj problemu. Twoje pozostałe komentarze są jednak dość trafne, chociaż powiedziałbym, że brak podświetlania składni jest kombinacją błędu IDE i faktu, że python nie jest językiem homoiconic.
ninjagecko

7
@ninjagecko Bezpieczeństwo zawsze stanowi problem, gdy udzielasz porad opinii publicznej. Większość czytelników przyjeżdża tutaj, ponieważ szukają w Google pytań. Nie sądzę, aby wiele osób skopiowało tę odpowiedź dosłownie; zamiast tego zastosują koncepcję, której się nauczyli, i zastosują ją do własnego problemu.
Mark E. Haase,

4

podsumowując:

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

0

Uważam , że nazwy zmiennych nie są przechowywane w plikach pyc / pyd / pyo, więc nie możesz pobrać dokładnych wierszy kodu, jeśli nie masz plików źródłowych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.