您当前的位置:首页 > 计算机 > 编程开发 > Python

如何获取 Python 对象的源代码

时间:12-14来源:作者:点击数:
城东书院 www.cdsy.xyz

有时候我们会想要知道某个 Python 函数、类或者模块(我们把这些东西统称为 Python 的对象)是怎么定义的,或者找出它们是在哪个文件中定义的。有两个库能够帮我们做到这一点,一个是内置的 inspect 库,一个是第三方的 dill 库。

inspect 库

用 help(inspect) 能看到关于 inspect 的描述如下:

DESCRIPTION
    This module encapsulates the interface provided by the internal special
    attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion.
    It also provides some help for examining source code and class layout.

我们主要是使用该模块的 getsource* 系列函数,即:

1、inspect.getsource(object) 函数以单字符串的形式返回某个 python 对象的源代码

import inspect
return inspect.getsource(inspect.getsource)
def getsource(object):
    """Return the text of the source code for an object.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a single string.  An
    OSError is raised if the source code cannot be retrieved."""
    lines, lnum = getsourcelines(object)
    return ''.join(lines)

2、inspect.getsourcefile(object) 函数返回某个 python 对象在哪个文件中定义

import inspect
return inspect.getsourcefile(inspect.getsourcefile)
/usr/lib/python3.6/inspect.py

3、inspect.getsourcelines(object) 函数返回一个元组,其中包含了 python 对象源代码的 list,以及其在源代码中定义的起始行

