如何获取Python函数的源代码?

假设我有一个如下定义的Python函数:

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

我可以使用foo.func_name获取函数的名称。我如何通过编程获得它的源代码,就像我上面键入的那样?

369590 次浏览

检查模块有从python对象中检索源代码的方法。但是,似乎只有当源文件位于文件中时,它才有效。如果你有这个,我猜你就不需要从对象中获取源了。


下面使用Python 3.6测试inspect.getsource(foo):

import inspect


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


source_foo = inspect.getsource(foo)  # foo is normal function
print(source_foo)


source_max = inspect.getsource(max)  # max is a built-in function
print(source_max)

这是第一次印刷:

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

然后在inspect.getsource(max)上失败,出现以下错误:

TypeError: <built-in function max> is not a module, class, method, function, traceback, frame, or code object

变量名不存储在pyc/pyd/pyo文件中,因此如果没有源文件,则无法检索准确的代码行。

如果函数来自文件系统中可用的源文件,那么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

但我相信,如果函数是从字符串、流或从编译文件导入的,那么你就不能检索它的源代码。

如果您自己严格地定义了函数,并且它是一个相对较短的定义,那么没有依赖关系的解决方案将是在字符串中定义函数,并将表达式的eval()赋值给您的函数。

如。

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

然后可选地将原始代码附加到函数:

func.source = funcstring

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
虽然我通常同意inspect是一个很好的答案,但我不同意你不能得到解释器中定义的对象的源代码。如果您从dill使用dill.source.getsource,您可以获得函数和lambda的源代码,即使它们是交互定义的。 它还可以从在curry中定义的绑定或未绑定类方法和函数中获取代码…但是,如果没有包含该对象的代码,则可能无法编译该代码
>>> 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


>>>

扩展一下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

编辑:正如@0sh指出的那样,这个例子使用ipython工作,而不是简单的python。但是,当从源文件导入代码时,在这两种情况下都应该没问题。

只需使用foo????foo即可。

如果您正在使用IPython,那么您需要键入foo????foo来查看完整的源代码。要只查看函数中的文档字符串,请使用foo??foo。这也适用于Jupyter笔记本电脑。

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

总结一下:

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

您可以使用inspect模块来获得完整的源代码。你必须从inspect模块中使用getsource()方法。例如:

import inspect


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


print(inspect.getsource(get_my_code))

你可以在下面的链接中查看更多选项。 # EYZ0 < / p >

请注意,只有在单独的一行上给出lambda时,接受的答案才有效。如果您将它作为参数传递给一个函数,并希望检索lambda作为对象的代码,那么问题就有点棘手了,因为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

在我看来,要检索lambda的源代码,最好的方法是重新解析整个源文件(使用f.__code__.co_filename),并根据行号及其上下文匹配lambda AST节点。

我们必须在契约式设计库icontract中精确地做到这一点,因为我们必须解析作为参数传递给装饰器的lambda函数。这里粘贴的代码太多了,所以看一下本函数的实现

由于这篇文章被标记为这是另一篇文章的副本,我在这里回答“lambda”情况,尽管OP不是关于lambdas的。

因此,对于没有在它们自己的行中定义的lambda函数:除了marko.ristin的答案之外,您可能希望使用mini-lambda或使用这个答案中建议的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 文档。顺便说一下,我是作者;)

# EYZ0状态:

我相信,如果函数是从字符串、流或从编译文件导入的,那么你就不能检索它的源代码。

然而,它可以检索从字符串编译的函数的源代码,前提是编译代码也添加了一个条目到linecache.cache字典:

import linecache
import inspect


script = '''
def add_nums(a, b):
return a + b
'''


bytecode = compile(script, 'unique_filename', 'exec')
tmp = {}
eval(bytecode, {}, tmp)
add_nums = tmp["add_nums"]


linecache.cache['unique_filename'] = (
len(script),
None,
script.splitlines(True),
'unique_filename',
)


print(inspect.getsource(add_nums))


# prints:
# """
# def add_nums(a, b):
#    return a + b
# """

这就是# EYZ0图书馆自动为类创建各种方法的方式,给定一组类期望初始化的属性。看看他们的源代码在这里。正如源代码所解释的,这是一个主要用于使调试器(如PDB)能够逐级遍历代码的特性。