4

说有一个功能func

def func():
    class a:
        def method(self):
            return 'method'
    def a(): return 'function'
    lambda x: 'lambda'

我需要检查。

作为考试的一部分,我想“检索”所有嵌套类和函数(如果有的话)的源代码或对象。但是我确实意识到它们还不存在并且没有直接/干净的方式来访问它们而不在func外部(之前)运行或定义它们func。不幸的是,我最多只能导入一个包含func获取func函数对象的模块。

我发现函数具有__code__包含code对象的属性,该对象具有co_consts属性,所以我写了这个:

In [11]: [x for x in func.__code__.co_consts if iscode(x) and x.co_name == 'a']
Out[11]: 
[<code object a at 0x7fe246aa9810, file "<ipython-input-6-31c52097eb5f>", line 2>,
 <code object a at 0x7fe246aa9030, file "<ipython-input-6-31c52097eb5f>", line 4>]

这些code对象看起来非常相似,我认为它们不包含帮助我区分它们所代表的对象类型(例如typefunction)所需的数据。

Q1:我说的对吗?

Q2:什么方法可以访问函数体内定义的类/函数(普通和 lambdas)?

4

2 回答 2

9

A1:可以帮助你的事情是——

代码对象的常量

文档中:

如果代码对象表示一个函数,则co_consts中的第一项是函数的文档字符串,如果未定义,则为None 。

此外,如果代码对象表示一个类,则第一项co_consts始终是该类的限定名称。您可以尝试使用此信息。

以下解决方案在大多数情况下都能正常工作,但您必须跳过 Python 为 list/set/dict 理解和生成器表达式创建的代码对象:

from inspect import iscode

for x in func.__code__.co_consts:
    if iscode(x):
        # Skip <setcomp>, <dictcomp>, <listcomp> or <genexp>
        if x.co_name.startswith('<') and x.co_name != '<lambda>':
            continue
        firstconst = x.co_consts[0]
        # Compute the qualified name for the current code object
        # Note that we don't know its "type" yet
        qualname = '{func_name}.<locals>.{code_name}'.format(
                        func_name=func.__name__, code_name=x.co_name)
        if firstconst is None or firstconst != qualname:
            print(x, 'represents a function {!r}'.format(x.co_name))
        else:
            print(x, 'represents a class {!r}'.format(x.co_name))

印刷

<code object a at 0x7fd149d1a9c0, file "<ipython-input>", line 2> represents a class 'a'
<code object a at 0x7fd149d1ab70, file "<ipython-input>", line 5> represents a function 'a'
<code object <lambda> at 0x7fd149d1aae0, file "<ipython-input>", line 6> represents a function '<lambda>'

代码标志

有一种方法可以从co_flags. 引用我上面链接的文档:

为co_flags定义了以下标志位:如果函数使用*arguments语法接受任意数量的位置参数,则设置位 0x04 ;如果函数使用 **keywords语法接受任意关键字参数,则设置位 0x08;如果函数是生成器,则设置位 0x20。

co_flags中的其他位保留供内部使用。

compute_code_flags在( Python/compile.c )中操作标志:

static int
compute_code_flags(struct compiler *c)
{
    PySTEntryObject *ste = c->u->u_ste;
    ...
    if (ste->ste_type == FunctionBlock) {
        flags |= CO_NEWLOCALS | CO_OPTIMIZED;
        if (ste->ste_nested)
            flags |= CO_NESTED;
        if (ste->ste_generator)
            flags |= CO_GENERATOR;
        if (ste->ste_varargs)
            flags |= CO_VARARGS;
        if (ste->ste_varkeywords)
            flags |= CO_VARKEYWORDS;
    }

    /* (Only) inherit compilerflags in PyCF_MASK */
    flags |= (c->c_flags->cf_flags & PyCF_MASK);

    n = PyDict_Size(c->u->u_freevars);
    ...
    if (n == 0) {
        n = PyDict_Size(c->u->u_cellvars);
        ...
        if (n == 0) {
            flags |= CO_NOFREE;
        }
    }
    ...
}

有 2 个代码标志 (CO_NEWLOCALSCO_OPTIMIZED) 不会为类设置。您可以使用它们来检查类型(并不意味着您应该 - 记录不充分的实现细节将来可能会改变):

from inspect import iscode

for x in complex_func.__code__.co_consts:
    if iscode(x):
        # Skip <setcomp>, <dictcomp>, <listcomp> or <genexp>
        if x.co_name.startswith('<') and x.co_name != '<lambda>':
            continue
        flags = x.co_flags
        # CO_OPTIMIZED = 0x0001, CO_NEWLOCALS = 0x0002
        if flags & 0x0001 and flags & 0x0002:
            print(x, 'represents a function {!r}'.format(x.co_name))
        else:
            print(x, 'represents a class {!r}'.format(x.co_name))

