Cuando entramos en la consola interactiva de Python (escribiendo python
en una shell (bash, cmd,…)) vemos que cada línea en la que podemos escribir código empieza por >>>
. Luego hay líneas de continuación de código, por ejemplo cuando escribimos un for
, que empiezan por ...
. Un ejemplo de este comportamiento sería algo como lo siguiente (lo que comento, el prompt, lo he resaltado en verde):
>>> for i in range(3):
... print(i)
...
0
1
2
>>>
Este comportamiento se puede modificar. En el módulo sys
tenemos unas cadenas que definen este comportamiento y están almacenadas en sys.ps1
para la entrada (>>>
) y sys.ps2
para la continuación (...
). Vamos a modificarlo, inicia la consola interactiva y escribe:
1 2 3 4 |
import sys sys.ps1 = "Escribe >> " sys.ps2 = "Continúa ... " |
Después de haber escrito lo anterior, si ahora replico el código del ejemplo anterior con el bucle for
se debería de ver así:
Escribe >> for i in range(3):
Continúa ... print(i)
Continúa ...
0
1
2
Escribe >>
Lo anterior es un poco horrible porque los distintos prompts no mantienen el mismo ancho y el código es más complicado de leer pero, recordad, esto es solo un ejemplo.
Podemos hacerlo un poco más interesante añadiendo el número de la entrada, similar a lo que hace IPython con su In [32]:
. Leyendo la documentación nos indica que si queremos que la cadena se actualice podemos usar un objeto y llamará a str()
cada vez que lo evalúe. Podemos crear una clase con un método mágico __str__
. Para obtener el número de la entrada voy a usar la función get_current_history_length
contenida en el módulo readline
:
1 2 3 4 5 6 7 8 |
import sys import readline class PS1: def __str__(self): return f"In [{readline.get_current_history_length()}]: " sys.ps1 = PS1() |
El resultado debería ser algo parecido a lo siguiente (los prompts originales en verde y el nuevo para la entrada en azul):
>>> import sys
>>> import readline
>>>
>>> class PS1:
... def __str__(self):
... return f"In [{readline.get_current_history_length()}]: "
...
>>> sys.ps1 = PS1()
In [83]:
El número empieza por 83 porque dependerá lo que tengáis en vuestra historia. Si queréis que empiece por 0 podéis limpiar la historia usando la función clear_history
, dentro del módulo readline
, antes de cambiar el prompt. Como no quería que se os borrase la historia lo he dejado tal cual.
¿Y en IPython?
En IPython el mecanismo es más complejo pero también más potente. Hemos de usar la clase IPython.terminal.prompts.Prompts que define el comportamiento de los prompts. En este caso, a diferencia de la consola interactiva normal, que muestra dos opciones, entrada (>>>
) y continuación (...
), tenemos cuatro posibilidades definidas mediante los métodos de la clase Prompts
.
A partir de este punto voy a considerar que estás usando una consola de IPython, que es donde tendrán efecto los cambios:
1 2 3 |
from IPython. terminal.prompts import Prompts print(dir(Prompts)) |
Cuyo resultado nos mostrará:
1 2 3 4 5 6 7 8 9 |
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_width', 'continuation_prompt_tokens', 'in_prompt_tokens', 'out_prompt_tokens', 'rewrite_prompt_tokens', 'vi_mode'] |
Podemos ver que hay 4 métodos llamados continuation_prompt_tokens
, in_prompt_tokens
, out_prompt_tokens
, rewrite_prompt_tokens
que definen el prompt en IPython:
in_prompt_tokens
: Este es el prompt tipico que vemos en las entradas de la consola de IPython (In [1]:
), similar asys.ps1
.continuation_prompt_tokens
: Este es el prompt típico de continuación, (...:
), similar asys.ps2
.out_prompt_tokens
: Este es el que muestra IPython para el resultado de la salida (Out[1]:
).rewrite_prompt_tokens
: Esta, después de buscar, no tengo ni idea de para qué sirve. La documentación dice The rewrite prompt is shown to highlight how special syntax has been interpreted (default like —–>). Si alguien me lo aclara se lo agradezco.
Voy a modificar la de la entrada y la de la salida para que haga cosas interesantes. Primero pongo un ejemplo y lo comento después:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from os import getcwd from platform import machine, platform import datetime as dt from IPython.terminal.prompts import Prompts, Token ip = get_ipython() class MisPrompts(Prompts): def in_prompt_tokens(self, cli=None): return [ (Token.Prompt, getcwd()), (Token.Prompt, " In ["), (Token.PromptNum, str(self.shell.execution_count)), (Token.Prompt, "]: ") ] def out_prompt_tokens(self): return [ ( Token.OutPrompt, (f"Ejecutado con {machine()}, " f"usando {platform()}, " f"el {dt.datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S')}" "\n") ), (Token.OutPrompt, "Out ["), (Token.OutPromptNum, str(self.shell.execution_count)), (Token.OutPrompt, "]: ") ] |
Defino ambos métodos, el del prompt de entrada y el de salida en la clase MisPrompts
que hereda de Prompts
. Dentro de cada método le pido que haga una serie de cosas. En el de entrada le digo que me muestre la ruta en la que me encuentro, antes del número de celda de entrada. Para definir cada cosa que se debe mostrar en el prompt uso tipos que provienen de la biblioteca pygments
y que usa IPython internamente. IPython define unos cuantos de esos tokens como Prompt
, PromptNum
, OutPrompt
y OutPromptNum
y permiten darle estilo a los valores definidos por cada uno de ellos. Por ejemplo, en el método out_prompt_tokens
podrías cambiar Token.OutPrompt
por Token.Prompt
y verías como cambia el color. Cuando instancio la clase MisPrompts
le paso una instancia de la shell de IPython. En el código anterior, sería ip
. Internamente se usa, entre otras cosas, para obtener el número de la celda que se está ejecutando, e.g., self.shell.execution_count
.
Bien, con todo lo anterior. Si ahora hacemos lo siguiente:
1 |
ip.prompts = MisPrompts(ip) |
Empezaremos a ver los prompts de entrada y de salida de una forma parecida a lo siguiente:
- En Windows:
1 2 3 4 5 6 7 |
C:\Users\pybonacci\Desktop In [53]: a = 1 C:\Users\pybonacci\Desktop In [54]: a Ejecutado con AMD64, usando Windows-10-10.3.12345-SP7, el 2020/05/26 11:21:33 Out [54]: 1 C:\Users\pybonacci\Desktop In [55]: |
- En linux:
1 2 3 4 5 6 7 |
/home/pybonacci In [3]: a = 1 /home/pybonacci In [4]: a Ejecutado con x86_64, usando Linux-4.19.0-5-amd64-x86_64-with-debian-9.12, el 2020/05/26 19:45:34 Out [4]: 1 /home/kiko In [5]: |
Estas cosas las podéis meter en los ficheros de arranque que usan la consola interactiva de Python o de IPython para que se comporte siempre de la forma en que lo hayáis configurado.
Espero que os haya resultado útil.