Indian numbering system formatting The following code converts a number into Indian numbering system format.

I know locale can also help in getting similar format but I wanted something that can work on any system because not all locales are present in all OSs.

The code was written for Python 3.5 hence no variable annotations.

from decimal import Decimal
from itertools import islice, zip_longest
from typing import Any, Iterable, Iterator, Sequence, Tuple, TypeVar, Union  # noqa: ignore=F401


_T = TypeVar('_T')


def grouper(iterable: Iterable[_T], n: int, fillvalue: Any = None) -&gt; Iterator[Tuple[_T, ...]]:
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --&gt; ABC DEF Gxx
    args = [iter(iterable)] * n
    return zip_longest(fillvalue=fillvalue, *args)


def indian_numbering_format(
    amount: Union[Decimal, int, str], places: int = 2,
    curr: str = '', pos: str = '', neg: str = '-'
) -&gt; str:
    """
    Formats a number and returns it in Indian numbering format
    https://en.wikipedia.org/wiki/Indian_numbering_system

    places:  optional number of places after the decimal point
    curr:    optional currency symbol before the sign
    pos:     optional sign for positive numbers: '+' or blank
    neg:     optional sign for negative numbers: '-' or blank

    Note that if the fractional part only contains 0 it will be
    dropped.
    """
    q = Decimal(10) ** -places  # type: Decimal
    amount = Decimal(amount).quantize(q)
    sign, digits, exponent = amount.as_tuple()
    integer_part, fractional_part = (), ()  # type: Sequence[int], Sequence[int]
    exponent = exponent or len(digits)
    integer_part = digits[:exponent]
    # If fractional part only contains 0s then ignore it.
    if set(digits[exponent:]) != {0}:
        fractional_part = digits[exponent:]
    integer_part_digits = reversed(''.join(map(str, integer_part)))
    thousand = ''.join(islice(integer_part_digits, 3))[::-1]
    hundreds = ','.join(map(''.join, grouper(integer_part_digits, 2, fillvalue='')))[::-1]
    sep = ',' if hundreds else ''
    fractional_part_digits = '.' + ''.join([str(d) for d in fractional_part]) if fractional_part else ''
    return '{sign}{curr}{hundreds}{sep}{thousand}{fractional_part}'.format(
        sign=neg if sign else pos,
        curr=curr,
        thousand=thousand,
        sep=sep,
        hundreds=hundreds,
        fractional_part=fractional_part_digits
    )


I was wondering if the code can be refactored in any way to make it much more simpler.
I don't think I'd get so fancy with the grouper; the data is small, a constant table will do:

comma_after_digit = set(3, 5, 7, 9, 11, 13, 15, 17)

result = '.' + ''.join(map(str, fractional_part))
for i, d in enumerate(integer_part_digits):
    if i in comma_after_digit:
        result = ',' + result
    result = d + result
result = {0:pos, 1:neg}[sign] + result
return result


Also:


Try and be a bit more consistent: you use reversed() to reverse a string on one line, and then the next you use [::-1].  You also use map(str, digits) in one place and [str(x) for x in digits] another! They do the same thing, but it's nice to the reader to pick one idiom and stick with it.
thousand = ''.join(islice(integer_part_digits, 3))[::-1] can be simplified into thousand = integer_part_digits[-4::-1]
Using .format() is overkill for concatenating strings.  + works just fine.  If you want to use it, you should 1) factor out constant strings (like the decimal point) and 2) use the args as your temporary variables, like:

return '{sign}{curr}{hundreds}{sep}{thousand}.{fractional_part}'.format(
    sign=neg if sign else pos,
    curr=curr,
    thousand=integer_part_digits[-4::-1],
    sep=',' if exponent &gt; 3 else '',
    hundreds=','.join(map(''.join, grouper(integer_part_digits, 2, fillvalue='')))[::-1],
    fractional_part=''.join(map(str,fractional_part)) if fractional_part else ''
)

According to the wikipedia page you cite, there's no comma after the 1 in 10**19, so your code is wrong :) ... which leads inevitably to:
Needs tests!