A1:可以帮助你的事情是——
代码对象的常量
从文档中:
如果代码对象表示一个函数,则co_consts中的第一项是函数的文档字符串,如果未定义,则为None 。
此外,如果代码对象表示一个类,则第一项co_consts
始终是该类的限定名称。您可以尝试使用此信息。
以下解决方案在大多数情况下都能正常工作,但您必须跳过 Python 为 list/set/dict 理解和生成器表达式创建的代码对象:
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定义了以下标志位:如果函数使用*arguments语法接受任意数量的位置参数,则设置位 0x04 ;如果函数使用
**keywords语法接受任意关键字参数,则设置位 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;
}
}
...
}
有 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
-将 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))
输出是相同的(再次)。
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