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 print
se 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
code
objeto dentro de la función con uno con unaco_consts
lista 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, exec
es 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?
42
a23
que 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
print
imprime, ¿verdad?O, de manera similar, puedes usar monkeypatch en
sys.stdout
lugar 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, elCodeType
inicializador 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,
RuntimeError
s que se comen toda la pila,RuntimeError
s más normales que se pueden manejar o valores de basura que probablemente solo aumentarán unaTypeError
oAttributeError
cuando intentes usarlos. Por ejemplo, intente crear un objeto de código con solo unRETURN_VALUE
sin nada en la pila (bytecodeb'S\0'
para 3.6+,b'S'
antes), o con una tupla vacía paraco_consts
cuando hay unLOAD_CONST 0
en el bytecode, o convarnames
decrementado por 1 para que el más altoLOAD_FAST
realmente cargue un freevar / cellvar cell. Para divertirse realmente, si selnotab
equivoca lo suficiente, su código solo será predeterminado cuando se ejecute en el depurador.Usar
bytecode
obyteplay
no 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
ctypes
para acceder a esa API desde Python, lo cual es una idea tan terrible que ponen unpythonapi
derecho allí en elctypes
módulo de stdlib . :) El truco más importante que necesita saber es queid(x)
es el puntero realx
en 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
superhackyinternals
proyecto 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,
int
es mucho más simple bajo las sábanas questr
. Y es mucho más fácil adivinar qué puedes romper cambiando el valor de2
a1
, ¿verdad? En realidad, olvida imaginarte, hagámoslo (usando los tipos desuperhackyinternals
nuevo):... 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
2
en el indicador, entró en una especie de bucle infinito ininterrumpido. Presumiblemente está usando el número2
para 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 queargs
es perezoso (lo que también podría lograrse reemplazando la comprensión de la lista anterior con un generador,*args
funciona de cualquier manera).PyUnicodeObject
definició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 parasuperhackyinternals
explicar 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_p
conversiones automáticas , etc.)print
a un nombre. También puede enlazar el nombreprint
para ellos:import yourmodule; yourmodule.print = badprint
.Parche de mono
print
print
es una función integrada, por lo que utilizará laprint
función definida en elbuiltins
mó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
print
llamadas se realizaráncustom_print
, incluso siprint
está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"
print
parches de mono.Modificar el objetivo en lugar del
print
Si mira la firma de
print
, notará unfile
argumento que essys.stdout
por defecto. Tenga en cuenta que este es un argumento predeterminado dinámico ( realmente se vesys.stdout
cada vez que llamaprint
) y no como los argumentos predeterminados normales en Python. Entonces, si cambiasys.stdout
print
, en realidad se imprimirá en el destino diferente, incluso más conveniente que Python también proporciona unaredirect_stdout
funció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
print
declaraciones que no se imprimensys.stdout
y que crear las suyasstdout
no 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
print
función y luego procesarla es cambiar la secuencia de salida a otra cosa, por ejemplo, un archivo.Usaré una
PHP
convenció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