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对象看起来非常相似,我认为它们不包含帮助我区分它们所代表的对象类型所必需的数据(例如type和function).
Q1:我是对的吗?
Q2:有没有办法访问函数体中定义的类/函数(普通函数和lambdas函数)?
从文档:
如果代码对象表示函数,则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_NEWLOCALS和CO_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)
输出完全相同.
也可以通过检查外部函数的字节码来获取对象类型.
搜索字节码指令以查找块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)
输出是相同的(再次).
为了获取代码对象的源代码,您可以使用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源代码.
例
对于lambda功能:
In [39]: source = sources[0]
In [40]: eval(source, func.__globals__)
Out[40]: <function __main__.<lambda>>
Run Code Online (Sandbox Code Playgroud)对于常规功能
In [21]: source, local = sources[1], {}
In [22]: exec(source, func.__globals__, local)
In [23]: local.popitem()[1]
Out[23]: <function __main__.decorator>
Run Code Online (Sandbox Code Playgroud)对于课程
In [24]: source, local = sources[3], {}
In [25]: exec(source, func.__globals__, local)
In [26]: local.popitem()[1]
Out[26]: __main__.c
Run Code Online (Sandbox Code Playgroud)| 归档时间: |
|
| 查看次数: |
657 次 |
| 最近记录: |