python - resueltos - ¿Es posible acceder a funciones y clases internas a través de objetos de código?
poo python 3 (1)
A1: Cosas que pueden ayudarte son -
Constantes del objeto de código
De la documentación :
Si un objeto de código representa una función, el primer elemento en co_consts es la cadena de documentación de la función, o None si no está definido.
Además, si un objeto de código representa una clase, el primer elemento de co_consts
es siempre el nombre calificado de esa clase. Puedes intentar usar esta información.
La siguiente solución funcionará correctamente en la mayoría de los casos, pero deberá omitir los objetos de código que Python crea para las comprensiones de la lista / conjunto / dict y las expresiones del generador:
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))
huellas dactilares
<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>''
Banderas de código
Hay una forma de obtener la información requerida de co_flags
. Citando la documentación que he vinculado anteriormente:
Los siguientes bits de marca se definen para co_flags : el bit 0x04 se establece si la función usa la sintaxis * arguments para aceptar un número arbitrario de argumentos posicionales; el bit 0x08 se establece si la función usa ** la sintaxis de las palabras clave para aceptar argumentos arbitrarios de palabra clave; bit 0x20 se establece si la función es un generador.
Otros bits en co_flags están reservados para uso interno.
Los indicadores se manipulan en 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;
}
}
...
}
Hay 2 indicadores de código ( CO_NEWLOCALS
y CO_OPTIMIZED
) que no se establecerán para las clases. Puede usarlos para verificar el tipo (no significa que deba hacerlo; los detalles de implementación mal documentados pueden cambiar en el futuro):
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))
La salida es exactamente la misma.
Bytecode de la función externa
También es posible obtener el tipo de objeto inspeccionando el bytecode de la función externa.
Buscar instrucciones de bytecode para encontrar bloques con LOAD_BUILD_CLASS
, significa la creación de una clase ( LOAD_BUILD_CLASS
- Impulsa builtins .__ build_class __ () en la pila. Posteriormente CALL_FUNCTION lo llama para construir una clase ) .
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))
La salida es la misma (de nuevo).
A2: Claro.
Código fuente
Para obtener el código fuente de los objetos de código, debe usar inspect.getsource
o equivalente:
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))
Por ejemplo, nested_sources(complex_func)
(ver a continuación)
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 ())
debe proporcionar el código fuente para la primera lambda
, decorator
, b
(incluido @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
==============================
Función y tipo objetos
Si aún necesita un objeto de función / clase, puede eval
/ exec
el código fuente.
Ejemplo
para funciones
lambda
:In [39]: source = sources[0] In [40]: eval(source, func.__globals__) Out[40]: <function __main__.<lambda>>
para funciones regulares
In [21]: source, local = sources[1], {} In [22]: exec(source, func.__globals__, local) In [23]: local.popitem()[1] Out[23]: <function __main__.decorator>
para clases
In [24]: source, local = sources[3], {} In [25]: exec(source, func.__globals__, local) In [26]: local.popitem()[1] Out[26]: __main__.c
Digamos que hay una función func
def func():
class a:
def method(self):
return ''method''
def a(): return ''function''
lambda x: ''lambda''
que necesito examinar.
Como parte del examen, quiero "recuperar" el código fuente u objetos de todas las clases y funciones anidadas (si las hay). Sin embargo, me doy cuenta de que aún no existen y no hay una forma directa / limpia de acceder a ellos sin ejecutar func
o definirlos fuera (antes) del func
. Desafortunadamente, lo máximo que puedo hacer es importar un módulo que contenga func
para obtener el objeto de función func
.
Descubrí que las funciones tienen el atributo __code__
contiene el objeto de code
, que tiene el atributo co_consts
así que escribí esto:
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>]
Esos objetos de code
se ven muy similares y no creo que contengan datos necesarios para ayudarme a distinguir entre los tipos de objetos que representan (por ejemplo, type
y function
).
P1: ¿Estoy en lo cierto?
P2: ¿Hay alguna forma de acceder a clases / funciones (ordinarias y lambdas) definidas dentro del cuerpo de la función?