29

I recall reading, in the Python 3.5 docs, how to change the >>> on the Python interactive prompt, such as how calling help() will change it to help>.

But for some reason, when I've gone back to try and remember, I just can't find the instructions to it. Does anyone know if this is possible?

bad_coder
  • 11,289
  • 20
  • 44
  • 72
MutantOctopus
  • 3,431
  • 4
  • 22
  • 31

3 Answers3

31

You remember correctly.

It's in the sys module (sys.ps1 & sys.ps2):

Strings specifying the primary and secondary prompt of the interpreter. These are only defined if the interpreter is in interactive mode. Their initial values in this case are '>>> ' and '... '. If a non-string object is assigned to either variable, its str() is re-evaluated each time the interpreter prepares to read a new interactive command; this can be used to implement a dynamic prompt.

For example:

   >>> import sys
   >>> sys.ps1 = "3.5>>> "
   3.5>>> sys.ps2 = "3.5... "
   3.5>>>
Bob Stein
  • 16,271
  • 10
  • 88
  • 101
Gerrat
  • 28,863
  • 9
  • 73
  • 101
11

It's great to set it to either:

  1. a color for better visual aspect
  2. a blank or space for easier copy/paste operations

Paste this into your Bash shell:

tee ~/.pyrc <<EOF
#!/usr/bin/env python3
import sys

# You also need \x01 and \x02 to separate escape sequence, due to:
# https://stackoverflow.com/a/9468954/1147688
sys.ps1='\x01\x1b[1;49;33m\x02>>>\x01\x1b[0m\x02 '  # bright yellow
sys.ps2='\x01\x1b[1;49;31m\x02...\x01\x1b[0m\x02 '  # bright red
EOF

Finally add this line to your ~/.bash_profile:

export PYTHONSTARTUP=~/.pyrc


If you're on Windows, use one of these:

# Set it Temperarily (for this session)
$env:PYTHONSTARTUP="C:\Users\<USERNAME>\.pyrc"                                                  

# Set it Locally:  HKEY_CURRENT_USER
[Environment]::SetEnvironmentVariable("PYTHONSTARTUP", 'C:\Users\<USERNAME>\.pyrc', 'User')     

# Set it Globaly: HKEY_LOCAL_MACHINE
[Environment]::SetEnvironmentVariable("PYTHONSTARTUP", 'C:\Users\<USERNAME>\.pyrc', 'Machine')  

# Set it Globaly: HKEY_LOCAL_MACHINE (also in CMD)
setx /m PYTHONSTARTUP "%HOME%\.pyrc"                                                        

Enjoy!

enter image description here

