是否可以通过代码对象访问内部函数和类? [英] Is it possible to access inner functions and classes via code objects?

查看:73
本文介绍了是否可以通过代码对象访问内部函数和类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说有一个功能func

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

我需要检查.

作为检查的一部分,我想检索"所有嵌套类和函数(如果有)的源代码或对象.但是我确实意识到它们还不存在,并且没有直接/干净的方式来访问它们而无需运行func或定义 它们在func之前(之前).不幸的是,我最能做的就是导入一个包含func的模块以获得func函数对象.

As a part of the examination I want to "retrieve" source code or objects of all nested classes and functions (if any). However I do realize that they don't exist yet and there's no direct/clean way of accessing them without running func or defining them outside (before) func. Unfortunately, the most I can do is import a module containing func to obtain the func function object.

我发现函数具有包含code对象的__code__属性,该对象具有co_consts属性,因此我这样写:

I discovered that functions have the __code__ attribute containing the code object, which has the co_consts attribute so I wrote this:

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

Those code objects look awfully similar and I don't think they contain data necessary to help me distinguish between types of objects they represent (e.g. type and function).

第一季度:我对吗?

第二季度:有什么任何方法可以访问函数体内定义的类/函数(普通和lambda)?

Q2: Is there any way to access classes/functions (ordinary and lambdas) defined within the function body?

推荐答案

A1:可以帮助您的事情-

代码对象的常量

来自文档:

A1: Things that can help you are -

Constants of the code object

From the documentation:

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

If a code object represents a function, the first item in co_consts is the documentation string of the function, or None if undefined.

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

Also, if a code object represents a class, the first item of co_consts is always the qualified name of that class. You can try to use this information.

以下解决方案将在大多数情况下正常工作,但是您必须跳过Python为列表/集合/字典理解和生成器表达式创建的代码对象:

The following solution will correctly work in most cases, but you'll have to skip code objects Python creates for list/set/dict comprehensions and generator expressions:

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获取所需的信息.引用我上面链接的文档:

Code flags

There's a way to get the required information from co_flags. Citing the documentation I linked above:

co_flags 定义了以下标志位:如果满足以下条件,则设置位0x04: 该函数使用 * arguments 语法接受任意数字 位置论点如果函数使用 ** keywords 语法,可以接受任意关键字参数;如果该函数是生成器,则将位0x20设置为

The following flag bits are defined for co_flags: bit 0x04 is set if the function uses the *arguments syntax to accept an arbitrary number of positional arguments; bit 0x08 is set if the function uses the **keywords syntax to accept arbitrary keyword arguments; bit 0x20 is set if the function is a generator.

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

Other bits in co_flags are reserved for internal use.

标记在compute_code_flags( Python /compile.c ):

Flags are manipulated in 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)不会为类设置.您可以 使用它们来检查类型(这并不意味着您应该-记录不充分的实现细节将来可能会更改):

There're 2 code flags (CO_NEWLOCALS and CO_OPTIMIZED) that won't be set for classes. You can use them to check the type (doesn't mean you should - poorly documented implementation details may change in the future):

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

输出完全相同.

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

It's also possible to get object type by inspecting the bytecode of the outer function.

搜索字节码指令以找到LOAD_BUILD_CLASS块,它表示创建了一个类(LOAD_BUILD_CLASS- 将buildins .__ build_class __()推入堆栈.稍后由CALL_FUNCTION调用以构造一个类. )

Search bytecode instructions to find blocks with LOAD_BUILD_CLASS, it signifies the creation of a class (LOAD_BUILD_CLASS - Pushes builtins.__build_class__() onto the stack. It is later called by CALL_FUNCTION to construct a class.)

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

输出相同(再次).

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

In order to get the source code for code objects, you'd use inspect.getsource or equivalent:

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

必须产生第一个lambdadecoratorb(包括@decorator)和c的源代码:

must yield source code for the first lambda, decorator, b (including @decorator) and 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 源代码.

Function and type objects

If you still need a function/class object, you can eval/exec the source code.

示例

  • 用于lambda功能:

In [39]: source = sources[0]

In [40]: eval(source, func.__globals__)
Out[40]: <function __main__.<lambda>>

  • 用于常规功能

  • for regular functions

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

  • 用于课程

  • for classes

    In [24]: source, local = sources[3], {}
    
    In [25]: exec(source, func.__globals__, local)
    
    In [26]: local.popitem()[1] 
    Out[26]: __main__.c
    

  • 这篇关于是否可以通过代码对象访问内部函数和类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