是否可以通过代码对象访问内部函数和类? [英] Is it possible to access inner functions and classes via code objects?
问题描述
说有一个功能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
对象看起来非常相似,我认为它们没有包含必要的数据来帮助我区分它们代表的对象类型(例如type
和function
).
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_NEWLOCALS
和CO_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 ())
必须产生第一个lambda
,decorator
,b
(包括@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屋!