Skip to main content
added 1952 characters in body
Source Link
Reinderien
  • 65.3k
  • 5
  • 69
  • 187
from typing import Sequence, Any, Iterator


def comma_and_insertion(items: Sequence[Any]) -> Iterator[str]:
    ifmatch len(items):
 > 0      case (only,):
        for item in items[:-1] yield str(only)
        case first, second:
            yield from (str(itemfirst)
 , ' and ', str(second))
        yieldcase '*others, 'second_last, last:
            for item in others:
        if len       yield str(itemsitem) 
 > 1:
              yield 'and', '
            yield from (str(items[-1]second_last), ', and ', str(last))


def list_string(items: Sequence[Any]) -> str:
    """Format with Oxford commas and the word "and" """
    return ''.join(comma_and_insertion(items))


def test() -> None:
    assert list_string([]) == ''

    assert list_string(['foo']) == 'foo'

    assert list_string(['foo', 'bar']) == 'foo, and bar'

    assert list_string(
        ['Apple', 'Lobster', 'Panda', 'Vis', 2, 3]
    ) == 'Apple, Lobster, Panda, Vis, 2, and 3'


if __name__ == '__main__':
    test()
              0 GEN_START                0

  5           2 LOAD_GLOBALLOAD_FAST                0 (lenitems)
  
  6           4 LOAD_FASTDUP_TOP
              6 MATCH_SEQUENCE
 0 (items)
            8 POP_JUMP_IF_FALSE   6 CALL_FUNCTION   19 (to 38)
       1
      10 GET_LEN
        8     12 LOAD_CONST               1 (01)
             1014 COMPARE_OP               42 (>==)
             1216 POP_JUMP_IF_FALSE       4219 (to 8438)
             18 UNPACK_SEQUENCE          1
  6          14 LOAD_FAST20 STORE_FAST               1 (only)
             22 POP_TOP

  7          24 LOAD_GLOBAL              0 (itemsstr)
             1626 LOAD_FAST                1 (only)
             28 CALL_FUNCTION            1
             30 YIELD_VALUE
             32 POP_TOP
             34 LOAD_CONST               0 (None)
             1836 RETURN_VALUE

  6     >>   38 POP_TOP

  8          40 DUP_TOP
             42 MATCH_SEQUENCE
             44 POP_JUMP_IF_FALSE       45 (to 90)
             46 GET_LEN
             48 LOAD_CONST               2 (-12)
             2050 BUILD_SLICECOMPARE_OP               2 (==)
             2252 BINARY_SUBSCRPOP_JUMP_IF_FALSE       45 (to 90)
             2454 GET_ITERUNPACK_SEQUENCE          2
        >>   26 FOR_ITER 56 STORE_FAST              10 (to2 48(first)
             2858 STORE_FAST               13 (itemsecond)
             60 POP_TOP

  79          3062 LOAD_GLOBAL              10 (str)
             3264 LOAD_FAST                12 (itemfirst)
             3466 CALL_FUNCTION            1
             3668 YIELD_VALUE
LOAD_CONST             38 POP_TOP 3 (' and ')
             70 LOAD_GLOBAL              0 (str)
  8          40 LOAD_CONST72 LOAD_FAST                3 (',second)
 '            74 CALL_FUNCTION            1
             76 BUILD_TUPLE              3
             78 GET_YIELD_FROM_ITER
             80 LOAD_CONST               0 (None)
             4282 YIELD_VALUEYIELD_FROM
             4484 POP_TOP
             4686 JUMP_ABSOLUTELOAD_CONST           13    0 (None)
             88 RETURN_VALUE

  8     >>   90 POP_TOP

 10          92 MATCH_SEQUENCE
             94 POP_JUMP_IF_FALSE       84 (to 26168)
             96 GET_LEN
 10     >>   48 LOAD_GLOBAL   98 LOAD_CONST           0    2 (len2)
            100 50COMPARE_OP LOAD_FAST              5 (>=)
      0      102 POP_JUMP_IF_FALSE       84 (itemsto 168)
            104 52EXTENDED_ARG CALL_FUNCTION            12
            106 54UNPACK_EX LOAD_CONST             512
            108 STORE_FAST               4 (1others)
            110 56STORE_FAST COMPARE_OP              5 (second_last)
            112 STORE_FAST               6 (last)

 11         114 LOAD_FAST                4 (>others)
            116 58GET_ITER
 POP_JUMP_IF_FALSE       33>>  118 FOR_ITER                10 (to 66140)
            120 STORE_FAST               7 (item)

 1112         122 60LOAD_GLOBAL              0 (str)
            124 LOAD_FAST                7 (item)
            126 CALL_FUNCTION            1
            128 YIELD_VALUE
            130 POP_TOP

 13         132 LOAD_CONST               54 ('and', ')
             62134 YIELD_VALUE
             64136 POP_TOP
            138 JUMP_ABSOLUTE           59 (to 118)

 1214     >>   66140 LOAD_GLOBAL              10 (str)
             68142 LOAD_FAST                05 (itemssecond_last)
            144 70CALL_FUNCTION            1
            146 LOAD_CONST               25 (-1', and ')
            148 72LOAD_GLOBAL BINARY_SUBSCR             0 (str)
            150 LOAD_FAST 74               6 (last)
            152 CALL_FUNCTION            1
            154 76BUILD_TUPLE YIELD_VALUE             3
            156 78GET_YIELD_FROM_ITER
 POP_TOP           158 LOAD_CONST               0 (None)
            160 80YIELD_FROM
            162 POP_TOP
            164 LOAD_CONST               0 (None)
             82166 RETURN_VALUE

  510     >>  168 84POP_TOP
            170 LOAD_CONST               0 (None)
             86172 RETURN_VALUE
from typing import Sequence, Any, Iterator


def comma_and_insertion(items: Sequence[Any]) -> Iterator[str]:
    if len(items) > 0:
        for item in items[:-1]:
            yield str(item)
             yield ', '

        if len(items) > 1:
            yield 'and '
        yield str(items[-1])


def list_string(items: Sequence[Any]) -> str:
    """Format with Oxford commas and the word "and" """
    return ''.join(comma_and_insertion(items))


def test() -> None:
    assert list_string([]) == ''

    assert list_string(['foo']) == 'foo'

    assert list_string(['foo', 'bar']) == 'foo, and bar'

    assert list_string(
        ['Apple', 'Lobster', 'Panda', 'Vis', 2, 3]
    ) == 'Apple, Lobster, Panda, Vis, 2, and 3'


if __name__ == '__main__':
    test()
              0 GEN_START                0

  5           2 LOAD_GLOBAL              0 (len)
              4 LOAD_FAST                0 (items)
              6 CALL_FUNCTION            1
              8 LOAD_CONST               1 (0)
             10 COMPARE_OP               4 (>)
             12 POP_JUMP_IF_FALSE       42 (to 84)

  6          14 LOAD_FAST                0 (items)
             16 LOAD_CONST               0 (None)
             18 LOAD_CONST               2 (-1)
             20 BUILD_SLICE              2
             22 BINARY_SUBSCR
             24 GET_ITER
        >>   26 FOR_ITER                10 (to 48)
             28 STORE_FAST               1 (item)

  7          30 LOAD_GLOBAL              1 (str)
             32 LOAD_FAST                1 (item)
             34 CALL_FUNCTION            1
             36 YIELD_VALUE
             38 POP_TOP

  8          40 LOAD_CONST               3 (', ')
             42 YIELD_VALUE
             44 POP_TOP
             46 JUMP_ABSOLUTE           13 (to 26)

 10     >>   48 LOAD_GLOBAL              0 (len)
             50 LOAD_FAST                0 (items)
             52 CALL_FUNCTION            1
             54 LOAD_CONST               4 (1)
             56 COMPARE_OP               4 (>)
             58 POP_JUMP_IF_FALSE       33 (to 66)

 11          60 LOAD_CONST               5 ('and ')
             62 YIELD_VALUE
             64 POP_TOP

 12     >>   66 LOAD_GLOBAL              1 (str)
             68 LOAD_FAST                0 (items)
             70 LOAD_CONST               2 (-1)
             72 BINARY_SUBSCR
             74 CALL_FUNCTION            1
             76 YIELD_VALUE
             78 POP_TOP
             80 LOAD_CONST               0 (None)
             82 RETURN_VALUE

  5     >>   84 LOAD_CONST               0 (None)
             86 RETURN_VALUE
from typing import Sequence, Any, Iterator


def comma_and_insertion(items: Sequence[Any]) -> Iterator[str]:
    match items:
        case (only,):
            yield str(only)
        case first, second:
            yield from (str(first), ' and ', str(second))
        case *others, second_last, last:
            for item in others:
                yield str(item) 
                yield ', '
            yield from (str(second_last), ', and ', str(last))


def list_string(items: Sequence[Any]) -> str:
    """Format with Oxford commas and the word "and" """
    return ''.join(comma_and_insertion(items))


def test() -> None:
    assert list_string([]) == ''

    assert list_string(['foo']) == 'foo'

    assert list_string(['foo', 'bar']) == 'foo, and bar'

    assert list_string(
        ['Apple', 'Lobster', 'Panda', 'Vis', 2, 3]
    ) == 'Apple, Lobster, Panda, Vis, 2, and 3'


if __name__ == '__main__':
    test()
              0 GEN_START                0

  5           2 LOAD_FAST                0 (items)
 
  6           4 DUP_TOP
              6 MATCH_SEQUENCE
              8 POP_JUMP_IF_FALSE       19 (to 38)
             10 GET_LEN
             12 LOAD_CONST               1 (1)
             14 COMPARE_OP               2 (==)
             16 POP_JUMP_IF_FALSE       19 (to 38)
             18 UNPACK_SEQUENCE          1
             20 STORE_FAST               1 (only)
             22 POP_TOP

  7          24 LOAD_GLOBAL              0 (str)
             26 LOAD_FAST                1 (only)
             28 CALL_FUNCTION            1
             30 YIELD_VALUE
             32 POP_TOP
             34 LOAD_CONST               0 (None)
             36 RETURN_VALUE

  6     >>   38 POP_TOP

  8          40 DUP_TOP
             42 MATCH_SEQUENCE
             44 POP_JUMP_IF_FALSE       45 (to 90)
             46 GET_LEN
             48 LOAD_CONST               2 (2)
             50 COMPARE_OP               2 (==)
             52 POP_JUMP_IF_FALSE       45 (to 90)
             54 UNPACK_SEQUENCE          2
             56 STORE_FAST               2 (first)
             58 STORE_FAST               3 (second)
             60 POP_TOP

  9          62 LOAD_GLOBAL              0 (str)
             64 LOAD_FAST                2 (first)
             66 CALL_FUNCTION            1
             68 LOAD_CONST               3 (' and ')
             70 LOAD_GLOBAL              0 (str)
             72 LOAD_FAST                3 (second)
             74 CALL_FUNCTION            1
             76 BUILD_TUPLE              3
             78 GET_YIELD_FROM_ITER
             80 LOAD_CONST               0 (None)
             82 YIELD_FROM
             84 POP_TOP
             86 LOAD_CONST               0 (None)
             88 RETURN_VALUE

  8     >>   90 POP_TOP

 10          92 MATCH_SEQUENCE
             94 POP_JUMP_IF_FALSE       84 (to 168)
             96 GET_LEN
             98 LOAD_CONST               2 (2)
            100 COMPARE_OP               5 (>=)
            102 POP_JUMP_IF_FALSE       84 (to 168)
            104 EXTENDED_ARG             2
            106 UNPACK_EX              512
            108 STORE_FAST               4 (others)
            110 STORE_FAST               5 (second_last)
            112 STORE_FAST               6 (last)

 11         114 LOAD_FAST                4 (others)
            116 GET_ITER
        >>  118 FOR_ITER                10 (to 140)
            120 STORE_FAST               7 (item)

 12         122 LOAD_GLOBAL              0 (str)
            124 LOAD_FAST                7 (item)
            126 CALL_FUNCTION            1
            128 YIELD_VALUE
            130 POP_TOP

 13         132 LOAD_CONST               4 (', ')
            134 YIELD_VALUE
            136 POP_TOP
            138 JUMP_ABSOLUTE           59 (to 118)

 14     >>  140 LOAD_GLOBAL              0 (str)
            142 LOAD_FAST                5 (second_last)
            144 CALL_FUNCTION            1
            146 LOAD_CONST               5 (', and ')
            148 LOAD_GLOBAL              0 (str)
            150 LOAD_FAST                6 (last)
            152 CALL_FUNCTION            1
            154 BUILD_TUPLE              3
            156 GET_YIELD_FROM_ITER
            158 LOAD_CONST               0 (None)
            160 YIELD_FROM
            162 POP_TOP
            164 LOAD_CONST               0 (None)
            166 RETURN_VALUE

 10     >>  168 POP_TOP
            170 LOAD_CONST               0 (None)
            172 RETURN_VALUE
Source Link
Reinderien
  • 65.3k
  • 5
  • 69
  • 187

Hang in there; this is about to get a bit technical.

It's a natural, and overall quite reasonable, strategy to do as you've done and write a list comprehension such as

[str(word) for word in l_list]

But what does that actually do inside of the Python interpreter? Let's ask the disassembler:

import dis
def list_string(l_list):
    if not type(l_list) is list:  # If not of type list throw's an error
        raise TypeError('Only lists are allowed')
    l_list = [str(word) for word in l_list]  # Makes sure everything is a string.

    # Rules for Oxford comma
    if len(l_list) == 1:
        return f"'{l_list[0]}'"
    elif len(l_list) == 2:
        return f"'{l_list[0]}' and '{l_list[1]}'"
    else:
        return "'{}, and {}.'".format(', '.join(l_list[:-1]), (l_list[-1]))

dis.dis(list_string)
  6           0 LOAD_GLOBAL              0 (type)
              2 LOAD_FAST                0 (l_list)
              4 CALL_FUNCTION            1
              6 LOAD_GLOBAL              1 (list)
              8 IS_OP                    1
             10 POP_JUMP_IF_FALSE       10 (to 20)

  7          12 LOAD_GLOBAL              2 (TypeError)
             14 LOAD_CONST               1 ('Only lists are allowed')
             16 CALL_FUNCTION            1
             18 RAISE_VARARGS            1

  8     >>   20 LOAD_CONST               2 (<code object <listcomp> at 0x7f2c36c942f0, file "/home/gtoombs/petprojects/stackexchange/285474.py", line 8>)
             22 LOAD_CONST               3 ('list_string.<locals>.<listcomp>')
             24 MAKE_FUNCTION            0
             26 LOAD_FAST                0 (l_list)
             28 GET_ITER
             30 CALL_FUNCTION            1
             32 STORE_FAST               0 (l_list)

 11          34 LOAD_GLOBAL              3 (len)
             36 LOAD_FAST                0 (l_list)
             38 CALL_FUNCTION            1
             40 LOAD_CONST               4 (1)
             42 COMPARE_OP               2 (==)
             44 POP_JUMP_IF_FALSE       31 (to 62)

 12          46 LOAD_CONST               5 ("'")
             48 LOAD_FAST                0 (l_list)
             50 LOAD_CONST               6 (0)
             52 BINARY_SUBSCR
             54 FORMAT_VALUE             0
             56 LOAD_CONST               5 ("'")
             58 BUILD_STRING             3
             60 RETURN_VALUE

 13     >>   62 LOAD_GLOBAL              3 (len)
             64 LOAD_FAST                0 (l_list)
             66 CALL_FUNCTION            1
             68 LOAD_CONST               7 (2)
             70 COMPARE_OP               2 (==)
             72 POP_JUMP_IF_FALSE       50 (to 100)

 14          74 LOAD_CONST               5 ("'")
             76 LOAD_FAST                0 (l_list)
             78 LOAD_CONST               6 (0)
             80 BINARY_SUBSCR
             82 FORMAT_VALUE             0
             84 LOAD_CONST               8 ("' and '")
             86 LOAD_FAST                0 (l_list)
             88 LOAD_CONST               4 (1)
             90 BINARY_SUBSCR
             92 FORMAT_VALUE             0
             94 LOAD_CONST               5 ("'")
             96 BUILD_STRING             5
             98 RETURN_VALUE

 16     >>  100 LOAD_CONST               9 ("'{}, and {}.'")
            102 LOAD_METHOD              4 (format)
            104 LOAD_CONST              10 (', ')
            106 LOAD_METHOD              5 (join)
            108 LOAD_FAST                0 (l_list)
            110 LOAD_CONST               0 (None)
            112 LOAD_CONST              11 (-1)
            114 BUILD_SLICE              2
            116 BINARY_SUBSCR
            118 CALL_METHOD              1
            120 LOAD_FAST                0 (l_list)
            122 LOAD_CONST              11 (-1)
            124 BINARY_SUBSCR
            126 CALL_METHOD              2
            128 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x7f2c36c942f0, file "/home/gtoombs/petprojects/stackexchange/285474.py", line 8>:
  8           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 6 (to 18)
              6 STORE_FAST               1 (word)
              8 LOAD_GLOBAL              0 (str)
             10 LOAD_FAST                1 (word)
             12 CALL_FUNCTION            1
             14 LIST_APPEND              2
             16 JUMP_ABSOLUTE            2 (to 4)
        >>   18 RETURN_VALUE

code object <listcomp> is referenced, and then later defined, to be an inner function - even though you haven't explicitly asked for an inner function to be made. In fact, for the most common implementations of Python, this is how generators are created.

It's not the end of the world for a function like this to be created, but if a function needs to be created, we should replace it with our own explicit function that

  • does everything we want it to without a pre-processing step: str()ing the items, yielding commas and yielding and;
  • avoids the in-memory list that you have created, going straight from a generator to a join; and
  • doing it all in a procedural style that is very easy to understand.

On top of that, this is a golden opportunity to write unit tests, so do that.

The generator approach can look like

from typing import Sequence, Any, Iterator


def comma_and_insertion(items: Sequence[Any]) -> Iterator[str]:
    if len(items) > 0:
        for item in items[:-1]:
            yield str(item)
            yield ', '

        if len(items) > 1:
            yield 'and '
        yield str(items[-1])


def list_string(items: Sequence[Any]) -> str:
    """Format with Oxford commas and the word "and" """
    return ''.join(comma_and_insertion(items))


def test() -> None:
    assert list_string([]) == ''

    assert list_string(['foo']) == 'foo'

    assert list_string(['foo', 'bar']) == 'foo, and bar'

    assert list_string(
        ['Apple', 'Lobster', 'Panda', 'Vis', 2, 3]
    ) == 'Apple, Lobster, Panda, Vis, 2, and 3'


if __name__ == '__main__':
    test()

If we dis() our new inner function comma_and_insertion, we get something similar to your implicit listcomp (note the FOR_ITER) - but that takes on all of the work in one pass:

              0 GEN_START                0

  5           2 LOAD_GLOBAL              0 (len)
              4 LOAD_FAST                0 (items)
              6 CALL_FUNCTION            1
              8 LOAD_CONST               1 (0)
             10 COMPARE_OP               4 (>)
             12 POP_JUMP_IF_FALSE       42 (to 84)

  6          14 LOAD_FAST                0 (items)
             16 LOAD_CONST               0 (None)
             18 LOAD_CONST               2 (-1)
             20 BUILD_SLICE              2
             22 BINARY_SUBSCR
             24 GET_ITER
        >>   26 FOR_ITER                10 (to 48)
             28 STORE_FAST               1 (item)

  7          30 LOAD_GLOBAL              1 (str)
             32 LOAD_FAST                1 (item)
             34 CALL_FUNCTION            1
             36 YIELD_VALUE
             38 POP_TOP

  8          40 LOAD_CONST               3 (', ')
             42 YIELD_VALUE
             44 POP_TOP
             46 JUMP_ABSOLUTE           13 (to 26)

 10     >>   48 LOAD_GLOBAL              0 (len)
             50 LOAD_FAST                0 (items)
             52 CALL_FUNCTION            1
             54 LOAD_CONST               4 (1)
             56 COMPARE_OP               4 (>)
             58 POP_JUMP_IF_FALSE       33 (to 66)

 11          60 LOAD_CONST               5 ('and ')
             62 YIELD_VALUE
             64 POP_TOP

 12     >>   66 LOAD_GLOBAL              1 (str)
             68 LOAD_FAST                0 (items)
             70 LOAD_CONST               2 (-1)
             72 BINARY_SUBSCR
             74 CALL_FUNCTION            1
             76 YIELD_VALUE
             78 POP_TOP
             80 LOAD_CONST               0 (None)
             82 RETURN_VALUE

  5     >>   84 LOAD_CONST               0 (None)
             86 RETURN_VALUE