Как я могу получить исходный код функции Python?

407

Предположим, у меня есть функция Python, как определено ниже:

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

Я могу получить имя функции, используя foo.func_name. Как я могу программно получить его исходный код, как я напечатал выше?

Vertexwahn
источник
1
Обратите внимание, что в Python 3 вы можете получить имя функции, используяfoo.__name__
MikeyE
Вы также можете получить много других вещей .
not2qubit

Ответы:

543

Если функция из исходного файла, доступного в файловой системе, то inspect.getsource(foo)может помочь:

Если fooопределяется как:

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

Затем:

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

Возвращает:

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

Но я считаю, что если функция скомпилирована из строки, потока или импортирована из скомпилированного файла, то вы не сможете получить ее исходный код.

Рафал Доугирд
источник
2
Возвращает кортеж; tuple [0] - это список строк, представляющих строки исходного кода, а tuple [1] - это номер строки в контексте выполнения, где он был запущен. В IPython; это номер строки в ячейке, а не весь блокнот
The Red Pea
12
Этот ответ явно не упоминает об этом, но inspect.getsource (foo) возвращает источник в одной строке вместо кортежа, где tuple [0] - это список строк. getsource будет более полезен, если вам нужно заглянуть в
реплей
это не работает, например, с функцией len. Где я могу найти исходный код lenфункции?
oaklander114
1
илиinspect.getsourcelines(foo)
Славомир Ленарт
1
@ oaklander113 inspect.getsource не работает со встроенными модулями, как большинство функций из стандартной библиотеки. Вы можете проверить исходный код для cpython на их веб-сайте или в Github
Nicolas Abril,
184

Модуль inspect имеет методы для извлечения исходного кода из объектов Python. По-видимому, это работает только в том случае, если источник находится в файле. Если бы у вас было это, я думаю, вам не нужно было бы получать источник от объекта.

runeh
источник
3
Да, похоже, он работает только для объектов, определенных в файле. Не для тех, которые определены в переводчике.
Састанин
3
к моему удивлению, это работает и в ноутбуках Ipython / Jupyter
д-р Гул
1
Я пытался использовать inspect в python 3.5.3интерпретаторе. import inspect+ inspect.getsource(foo)работал нормально.
Андре С. Андерсен
@ AndréChristofferAndersen Да, но это не должно работать для функций, определенных в интерпретаторе
кто-то
86

dis Ваш друг, если исходный код недоступен:

>>> 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
источник
1
Выдает ошибку типа для встроенных функций.
Нумен
8
@Noumenon, потому что у них обычно нет исходного кода на Python, они написаны на C
schlamar
83

Если вы используете IPython, то вам нужно набрать "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
источник
9
Очень полезно в записной книжке IPython и Jupyter, если / когда вы случайно удалили несколько ячеек, содержащих функции, которые вы только что потратили на создание и тестирование…
AGS
Кому, кто потерял весь класс: вы можете восстановить его методом по методу:, dir(MyClass)затем MyClass.__init__??и так далее.
Валерий
62

Хотя я в целом согласен, что inspectэто хороший ответ, я не согласен с тем, что вы не можете получить исходный код объектов, определенных в интерпретаторе. Если вы используете dill.source.getsourceиз dill, вы можете получить источник функций и лямбд, даже если они определены в интерактивном режиме. Он также может получить код для связанных или несвязанных методов и функций класса, определенных в карри ... однако вы не сможете скомпилировать этот код без кода вмещающего объекта.

>>> 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: ну, это просто подлый. dill.source.getsourceпроверяет историю интерпретатора для функций, классов, лямбд и т. д. - он не проверяет содержимое строк, переданных в exec.
Майк Маккернс,
Это кажется очень интересным. Можно ли использовать dillдля ответа на этот вопрос: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ArtOfWarfare: частично, да. dill.sourceимеет такие функции, как getnameи, importableи getsourceкоторые направлены на получение исходного кода (или импортируемого, который возвращает объект) для любого данного объекта. Для простых вещей , как intнет нет источника, так что это не работает , как ожидалось (т.е. «а = 10» возвращает «10»).
Майк Маккернс
Это действительно работает для глобалов, однако: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Майк Маккернс
@MikeMcKerns: я приложил все усилия, чтобы ответить на этот вопрос без использования dill, но мой ответ оставляет желать лучшего (IE, если у вас есть несколько имен для одного и того же значения, он не может выяснить, какое использовалось. Если вы передать выражение, оно не может сказать, что это было за выражение. Черт, если вы передадите выражение, которое соответствует имени, оно даст это имя вместо этого.) Может dillустранить любой из этих недостатков моего ответа здесь: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Чтобы расширить ответ Руны:

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

