是否可以通过代码对象访问内部函数和类?

vau*_*tah 4 python nested bytecode code-inspection python-3.x

说有一个功能 func

def func():
    class a:
        def method(self):
            return 'method'
    def a(): return 'function'
    lambda x: 'lambda'
Run Code Online (Sandbox Code Playgroud)

我需要检查一下.

作为考试的一部分,我想"检索"所有嵌套类和函数的源代码或对象(如果有的话).但是我确实意识到它们还不存在,并且没有直接/干净的方式来访问它们而不在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>]
Run Code Online (Sandbox Code Playgroud)

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

Q1:我是对的吗?

Q2:没有办法访问函数体中定义的类/函数(普通函数和lambdas函数)?

vau*_*tah 7

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

代码对象的常量

文档:

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

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

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

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))
Run Code Online (Sandbox Code Playgroud)

版画

<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>'
Run Code Online (Sandbox Code Playgroud)

代码标志

有办法从中获取所需的信息co_flags.引用我上面链接的文档:

co_flags定义了以下标志位:如果函数使用*arguments语法接受任意数量的位置参数,则设置位0x04 ; 如果函数使用**关键字语法接受任意关键字参数,则设置位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;
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

有两个代码标志(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))
Run Code Online (Sandbox Code Playgroud)

输出完全相同.

Bytecode的外部功能

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

搜索字节码指令以查找块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)) 
Run Code Online (Sandbox Code Playgroud)

输出是相同的(再次).

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))
Run Code Online (Sandbox Code Playgroud)

例如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 ())
Run Code Online (Sandbox Code Playgroud)

必须产生源代码,第一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
==============================
Run Code Online (Sandbox Code Playgroud)

功能和类型对象

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