输出完全相同。

外部函数的字节码

也可以通过检查外部函数的字节码来获取对象类型。

搜索字节码指令以查找带有LOAD_BUILD_CLASS的块,它表示创建一个类(LOAD_BUILD_CLASS-将 builtins.__build_class__() 推入堆栈。稍后由 CALL_FUNCTION 调用以构造一个类。

from dis import Bytecode
from inspect import iscode
from itertools import groupby

def _group(i):
    if i.starts_line is not None: _group.starts = i
    return _group.starts

bytecode = Bytecode(func)

for _, iset in groupby(bytecode, _group):
    iset = list(iset)
    try:
        code = next(arg.argval for arg in iset if iscode(arg.argval))
        # Skip <setcomp>, <dictcomp>, <listcomp> or <genexp>
        if code.co_name.startswith('<') and code.co_name != '<lambda>':
            raise TypeError
    except (StopIteration, TypeError):
        continue
    else:
        if any(x.opname == 'LOAD_BUILD_CLASS' for x in iset):
            print(code, 'represents a function {!r}'.format(code.co_name))
        else:
            print(code, 'represents a class {!r}'.format(code.co_name)) 

输出是相同的(再次)。

A2:当然。

源代码

为了获取代码对象的源代码,您可以使用inspect.getsource或等效:

from inspect import iscode, ismethod, getsource
from textwrap import dedent


def nested_sources(ob):
    if ismethod(ob):
        ob = ob.__func__
    try:
        code = ob.__code__
    except AttributeError:
        raise TypeError('Can\'t inspect {!r}'.format(ob)) from None
    for c in code.co_consts:
        if not iscode(c):
            continue
        name = c.co_name
        # Skip <setcomp>, <dictcomp>, <listcomp> or <genexp>
        if not name.startswith('<') or name == '<lambda>':
            yield dedent(getsource(c))

例如nested_sources(complex_func)(见下文)

def complex_func():
    lambda x: 42

    def decorator(cls):
        return lambda: cls()

    @decorator
    class b():
        def method():
            pass

    class c(int, metaclass=abc.ABCMeta):
        def method():
            pass

    {x for x in ()}
    {x: x for x in ()}
    [x for x in ()]
    (x for x in ())

必须为第一个lambda, decorator, b(包括@decorator) 和产生源代码c

In [41]: nested_sources(complex_func)
Out[41]: <generator object nested_sources at 0x7fd380781d58>

In [42]: for source in _:
   ....:     print(source, end='=' * 30 + '\n')
   ....:     
lambda x: 42
==============================
def decorator(cls):
    return lambda: cls()
==============================
@decorator
class b():
    def method():
        pass
==============================
class c(int, metaclass=abc.ABCMeta):
    def method():
        pass
==============================

函数和类型对象

如果你还需要一个函数/类对象,你可以eval/exec源代码。

例子

  • 对于lambda功能:

    In [39]: source = sources[0]
    
    In [40]: eval(source, func.__globals__)
    Out[40]: <function __main__.<lambda>>
    
  • 用于常规功能

    In [21]: source, local = sources[1], {}
    
    In [22]: exec(source, func.__globals__, local)
    
    In [23]: local.popitem()[1]
    Out[23]: <function __main__.decorator>
    
  • 上课

    In [24]: source, local = sources[3], {}
    
    In [25]: exec(source, func.__globals__, local)
    
    In [26]: local.popitem()[1] 
    Out[26]: __main__.c
    
于 2015-09-16T14:34:22.583 回答
0
Disassemble the x object. x can denote either a module, a class, a method, a function, a generator, an asynchronous generator, a coroutine, a code object, a string of source code or a byte sequence of raw bytecode. For a module, it disassembles all functions. For a class, it disassembles all methods (including class and static methods). For a code object or sequence of raw bytecode, it prints one line per bytecode instruction. It also recursively disassembles nested code objects (the code of comprehensions, generator expressions and nested functions, and the code used for building nested classes). Strings are first compiled to code objects with the compile() built-in function before being disassembled. If no object is provided, this function disassembles the last traceback.

The disassembly is written as text to the supplied file argument if provided and to sys.stdout otherwise.

The maximal depth of recursion is limited by depth unless it is None. depth=0 means no recursion.

Changed in version 3.4: Added file parameter.

Changed in version 3.7: Implemented recursive disassembling and added depth parameter.

Changed in version 3.7: This can now handle coroutine and asynchronous generator objects.

https://docs.python.org/3/library/dis.html#dis.dis

于 2018-11-25T13:55:27.700 回答