4

I am using ipython 5.8.0 on Debian 10.

This is how output looks like:

In [1]: 50*50
Out[1]: 2500

Is it possible to configure ipython to print all numbers with thousands separators? ie:

In [1]: 50*50
Out[1]: 2'500

In [2]: 5000*5000
Out[2]: 25'000'000

And perhaps, is it possible to make ipython also understand thousands separators on input?

In [1]: 5'000*5'000
Out[1]: 25'000'000

UPDATE

The accepted answer from @Chayim Friedman works for integers, but does not work for float:

In [1]: 500.1*500
Out[1]: 250050.0

Also, when it works, it uses , as the character for thousand separator:

In [1]: 500*500
Out[1]: 250,000

Can I use ' instead?

400 the Cat
  • 266
  • 3
  • 23

4 Answers4

9

Using ' as thousands separator in input is quite problematic because Python uses ' to delimit strings, but you can use _ (PEP 515, Underscores in Numeric Literals):

IPython input thousand separators

Regarding output, this is slightly harder, but can be done using IPython extensions.

Put the following Python code in a new file at ~/.ipython/extensions/thousands_separator.py:

default_int_printer = None

def print_int(number, printer, cycle):
    printer.text(f'{number:,}') # You can use `'{:,}'.format(number)` if you're using a Python version older than 3.6

def load_ipython_extension(ipython):
    global default_int_printer

    default_int_printer = ipython.display_formatter.formatters['text/plain'].for_type(int, print_int)

def unload_ipython_extension(ipython):
    ipython.display_formatter.formatters['text/plain'].for_type(int, default_int_printer)

This code tells IPython to replace the default int formatter with one that prints thousand separators when this extension is loaded, and restore the original when it is unloaded.

Edit: If you want a different separator, for instance ', replace the f'{number:,}' with f'{number:,}'.replace(',', "'").

You can load the extension using the magic command %load_ext thousands_separator and unload it using %unload_ext thousands_separator, but if you want it always, you can place it in the default profile.

Run the following code in the terminal:

ipython3 profile create

It will report that a file ~/.ipython/profile_default/ipython_config.py was created. Enter it, and search for the following string:

## A list of dotted module names of IPython extensions to load.
#c.InteractiveShellApp.extensions = []

Replace it with the following:

# A list of dotted module names of IPython extensions to load.
c.InteractiveShellApp.extensions = [
    'thousands_separator'
]

This tells IPython to load this extension by default.

Done!

enter image description here

Edit: I saw that you want to a) use ' as separator, and b) do the same for floats:

Using different separator is quite easy: just str.replace():

def print_int(number, printer, cycle):
    printer.text(f'{number:,}'.replace(',', "'"))

Doing the same for floats is also easy: just setup print_int so it prints floats to. I also suggest to change the name to print_number.

Final code:

default_int_printer = None
default_float_printer = None

def print_number(number, printer, cycle):
    printer.text(f'{number:,}'.replace(',', "'"))

def load_ipython_extension(ipython):
    global default_int_printer
    global default_float_printer

    default_int_printer = ipython.display_formatter.formatters['text/plain'].for_type(int, print_number)
    default_float_printer = ipython.display_formatter.formatters['text/plain'].for_type(float, print_number)

def unload_ipython_extension(ipython):
    ipython.display_formatter.formatters['text/plain'].for_type(int, default_int_printer)
    ipython.display_formatter.formatters['text/plain'].for_type(float, default_float_printer)
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
0

After update: you can subclass int:

class Int(int):
    def __repr__(self):
        return "{:,}".format(self)
Int(1000)
# 1,000
frab
  • 1,162
  • 1
  • 4
  • 14
0

I don't believe you can achieve all that you are looking for without rewriting the iPython interpreter, which means changing the Python language specification, to be able to input numbers with embedded ' characters and have them ignored. But you can achieve some of it. Subclassing the int class is a good start. But you should also overload the various operators you plan on using. For example:

class Integer(int):
    def __str__(self):
        # if you want ' as the separator:
        return "{:,}".format(self).replace(",", "'")

    def __add__(self, x):
        return Integer(int(self) + x)

    def __mul__(self, x):
        return Integer(int(self) * x)

    """
    define other operations: __sub__, __floordiv__, __mod__, __neg__, etc.
    """


i1 = Integer(2)
i2 = Integer(1000) + 4.5 * i1
print(i2)
print(i1 * (3 + i2))

Prints:

1'009
2'024

Update

It seems that for Python 3.7 you need to override the __str__ method rather than the __repr__ method. This works for Python 3.8 and should work for later releases as well.

Update 2

import locale

#locale.setlocale(locale.LC_ALL, '') # probably not required
print(locale.format_string("%d", 1255000, grouping=True).replace(",", "'"))

Prints:

1'255'000

An alternative if you have package Babel from the PyPi repository:

from babel import Locale
from babel.numbers import format_number

locale = Locale('en', 'US')
locale.number_symbols['group'] = "'"

print(format_number(1255000, locale='en_US'))

Prints:

1'255'000

Or if you prefer to custom-tailor a locale just for this purpose and leave the standard en_US locale unmodified. This also shows how you can parse input values:

from copy import deepcopy
from babel import Locale
from babel.numbers import format_number, parse_number

my_locale = deepcopy(Locale('en', 'US'))
my_locale.number_symbols['group'] = "'"
print(format_number(1255000, locale=my_locale))
print(parse_number("1'125'000", locale=my_locale))

Prints:

1'255'000
1125000
Booboo
  • 38,656
  • 3
  • 37
  • 60
  • it does not work for me. I get numbers without thousands separator, when I copy/paste your example exactly. – 400 the Cat Jan 24 '21 at 02:59
  • I've added an iPython Demo. – Booboo Jan 24 '21 at 03:24
  • please see my `UPDATE 2`. I have made a screeshot. – 400 the Cat Jan 24 '21 at 07:03
  • I figured out the problem for Python 3.7 (your version). See updated answer. – Booboo Jan 24 '21 at 11:29
  • Booboo@ - now it works when I copy/paste your code above. But why does it not work when I simply type `500*500` in ipython? – 400 the Cat Jan 24 '21 at 12:42
  • As I mentioned in my prolog to the answer, this is a fundamental limitation to the Python language in how the type `int` behaves and I don't see a way of getting around that. However, when you enter an expression such as `Integer(7) + 3 + 2 * 7000 + 3`, the resulting type is no longer an `int` but rather the new special type `Integer`, which formats its output specially. But if it is inconvenient to use this class I will update the answer to show you how to use `format` without generating warnings. – Booboo Jan 24 '21 at 13:21
  • Be sure to look at the last example that uses package `Babel` -- it has an example of parsing input values that have `'` separators. – Booboo Jan 24 '21 at 13:51
0

Based on PEP-0378, you can use the following code:

a = 1200
b = 500
c = 10

#res = a
#res = a*b
res = a*b*c

dig = len(str(res))       # to figure out how many digits are required in result
print(format(res, "{},d".format(dig)))

It will produce:

6,000,000
anurag
  • 1,715
  • 1
  • 8
  • 28
  • If you want the behaviour to be implicit, i.e., if you just type in the numbers and the interpretor generates the result with thousand separator, I don't think that is currently possible! – anurag Jan 26 '21 at 12:19