not2qubit
  • 14,531
  • 8
  • 95
  • 135
  • Sadly this does not work on windows (using the `colorama` package). Also note that making `~/.pyrc` executable is not necessary, as you will not be executing it directly from the terminal. – kyrill Apr 29 '19 at 22:09
  • 1
    YMMV because Windows is horrible in supporting ANSI color codes. So it depends on very specific things about the console, your powershell version, your Windows version and which python version (Native Windows, Cygwin, WSL). That said, *colorama* never worked as expected across different Python versions, for me. So just try your own ANSI codes. The ones above are for `TRUECOLOR` supported terminals, AFAICR. Also try using *ConEmu*. – not2qubit Apr 29 '19 at 22:45
  • 1
    Colorama actually works fine for me on windows when I use `print` or otherwise write to stdout from my code. The problem is just with `sys.ps1` ‒ for some reason it prints the escape characters to the terminal even if I replace both `sys.stdin` and `sys.__stdin__` with the `AnsiToWin32` wrapper. I think the stream to which the CPython interactive interpreter writes `ps1` is somehow hard-coded or saved at startup-time and disregards later changes of `sys.stdout`. – kyrill Apr 30 '19 at 08:59
  • 1
    Due to escape sequence issues under windows, I have updated the answer to include the correct `RL_PROMPT_START_IGNORE` (`\x01`) and `RL_PROMPT_END_IGNORE` (`\x02`) special sequences, used by **readline**, to keep track of string lengths when they contain escape sequences. Deatils of solution was found [here](https://stackoverflow.com/a/9468954/1147688). – not2qubit Dec 29 '21 at 16:57
2

If you're on Windows (cmd.exe) and you want a colored prompt, you can use colorama, but there are some caveats. If you call colorama.init in your PYTHONSTARTUP and assign to sys.ps1 a string containing coloring escape codes, it won't work. However, colored output does work when you call print with a string containing coloring escape codes.

Luckily, the Python people who came up with sys.ps1 were generous (or smart?) enough to let you use any object as ps1, not only strings. The assigned object is then converted to string using its __str__ method. This means you can define your own class, eg. Prompt, and do anything in its __str__ method, including writing to the colorama-wrapped stdout (which will work!). Then you simply return an empty string.

This fact brings you a nice bonus ‒ you can also use a non-constant prompt. Do you want a date in your Python shell like you have in bash? No problem.

import sys
import datetime
import colorama

colorama.init(autoreset=True)

class Prompt:
  def __str__(self):
    print(self.prompt, end='')
    return ''

class PS1(Prompt):

  @property
  def prompt(self):
    return '{brace_c}[{time_c}{time}{brace_c}]{prompt_c}>>> '.format(
              brace_c  = colorama.Fore.BLACK + colorama.Style.BRIGHT,
              # style is preserved, so the following are also bright:
              prompt_c = colorama.Fore.LIGHTYELLOW_EX,
              time_c   = colorama.Fore.BLACK,
              time     = datetime.datetime.now().strftime('%H:%M'),
            )

sys.ps1 = PS1()

Although this works just fine, it is a rather hacky way as the intended purpose of the __str__ method is to return a str instance. Because of this, it breaks when you do the same thing with sys.ps2. The Python interpreter expects the __str__ method to have no side effects, and apparently evaluates both str(sys.ps1) and str(sys.ps2) when printing PS1, whereas when printing PS2 the already-evaluated (and saved) value of str(sys.ps2) is used. The result is that, if you create a PS2 class similar to the PS1 above, you will see both PS1 and PS2 when you should only see PS1, and you will see nothing when you should see PS2. Another case in which this does not work well is when multiple threads / processes are writing to the console. Then the output from several threads is interleaved, and while this can happen with normal PS1 as well, this hack makes it even worse.

EDIT: Screenshot

screenshot

In this simple example it's not so bad (no interleaving, only messed up colors), but if you get the timing "right", it can be worse.

kyrill
  • 986
  • 10
  • 27
  • Nice one! But please add a screenshot so we can better understand how it looks. It's possible that the problem you have with *interleave*, is a buffering problem. Python interpreters love to buffer the output, so you should make sure that anything that prints, do so unbuffered. – not2qubit May 02 '19 at 20:44
  • It looks from the picture that the color you activated was not disabled after its use. Make sure you also provide the ANSI sequence to disable the color after having selected and printed it. – not2qubit May 07 '19 at 05:05
  • I'm not sure what you mean. I use `colorama.init(autoreset=True)`, so there's no need to disable color. As you can see in the screenshot, the text I write in the terminal (eg. `from threading import Thread`) is white, which is my normal text color. Also the text `test` written by the started thread is white. The problem is that the part of the prompt before the closing bracket, which is _not_ supposed to be white, is white. So the problem is in fact that the color wasn't _enabled_, not disabled. – kyrill May 11 '19 at 19:40
  • What I'm saying is that you probably need to re-set the *style* back to either `NORMAL` or `RESET_ALL`. And since you're using threads, you may need to define these (e.g. `brace_c` etc.) items earlier and elsewhere. Why are you even defining them when you're not using them again? – not2qubit May 12 '19 at 17:21
  • Also, please add description of how to implement the above script. Where do you put it, how is it executed? – not2qubit May 12 '19 at 17:23
  • OR you may need to issue the `RESET_ALL` first, before the prompt is created, since you have no idea how non-ascii characters are handled in the thread functions. – not2qubit May 12 '19 at 17:36