Number to words in Python 3
Bug

I generally like your logic.  You have a bug, though:


&gt;&gt;&gt; number_to_words(1_000_001)
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
  File "cr175527.py", line 94, in number_to_words
    chunk_words.append(chunk_to_words(chunk, position))
  File "cr175527.py", line 81, in chunk_to_words
    return three_digit_to_words(hundreds, tens, ones) + multiplier
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'



The problem is that for middle chunk of three digits, three_digit_to_words(0, 0, 0) evaluates to None, which isn't properly handled.

Comments and style

I find it annoying that your comments often appear after the corresponding code.  It is more conventional to either put the comment before the code, or at the end of the same line.

The most important kind of comment is the docstring, and you didn't write any.  (Your comment on to_base_1000() was almost like a docstring.)  Fortunately, your function names were clear enough to indicate their purpose.  Still, one feature of docstrings that you are missing out on is the ability to embed doctests, which would be extremely helpful for this kind of code.  For each function, a simple example, a complex example, and some corner cases would clarify exactly how it behaves.

I would change your brace style for the definitions of ONES, TEENS, etc. so that they all have the same indentation.  Also, if you write ONES = [None, 'one', …], then you should also be consistent and write TENS_PREFIXES = [None, None, 'twen', …].

Logic and organization

I am slightly annoyed by your 


TENS_PREFIXES = ['_', '_', 'twen', 'thir', 'for',
                 'fif', 'six', 'seven', 'eigh', 'nine']

# Tens places words without "-ty". ex. twenty &gt; twen, thirty &gt; thir



There is no situation in which you would ever output "twen" rather than "twenty", so why skimp on those letters, when you could have written 'twenty' and avoided the need for that apologetic comment?

If you use "chunk" as your terminology for a block of three digits, consider using that name instead of to_base_1000().  Then, your numbers_to_words() function would be a composition of chunk_to_words() and number_to_chunks(), which I find pleasing.

In my opinion, you would be better off rolling the helper functions to_hundreds_tens_ones(), two_digit_to_words(), and three_digit_to_words() into chunk_to_words().  The code would be more readable with less jumping around.

A lot of the conditions exist to ensure that exactly one space appears between words.  For example:


if last_two_digits is None:
    return '{} hundred'.format(ONES[hundreds])

else:
    return '{} hundred {}'.format(ONES[hundreds], last_two_digits)



I would prefer to yield each word as a generator, then let ' '.join(…) take care of inserting the spaces.

Suggested solution

ONES = [
    None, 'one', 'two', 'three', 'four',
    'five', 'six', 'seven', 'eight', 'nine'
]

TEENS = [
    'ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
    'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'
]

TENS = [
    None, None, 'twenty', 'thirty', 'forty',
    'fifty', 'sixty', 'seventy', 'eighty', 'ninety'
]

POWERS_OF_1000 = [
    None, 'thousand', 'million', 'billion', 'trillion',
    'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion'
]


def number_to_chunks(number):
    """
    Split an integer into little-endian base-1000 chunks.

    &gt;&gt;&gt; list(number_to_chunks(1234))
    [234, 1]
    &gt;&gt;&gt; list(number_to_chunks(31415000))
    [0, 415, 31]
    &gt;&gt;&gt; list(number_to_chunks(0))
    []
    """
    while number:
        number, n = divmod(number, 1000)
        yield n

def chunk_to_words(chunk, scale):
    """
    Generate English words given a chunk (an integer less than 1000) and its
    scale (power of 1000).

    &gt;&gt;&gt; list(chunk_to_words(31, 1))
    ['thirty', 'one', 'thousand']
    &gt;&gt;&gt; list(chunk_to_words(50, 2))
    ['fifty', 'million']
    &gt;&gt;&gt; list(chunk_to_words(0, 0))
    []
    &gt;&gt;&gt; list(chunk_to_words(12, 3))
    ['twelve', 'billion']
    """
    assert 0 &lt;= chunk &lt; 1000
    hundreds, tens, ones = chunk // 100, chunk // 10 % 10, chunk % 10

    if hundreds:
        yield ONES[hundreds]
        yield 'hundred'

    if tens == 1:
        yield TEENS[ones]
    else:
        if tens:
            yield TENS[tens]
        if ones:
            yield ONES[ones]

    if chunk and scale:
        yield POWERS_OF_1000[scale]

def number_to_words(number):
    """
    Convert a nonnegative integer less than 10**30 to an English phrase.

    &gt;&gt;&gt; number_to_words(103)
    'one hundred three'
    &gt;&gt;&gt; number_to_words(0)
    'zero'
    &gt;&gt;&gt; number_to_words(1_000_001)
    'one million one'
    &gt;&gt;&gt; number_to_words(31_415_926_535)
    'thirty one billion four hundred fifteen million nine hundred twenty six thousand five hundred thirty five'
    """
    chunks = [
        ' '.join(chunk_to_words(chunk, i))
        for i, chunk in enumerate(number_to_chunks(number))
    ]
    return ' '.join(c for c in chunks[::-1] if c) or 'zero'