Search code examples
pythonunicodejupyter-notebookpretty-print

Is it possible to print characters on top of each other without erasing the previous one in order to have both superscript and subscript?


I am wondering if I can have print() outputs such as

                                    

in a terminal and/or an IPython/Jupyter Notebook. I want to develop a library working with toleranced dimensions and these types of pretty-printed outputs will come quite handy during development and testing.

What I know so far:

  • There are escape characters such as Carriage Return \r that goes to the beginning of the line without erasing the existing characters and the Backspace \b that deletes the last character. For example print("some text\bsome other text \rbingo", end="") should give me bingotexsome other text. Anyway, when printing a new character the previous one is erased.
  • I also know how to use Unicode characters to have superscripted/subscripted digits and plus/minus signs. For example, the print('1.23\u207a\u2074\u2027\u2075\u2076') will give me something like 1.23+4.56 and print('1.23\u208b\u2087.\u2088\u2089') outputs close to 1.23-7.89. Although what unicode characters should be used for superscript/subscript decimal delimiters (in this case period/dot/point) is still debatable. There are multiple options for superscipted dot including also \u0387 and \u22c5. However, AFIK there are no unicode characters suitable for subscripted dot. (more info here)

what I don't know

  • if there is an escape character or Unicode one that replicates the left arrow key on the keyboard?
  • how to print without erasing the pixels in the terminal? Is there a way to print/display characters on top of each other?
  • and if none of the above is possible in a terminal, if/how I can control the HTTP/CSS outputs in a Jupyter Notebook to print both superscript and subscript at the same time?

Solution

  • In Jupyter Notebook/Lab this should work:

    from IPython.display import Math
    
    Math(r"1.23^{+4.56}_{-7.89}")
    

    For convenience, you can package it in a class:

    from IPython.display import Math
    
    class PPrint:
        def __init__(self, base, sub, sup):
        self.base = base
        self.sub = sub
        self.sup = sup
        
    
        def  _ipython_display_(self):
            display(Math(f"{{{self.base}}}^{{{self.sub}}}_{{{self.sup}}}"))
    

    Then you can create an instance e.g.:

    x = PPrint("1.23", "+4.56", "-7.89")
    

    and if you execute in a notebook either x or display(x), it should appear as in your example.