Nota: Esta pregunta es solo para fines informativos. Me interesa ver qué tan profundo en el interior de Python es posible llegar con esto.
No hace mucho tiempo, comenzó una discusión dentro de una determinada pregunta con respecto a si las cadenas pasadas a las declaraciones impresas podrían modificarse después / durante la llamada print. Por ejemplo, considere la función:
def print_something():
print('This cat was scared.')
Ahora, cuando printse ejecuta, la salida al terminal debería mostrar:
This dog was scared.
Observe que la palabra "gato" ha sido reemplazada por la palabra "perro". Algo en alguna parte pudo modificar esos búferes internos para cambiar lo que se imprimió. Suponga que esto se hace sin el permiso explícito del autor del código original (por lo tanto, piratería / secuestro).
Este comentario del sabio @abarnert, en particular, me hizo pensar:
Hay un par de formas de hacerlo, pero todas son muy feas y nunca deberían hacerse. La forma menos fea es probablemente reemplazar el
codeobjeto dentro de la función con uno con unaco_constslista diferente . Lo siguiente es probablemente llegar a la API de C para acceder al búfer interno del str. [...]
Entonces, parece que esto es realmente posible.
Aquí está mi manera ingenua de abordar este problema:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Por supuesto, execes malo, pero eso realmente no responde a la pregunta, porque en realidad no modifica nada durante la print llamada cuando / después .
¿Cómo se haría como lo explicó @abarnert?

