A1:可以帮助你的事情是 -
代码对象的常量
来自documentation:
如果一个代码对象表示一个函数,co_consts 中的第一项是
函数的文档字符串,如果未定义,则为 None。
另外,如果代码对象代表一个类,co_consts 的第一项始终是该类的限定名称。您可以尝试使用此信息。
以下解决方案在大多数情况下都能正常工作,但您必须跳过 Python 为列表/集合/字典推导和生成器表达式创建的代码对象:
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 定义了以下标志位:如果设置了位 0x04
该函数使用 *arguments 语法来接受任意数字
位置论据;如果函数使用
**keywords 语法接受任意关键字参数;如果函数是生成器,则设置位 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_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))
输出完全一样。
外部函数的字节码
也可以通过检查外部函数的字节码来获取对象类型。
搜索字节码指令以查找带有LOAD_BUILD_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))
输出相同(再次)。
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