【问题标题】:How can I get the source code of a Python function?如何获取 Python 函数的源代码?
【发布时间】:2010-09-30 11:00:18
【问题描述】:

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

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

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

【问题讨论】:

标签: python function


【解决方案1】:

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 的评论包含实际答案。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    如果函数来自文件系统上可用的源文件,那么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的源代码
    【解决方案4】:

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

    例如

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

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

    func.source = funcstring
    

    【讨论】:

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

    如果源代码不可用,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 编写的
    【解决方案6】:

    虽然我通常同意inspect 是一个很好的答案,但我不同意您无法获得解释器中定义的对象的源代码。如果您使用dill 中的dill.source.getsource,您可以获得函数和lambda 的来源,即使它们是交互定义的。 它还可以从 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')。
    • 这确实适用于全局变量:&gt;&gt;&gt; a = 10; print( [key for key, val in globals().items() if val is a][0] )
    • @MikeMcKerns:我已尽我所能在不使用dill 的情况下回答了这个问题,但我的回答还有一点不足之处(即,如果你有多个相同值的名称,它无法弄清楚使用了哪个。如果你传入一个表达式,它不能说那个表达式是什么。哎呀,如果你传入一个与名称相同的表达式,它会给出那个名称.) dill 可以在这里解决我的答案中的任何缺点:stackoverflow.com/a/28634996/901641
    【解决方案7】:

    扩展 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。
    • @0sh 就香草 python 解释器而言是个好点。但是,上面的代码示例在使用 IPython 时有效。
    【解决方案8】:

    如果你使用的是 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
    

    【讨论】:

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

    总结:

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

    【讨论】:

      【解决方案10】:

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

      import inspect
      
      def get_my_code():
          x = "abcd"
          return x
      
      print(inspect.getsource(get_my_code))
      

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

      【讨论】:

        【解决方案11】:

        请注意,仅当 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

        【讨论】:

          【解决方案12】:

          由于这篇文章被标记为this other post 的重复,我在这里回答“lambda”的情况,虽然 OP 不是关于 lambdas。

          因此,对于未在自己的行中定义的 lambda 函数:除了marko.ristin 的答案,您可能希望使用mini-lambda 或使用SymPy,如this answer 中所建议的那样。

          • 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-lambdadocumentation。顺便说一句,我是作者;)

          【解决方案13】:

          Rafał Dowgird's answer 状态:

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

          但是,可以检索从字符串编译的函数的源代码,前提是编译代码还向linecache.cache dict添加了一个条目:

          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 等调试器能够单步执行代码的功能。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-12-25
            • 2011-09-09
            • 1970-01-01
            • 1970-01-01
            • 2020-10-16
            相关资源
            最近更新 更多