РЕДАКТИРОВАТЬ: Как указывает @ 0sh, этот пример работает, ipythonно не простой python. Однако при импорте кода из исходных файлов все должно быть хорошо.

TomDotTom
источник
2
Это не сработает, поскольку интерпретатор скомпилирует foo в байт-код и выбросит исходный код, вызывая OSError, если вы попытаетесь запустить getsource(foo).
Майло Виелондек
@ 0sh хороший момент, когда речь идет о ванильном интерпретаторе python. Однако приведенный выше пример кода работает при использовании IPython.
TomDotTom
11

Вы можете использовать inspectмодуль, чтобы получить полный исходный код для этого. Вы должны использовать getsource()метод для этого из inspectмодуля. Например:

import inspect

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

print(inspect.getsource(get_my_code))

Вы можете проверить это больше вариантов по ссылке ниже. получить ваш код Python

Крутарт Гор
источник
7

Поскольку этот пост помечен как дубликат этого другого поста , я отвечу здесь за случай «лямбда», хотя ОП не касается лямбд.

Таким образом, для лямбда-функций, которые не определены в своих собственных строках: в дополнение к ответу marko.ristin вы можете использовать мини-лямбду или SymPy, как предложено в этом ответе .

  • mini-lambda легче и поддерживает любые операции, но работает только для одной переменной
  • SymPyтяжелее, но гораздо больше оснащено математическими / вычислительными операциями. В частности, это может упростить ваши выражения. Он также поддерживает несколько переменных в одном выражении.

Вот как вы можете сделать это, используя 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))

Это правильно дает

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

x ** 2

Смотрите mini-lambda документацию для деталей. Я, кстати, автор;)

smarie
источник
5

Пожалуйста, обратите внимание, что принятые ответы работают, только если лямбда дана в отдельной строке. Если вы передадите его в качестве аргумента функции и захотите получить лямбда-код в виде объекта, проблема будет немного хитрой, поскольку inspectдаст вам всю строку.

Например, рассмотрим файл test.py:

import inspect

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

if __name__ == "__main__":
    main()

Выполнение этого дает вам (помните абзац!):

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

Чтобы получить исходный код лямбда-выражения, лучше всего, на мой взгляд, повторно проанализировать весь исходный файл (используя f.__code__.co_filename) и сопоставить лямбда-узел AST по номеру строки и ее контексту.

Мы должны были сделать именно это в нашей библиотеке конструирования по контракту icontract, поскольку нам пришлось анализировать лямбда-функции, которые мы передаем в качестве аргументов декораторам. Здесь слишком много кода для вставки, так что взгляните на реализацию этой функции .

marko.ristin
источник
4

Если вы строго определяете функцию самостоятельно, и это относительно короткое определение, решение без зависимостей будет состоять в том, чтобы определить функцию в строке и присвоить eval () выражения вашей функции.

Например

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

затем при желании присоединить исходный код к функции:

func.source = funcstring
cherplunk
источник
2
Использование eval () кажется мне действительно, ОЧЕНЬ плохим, если вы не пишете какой-то интерактивный интерпретатор Python. Eval открывает серьезные проблемы с безопасностью. Если вы применяете политику, состоящую только в оценке строковых литералов, вы все равно теряете множество полезных действий - от подсветки синтаксиса до правильного отражения классов, которые содержат проверенные члены.
Марк Э. Хаас
2
Upvoting. @mehaase: безопасность здесь явно не проблема. Ваши другие комментарии, тем не менее, весьма актуальны, хотя я бы сказал, что отсутствие подсветки синтаксиса - это сочетание ошибки IDE и того факта, что python не является гомоиконическим языком.
ninjagecko
7
@ninjagecko Безопасность - это всегда проблема, когда вы даете советы широкой публике. Большинство читателей приезжают сюда, потому что задают вопросы по поиску. Я не думаю, что многие люди будут копировать этот ответ дословно; вместо этого они собираются взять концепцию, которую они изучили, и применить ее к своей проблеме.
Марк Э. Хаас
4

обобщить :

import inspect
print( "".join(inspect.getsourcelines(foo)[0]))
douardo
источник
0

Я считаю, что имена переменных не хранятся в файлах pyc / pyd / pyo, поэтому вы не можете получить точные строки кода, если у вас нет исходных файлов.

Abgan
источник