import inspect
return inspect.getsourcelines(inspect.getsourcelines)
(['def getsourcelines(object):\n',
'    """Return a list of source lines and starting line number for an object.\n',
'\n', '    The argument may be a module, class, method, function, traceback, frame,\n',
'    or code object.  The source code is returned as a list of the lines\n',
'    corresponding to the object and the line number indicates where in the\n',
'    original source file the first line of code was found.  An OSError is\n',
'    raised if the source code cannot be retrieved."""\n',
'    object = unwrap(object)\n', '    lines, lnum = findsource(object)\n',
'\n', '    if ismodule(object):\n',
'        return lines, 0\n', '    else:\n',
'        return getblock(lines[lnum:]),
lnum + 1\n'], 946)

然而, inspect 有一个缺陷,那就是无法获取 interactive python session 中定义对象的源代码:

import inspect
def sum(a,b):
    return a+b
return inspect.getsource(sum)
Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
  File "<stdin>", line 6, in main
  File "/usr/lib/python3.6/inspect.py", line 968, in getsource
    lines, lnum = getsourcelines(object)
  File "/usr/lib/python3.6/inspect.py", line 955, in getsourcelines
    lines, lnum = findsource(object)
  File "/usr/lib/python3.6/inspect.py", line 786, in findsource
    raise OSError('could not get source code')
OSError: could not get source code

若想要能够获取 interactive python session 中定义对象的源代码,则可以使用第三方的 dill 库

dill 库

dill 有一个 source 子 module,它提供了跟 inspect 非常类似的 API 接口:

1、dill.source.getsource 函数

import dill
return dill.source.getsource(dill.source.getsource)
def getsource(object, alias='', lstrip=False, enclosing=False, \
                                              force=False, builtin=False):
    """Return the text of the source code for an object. The source code for
    interactively-defined objects are extracted from the interpreter's history.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a single string.  An
    IOError is raised if the source code cannot be retrieved, while a
    TypeError is raised for objects where the source code is unavailable
    (e.g. builtins).

    If alias is provided, then add a line of code that renames the object.
    If lstrip=True, ensure there is no indentation in the first line of code.
    If enclosing=True, then also return any enclosing code.
    If force=True, catch (TypeError,IOError) and try to use import hooks.
    If builtin=True, force an import for any builtins
    """
    # hascode denotes a callable
    hascode = _hascode(object)
    # is a class instance type (and not in builtins)
    instance = _isinstance(object)

    # get source lines; if fail, try to 'force' an import
    try: # fails for builtins, and other assorted object types
        lines, lnum = getsourcelines(object, enclosing=enclosing)
    except (TypeError, IOError): # failed to get source, resort to import hooks
        if not force: # don't try to get types that findsource can't get
            raise
        if not getmodule(object): # get things like 'None' and '1'
            if not instance: return getimport(object, alias, builtin=builtin)
            # special handling (numpy arrays, ...)
            _import = getimport(object, builtin=builtin)
            name = getname(object, force=True)
            _alias = "%s = " % alias if alias else ""
            if alias == name: _alias = ""
            return _import+_alias+"%s\n" % name
        else: #FIXME: could use a good bit of cleanup, since using getimport...
            if not instance: return getimport(object, alias, builtin=builtin)
            # now we are dealing with an instance...
            name = object.__class__.__name__
            module = object.__module__
            if module in ['builtins','__builtin__']:
                return getimport(object, alias, builtin=builtin)
            else: #FIXME: leverage getimport? use 'from module import name'?
                lines, lnum = ["%s = __import__('%s', fromlist=['%s']).%s\n" % (name,module,name,name)], 0
                obj = eval(lines[0].lstrip(name + ' = '))
                lines, lnum = getsourcelines(obj, enclosing=enclosing)

    # strip leading indent (helps ensure can be imported)
    if lstrip or alias:
        lines = _outdent(lines)

    # instantiate, if there's a nice repr  #XXX: BAD IDEA???
    if instance: #and force: #XXX: move into findsource or getsourcelines ?
        if '(' in repr(object): lines.append('%r\n' % object)
       #else: #XXX: better to somehow to leverage __reduce__ ?
       #    reconstructor,args = object.__reduce__()
       #    _ = reconstructor(*args)
        else: # fall back to serialization #XXX: bad idea?
            #XXX: better not duplicate work? #XXX: better new/enclose=True?
            lines = dumpsource(object, alias='', new=force, enclose=False)
            lines, lnum = [line+'\n' for line in lines.split('\n')][:-1], 0
       #else: object.__code__ # raise AttributeError

    # add an alias to the source code
    if alias:
        if hascode:
            skip = 0
            for line in lines: # skip lines that are decorators
                if not line.startswith('@'): break
                skip += 1
            #XXX: use regex from findsource / getsourcelines ?
            if lines[skip].lstrip().startswith('def '): # we have a function
                if alias != object.__name__:
                    lines.append('\n%s = %s\n' % (alias, object.__name__))
            elif 'lambda ' in lines[skip]: # we have a lambda
                if alias != lines[skip].split('=')[0].strip():
                    lines[skip] = '%s = %s' % (alias, lines[skip])
            else: # ...try to use the object's name
                if alias != object.__name__:
                    lines.append('\n%s = %s\n' % (alias, object.__name__))
        else: # class or class instance
            if instance:
                if alias != lines[-1].split('=')[0].strip():
                    lines[-1] = ('%s = ' % alias) + lines[-1]
            else:
                name = getname(object, force=True) or object.__name__
                if alias != name:
                    lines.append('\n%s = %s\n' % (alias, name))
    return ''.join(lines)

2、dill.source.getsourcefile 函数

import dill
return dill.source.getsourcefile(dill.source.getsourcefile)
/usr/lib/python3.6/inspect.py

3、dill.source.getsourcelines 函数

import dill
return dill.source.getsourcelines(dill.source.getsourcelines)
(['def getsourcelines(object, lstrip=False, enclosing=False):\n',
'    """Return a list of source lines and starting line number for an object.\n',
"    Interactively-defined objects refer to lines in the interpreter's history.\n",
'\n', '    The argument may be a module, class, method, function, traceback, frame,\n',
'    or code object.  The source code is returned as a list of the lines\n',
'    corresponding to the object and the line number indicates where in the\n',
'    original source file the first line of code was found.  An IOError is\n',
'    raised if the source code cannot be retrieved, while a TypeError is\n',
'    raised for objects where the source code is unavailable (e.g. builtins).\n',
'\n', '    If lstrip=True, ensure there is no indentation in the first line of code.\n',
'    If enclosing=True, then also return any enclosing code."""\n',
'    code, n = getblocks(object, lstrip=lstrip, enclosing=enclosing, locate=True)\n',
'    return code[-1], n[-1]\n'], 300)

不过它还支持获取 interactive python session 中定义对象的源代码:

screenshot-01.png
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