ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我可以使用 foo.func_name 获取函数的名称。如上所述,我如何以编程方式获取其源代码?

请注意,在 Python 3 中,您可以使用 foo.__name__ 获取函数名称
您也可以获得 lot of other things

S
Smart Manoj

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

但我相信,如果函数是从字符串、流编译或从编译文件导入的,那么您将无法检索其源代码。


Returns 一个元组; tuple[0] 是表示源代码行的字符串列表,tuple[1] 是运行上下文中的行号。在 IPython 中;这是 cell 中的行号,而不是整个 notebook
这个答案没有明确提到它,但是 inspect.getsource(foo) 以单个字符串而不是元组返回源,其中 tuple[0] 是行列表。如果您需要查看 repl,getsource 会更有用
它不适用于例如函数 len。在哪里可以找到 len 函数的源代码?
inspect.getsourcelines(foo)
@oaklander113 inspect.getsource 不能像标准库中的大多数函数那样与内置函数一起使用。您可以在 their websitetheir Github 查看 cpython 的源代码
A
André C. Andersen

inspect module 具有从 python 对象中检索源代码的方法。似乎它仅在源位于文件中时才有效。如果你有,我猜你不需要从对象中获取源。

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

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

是的,它似乎只适用于文件中定义的对象。不适用于解释器中定义的那些。
令我惊讶的是,它也适用于 Ipython/Jupyter 笔记本
我尝试在 python 3.5.3 解释器中使用检查。 import inspect + inspect.getsource(foo) 工作正常。
@AndréChristofferAndersen 是的,但它不适用于解释器中定义的函数
+1,但如果答案提供更多信息而只是指向文档的链接,这将很有用。 @AndréC.Andersen 的评论包含实际答案。
p
prashanth

只需使用 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

如果/当您不小心删除了多个包含您刚刚花费一天时间创建和测试的函数的单元格时,在 IPython 和 Jupyter 笔记本中非常有用......
给谁,谁丢了全班:你可以通过方法恢复它:dir(MyClass),然后MyClass.__init__??等等。
@Valerij 你能详细说明一下吗?
s
schlamar

如果源代码不可用,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

为内置函数引发 TypeError。
@Noumenon 因为它们通常没有 Python 源代码,它们是用 C 编写的
M
Mike McKerns

虽然我通常同意 inspect 是一个很好的答案,但我不同意您无法获得解释器中定义的对象的源代码。如果您使用 dill 中的 dill.source.getsource,您可以获得函数和 lambdas 的来源,即使它们是交互式定义的。它还可以从 curries 中定义的绑定或未绑定的类方法和函数中获取代码……但是,如果没有封闭对象的代码,您可能无法编译该代码。

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

>>> 

@Ant6n:好吧,那只是偷偷摸摸。 dill.source.getsource 检查解释器的函数、类、lambda 等历史记录——它不检查传递给 exec 的字符串的内容。
这似乎很有趣。是否可以使用 dill 来回答这个问题:stackoverflow.com/questions/13827543/…
@ArtOfWarfare:部分是的。 dill.source 具有 getnameimportablegetsource 等函数,它们专注于获取任何给定对象的源代码(或生成对象的可导入对象)。对于像 int 这样的简单事物,没有 no 源,因此它不能按预期工作(即对于 'a = 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
T
TomDotTom

要扩展 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 编译为字节码并丢弃源代码,如果您尝试运行 getsource(foo),则会引发 OSError。
就香草 python 解释器而言,@0sh 好点。然而,上面的代码示例在使用 IPython 时有效。
s
smarie

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

因此,对于未在自己的行中定义的 lambda 函数:除了 marko.ristin 的答案之外,您可能希望使用 mini-lambda 或按照 this answer 中的建议使用 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 documentation。顺便说一句,我是作者;)


S
Szabolcs

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

import inspect

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

print(inspect.getsource(get_my_code))

您可以在以下链接中查看更多选项。 retrieve your python code


d
douardo

总结一下:

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

m
marko.ristin

请注意,只有在单独的行上给出 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 函数。此处粘贴的代码太多,请查看 the implementation of this function


c
cherplunk

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

例如

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

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

func.source = funcstring

eval() 的使用让我觉得非常非常糟糕,除非你正在编写某种交互式 Python 解释器。 Eval 引发了严重的安全问题。如果您采用仅评估字符串文字的策略,您仍然会失去各种有用的行为,从语法突出显示到包含评估成员的类的正确反映。
赞成。 @mehaase:安全显然不是这里的问题。您的其他评论虽然非常相关,但我会说缺乏语法突出显示是 IDE 的错误和 python 不是同形语言这一事实的结合。
@ninjagecko 当您向公众提供建议时,安全始终是一个问题。大多数读者来这里是因为他们在谷歌上搜索问题。我认为没有多少人会逐字复制这个答案。相反,他们将把他们学到的概念应用到他们自己的问题上。
@MarkE.Haase 说得对,安全始终是一个需要注意的问题,但是 eval 肯定有有效的用例(或者它不会在规范中)。 attrsuses eval 为类构建自定义的 dunder 方法。不评估用户输入解决了绝大多数相关的安全问题。不过,也不是什么可小题大做的。
m
mtoor

Rafał Dowgird's answer 指出:

我相信,如果函数是从字符串、流编译或从编译文件导入的,那么您将无法检索其源代码。

但是,可以检索从字符串编译的函数的源代码,前提是编译代码还向 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
# """

这就是 attrs library 自动为类创建各种方法的方式,给定类期望初始化的一组属性。见他们的source code here。正如消息来源所解释的那样,这是一个主要旨在使 PDB 等调试器能够单步执行代码的功能。


A
Abgan

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