42a23que por qué es una mala idea cambiar el valor de"My name is Y"a"My name is X".Respuestas:
Primero, en realidad hay una forma mucho menos hacky. Todo lo que queremos hacer es cambiar lo que
printimprime, ¿verdad?O, de manera similar, puedes usar monkeypatch en
sys.stdoutlugar deprint.Además, no hay nada malo con la
exec … getsource …idea. Bueno, por supuesto, hay muchas cosas malas, pero menos de lo que sigue aquí ...Pero si desea modificar las constantes de código del objeto de función, podemos hacerlo.
Si realmente quiere jugar con objetos de código de verdad, debe usar una biblioteca como
bytecode(cuando esté terminada) obyteplay(hasta entonces, o para versiones anteriores de Python) en lugar de hacerlo manualmente. Incluso para algo tan trivial, elCodeTypeinicializador es un dolor; Si realmente necesita hacer cosas como arreglarlnotab, solo un loco lo haría manualmente.Además, no hace falta decir que no todas las implementaciones de Python usan objetos de código de estilo CPython. Este código funcionará en CPython 3.7, y probablemente todas las versiones vuelvan al menos a 2.2 con algunos cambios menores (y no las cosas de pirateo de códigos, sino cosas como expresiones generadoras), pero no funcionará con ninguna versión de IronPython.
¿Qué podría salir mal al hackear objetos de código? La mayoría de las veces son solo segfaults,
RuntimeErrors que se comen toda la pila,RuntimeErrors más normales que se pueden manejar o valores de basura que probablemente solo aumentarán unaTypeErroroAttributeErrorcuando intentes usarlos. Por ejemplo, intente crear un objeto de código con solo unRETURN_VALUEsin nada en la pila (bytecodeb'S\0'para 3.6+,b'S'antes), o con una tupla vacía paraco_constscuando hay unLOAD_CONST 0en el bytecode, o convarnamesdecrementado por 1 para que el más altoLOAD_FASTrealmente cargue un freevar / cellvar cell. Para divertirse realmente, si selnotabequivoca lo suficiente, su código solo será predeterminado cuando se ejecute en el depurador.Usar
bytecodeobyteplayno lo protegerá de todos esos problemas, pero tienen algunas comprobaciones básicas de cordura y buenos ayudantes que le permiten hacer cosas como insertar un fragmento de código y dejar que se preocupe por actualizar todas las compensaciones y etiquetas para que pueda ' No te equivoques, y así sucesivamente. (Además, evitan que tengas que escribir ese ridículo constructor de 6 líneas y tener que depurar los errores tontos que surgen al hacerlo).Ahora al # 2.
Mencioné que los objetos de código son inmutables. Y, por supuesto, los concursos son una tupla, por lo que no podemos cambiar eso directamente. Y la cosa en la tupla constante es una cadena, que tampoco podemos cambiar directamente. Es por eso que tuve que construir una nueva cadena para construir una nueva tupla para construir un nuevo objeto de código.
Pero, ¿y si pudieras cambiar una cadena directamente?
Bueno, lo suficientemente profundo debajo de las cubiertas, todo es solo un puntero a algunos datos C, ¿verdad? Si está utilizando CPython, hay una API C para acceder a los objetos , y puede usarla
ctypespara acceder a esa API desde Python, lo cual es una idea tan terrible que ponen unpythonapiderecho allí en elctypesmódulo de stdlib . :) El truco más importante que necesita saber es queid(x)es el puntero realxen la memoria (como unint).Desafortunadamente, la API de C para cadenas no nos permitirá acceder de forma segura al almacenamiento interno de una cadena ya congelada. Así que atornille con seguridad, solo leamos los archivos de encabezado y encontremos ese almacenamiento nosotros mismos.
Si está utilizando CPython 3.4 - 3.7 (es diferente para versiones anteriores, y quién sabe para el futuro), un literal de cadena de un módulo que está hecho de ASCII puro se almacenará utilizando el formato ASCII compacto, lo que significa la estructura termina temprano y el búfer de bytes ASCII sigue inmediatamente en la memoria. Esto se interrumpirá (como probablemente en segfault) si coloca un carácter no ASCII en la cadena, o ciertos tipos de cadenas no literales, pero puede leer sobre las otras 4 formas de acceder al búfer para diferentes tipos de cadenas.
Para hacer las cosas un poco más fáciles, estoy usando el
superhackyinternalsproyecto en mi GitHub. (Intencionalmente no es pip-installable porque realmente no deberías usar esto excepto para experimentar con tu versión local del intérprete y similares).Si quieres jugar con estas cosas,
intes mucho más simple bajo las sábanas questr. Y es mucho más fácil adivinar qué puedes romper cambiando el valor de2a1, ¿verdad? En realidad, olvida imaginarte, hagámoslo (usando los tipos desuperhackyinternalsnuevo):... finja que el cuadro de código tiene una barra de desplazamiento de longitud infinita.
Intenté lo mismo en IPython, y la primera vez que intenté evaluar
2en el indicador, entró en una especie de bucle infinito ininterrumpido. Presumiblemente está usando el número2para algo en su ciclo REPL, mientras que el intérprete de valores no lo está.fuente
PyUnicodeObject, por otro lado, eso es probablemente solo Python en el sentido de que un intérprete de Python lo ejecutará ...NameError: name 'arg' is not defined. ¿Quiso decirargs = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]:? Una forma podría decirse que mejor escribir esto sería:args = [str(arg).replace('cat', 'dog') for arg in args]. Otro, aún más corto, opción:args = map(lambda a: str(a).replace('cat', 'dog'), args). Esto tiene el beneficio adicional de queargses perezoso (lo que también podría lograrse reemplazando la comprensión de la lista anterior con un generador,*argsfunciona de cualquier manera).PyUnicodeObjectdefinición de estructura, pero al copiar eso en la respuesta, creo que solo me interpongo, y creo que el léame y / o los comentarios de la fuente parasuperhackyinternalsexplicar realmente cómo acceder al búfer (al menos lo suficientemente bien como para recordarme la próxima vez que me importe; no estoy seguro de si será suficiente para alguien más ...), que no quería entrar aquí. La parte relevante es cómo pasar de un objeto Python en vivo a suPyObject *víactypes. (Y tal vez simulando aritmética de puntero, evitandochar_pconversiones automáticas , etc.)printa un nombre. También puede enlazar el nombreprintpara ellos:import yourmodule; yourmodule.print = badprint.Parche de mono
printprintes una función integrada, por lo que utilizará laprintfunción definida en elbuiltinsmódulo (o__builtin__en Python 2). Por lo tanto, cada vez que desee modificar o cambiar el comportamiento de una función integrada, simplemente puede reasignar el nombre en ese módulo.Este proceso se llama
monkey-patching.Después de eso, todas las
printllamadas se realizaráncustom_print, incluso siprintestán en un módulo externo.Sin embargo, realmente no desea imprimir texto adicional, desea cambiar el texto que se imprime. Una forma de hacerlo es reemplazarlo en la cadena que se imprimiría:
Y de hecho si corres:
O si escribe eso en un archivo:
test_file.py
e importarlo:
Por lo tanto, realmente funciona según lo previsto.
Sin embargo, en caso de que solo desee temporalmente imprimir un parche de mono, puede envolver esto en un administrador de contexto:
Entonces, cuando ejecuta eso, depende del contexto lo que se imprime:
Así es como puedes "hackear"
printparches de mono.Modificar el objetivo en lugar del
printSi mira la firma de
print, notará unfileargumento que essys.stdoutpor defecto. Tenga en cuenta que este es un argumento predeterminado dinámico ( realmente se vesys.stdoutcada vez que llamaprint) y no como los argumentos predeterminados normales en Python. Entonces, si cambiasys.stdoutprint, en realidad se imprimirá en el destino diferente, incluso más conveniente que Python también proporciona unaredirect_stdoutfunción (desde Python 3.4 en adelante, pero es fácil crear una función equivalente para versiones anteriores de Python).La desventaja es que no funcionará para las
printdeclaraciones que no se imprimensys.stdouty que crear las suyasstdoutno es realmente sencillo.Sin embargo, esto también funciona:
Resumen
@Abarnet ya ha mencionado algunos de estos puntos, pero quería explorar estas opciones con más detalle. Especialmente cómo modificarlo a través de módulos (usando
builtins/__builtin__) y cómo hacer que ese cambio solo sea temporal (usando contextmanagers).fuente
redirect_stdout, por lo que es bueno tener una respuesta clara que conduzca a eso.Una forma sencilla de capturar toda la salida de una
printfunción y luego procesarla es cambiar la secuencia de salida a otra cosa, por ejemplo, un archivo.Usaré una
PHPconvención de nomenclatura ( ob_start , ob_get_contents , ...)Uso:
Imprimir
fuente
¡Combinemos esto con la introspección de cuadros!
Encontrarás este truco antes de cada saludo con la función o método de llamada. Esto puede ser muy útil para iniciar sesión o depurar; especialmente porque le permite "secuestrar" declaraciones impresas en código de terceros.
fuente