How to mix list comprehensions with non-comprehension items?

I want to build-up a list from some individual items as well as groups of items defined by list comprehensions. Suppose I want a list ['foo', 0, 2, 4, 'bar'], where 0,2,4 would come from a comprehension. The only way I know of doing it is:

['foo'] + [x*2 for x in range(3)] + ['bar']

Is there a way of doing it without explicit appending? I'm looking for some clever equivalent to the following non-working desired syntax:

['foo', x*2 for x in range(3), 'bar'] # invalid syntax :(

My presumption is that perhaps a similar syntax would avoid the creation of all the intermediate temporary lists that then have to be copied element-by-element in order to append them to the list being built, when using the explicit + operator.

I'm looking for a solution that'd preferably be pythonic (idiomatic), for Python 2.7 and 3.x - should they be different.

The motivating code is below, where ScreamingBOM is a named tuple - I'm trying to build the tuple up while creating as few temporaries as possible, since some of them may get quite sizable.

def make_screaming_bom(self, bom):
    return ScreamingBOM(
        summary = [
            ['', '', 'generated on: %s' % bom.time],
            ['', bom.unique_parts, 'unique parts'],
            []
        ]
        + [['', count, 'total %s' % (type)] for type, count in bom.counts.items()]
        + [
            ['', bom.total, 'total placements'],
            [],
            ['BOM: %s' % (bom.title)],
            ['Assembly Part Number/Revision: %s / %s' % (bom.basename, bom.revision)],
            ['Customer: %s' % (bom.company)]
        ],
        # other tuple fields follow
    )

Upvotes: 0

Views: 93

Answers (2)

ShadowRanger
ShadowRanger

Reputation: 155684

Best you can get to avoid temporaries is using generalized unpacking with a generator expression:

['foo', *(x*2 for x in range(3)), 'bar']

On CPython 3.9, that compiles to code equivalent to making an empty list, appending 'foo', extending with the genexpr, then appending 'bar'. The extend step consumes the generator without realizing it as a list, avoiding any temporaries. The code with a temporary list (see bb1's answer) is likely to be slightly faster, but if you're set on avoiding temporaries, this is the most memory efficient solution.

The Python 2 "solution" is you just make a list and manually append and extend line-by-line until you're done. If you really insist on one-liners no matter how ugly, using itertools.chain will allow something fairly similar to how generalized unpacking worked when it was first introduced through 3.8 (items not unpacked were wrapped in temporary tuples, then a list was built by unpacking all the arguments passed):

list(itertools.chain(('foo',), (x*2 for x in range(3)), ('bar',)))

But Python 2 has been end-of-life for over a year, so just write modern Python 3 code. :-)

Upvotes: 5

bb1
bb1

Reputation: 7873

How about list upacking?

['foo', *[x*2 for x in range(3)], 'bar']

Upvotes: 2

Related Questions