Syntax to skip elements when constructing a list/tuple/set/dict

I’m pretty regularly in the situation of making an inline list, and having an entry in the list that I want to include conditionally. For example:

verbose: bool = ...
subprocess.run(['cmd', '--verbose' if verbose else ...])

Usually I end up making the list separately:

args = ['cmd']
if verbose:
    args.append('--verbose')

Or sometimes if it keeps coming up I’ll use a placeholder and filter it out:

subprocess.run([arg for arg in ['cmd', '--verbose' if verbose else None] if arg is not None])

But those are both clunky. Has there ever been a proposal for a way to specify an element during list construction that is just skipped entirely? My first instinct is the pass keyword, e.g.:

subprocess.run(['cmd', '--verbose' if verbose else pass])

In general it would be the case that:

[1, pass, 2, pass, 3] == [1, 2, 3]

I expect something similar would also work in other inline constructions:

('yay', pass, 'tuples')
{'also': True, 'dicts': pass}
{'why', 'not', pass, 'sets'}

I don’t know the technical implications of using pass specifically and don’t care so much about the exact syntax, as long as it’s relatively simple compared to the workarounds I mentioned using above. Not sure if this has already been suggested, I was struggling to come up with good search keywords.

2 Likes

If you’re using None as a placeholder, you can use filter:

args = ['cmd', '--verbose' if verbose else None]
subprocess.run(filter(None, args))

I’ll note that the docs for run say it takes a sequence, and this is a generator, but it worked for me at least. Otherwise I’d add a call to list.

I’m less sure why you’d want this for a dictionary. If it’s for kwargs, I would expect there to already be a default value, or you’re required to supply something.

1 Like

That actually is the way I normally do it; I wrote it here with a list comprehension because my understanding is people are a bit down on map and filter these days.

It may not actually be useful for dictionaries, I’ve only ever really wanted it for lists, I just threw the others in at the end because it felt weird to add it for just the one type.

You can make use of falsy values:

verbose = ''  # '--verbose'
args = ['cmd', verbose]
print([arg for arg in args if arg])

You can also use iterable unpacking – the asterisk operator *:

subprocess.run(['cmd', *(['--verbose'] if verbose else [])])

It unpacks a list argument into the individual arguments. If the list is empty then it produces no values, hence is skipped.

You can use the same syntax for lists, tuples and sets, since it does not matter what kind of iterable is supplied to *. Try the following, for example:

for verbose in [False, True]:
    print(   [ 'cmd', *(['--verbose'] if verbose else []) ]   )
    print(   ( 'cmd', *(['--verbose'] if verbose else []) )   )
    print(   { 'cmd', *(['--verbose'] if verbose else []) }   )

For dictionaries, you would need to use dictionary unpacking ** instead:

{'cmd': 'value1', **({'--verbose': 'value2'} if verbose else {})}
1 Like

I think using filter is perfectly fine here. But you have to be sure that your list only contains either strings or None, not values of other types, since filtering on None removes all “falsy” values:

>>> list(filter(None, [0, 0.0, False, None, '', 'a'])) == ['a']
True

Honestly if I found myself writing this a lot (like if I were writing a workflow manager with a lot of different options) I’d think about creating a CommandGenerator class or something. Part of the challenge of maintaining this type of code is that different commands can use different names for the same idea (like one uses --verbose and one wants -vvv or something). Storing that sort of thing in code and then using a consistent get_args() interface might be easier to deal with.

This isn’t really related to optional list elements, just a thought for this use-case.

2 Likes

@mrozekma, I had a similar idea some time ago.
You might be interested in the following thread.

Great thread, thanks. Can’t believe I didn’t think of conditionally passing an argument to a function, I’ve run into that one too but less often than the others I mentioned. I like the syntax you proposed, although I’d guess it’s probably trickier to parse. I’m going to go read the rest of the discussion now.

Point well made, but I have to disagree that filter(None,...) is fine. Empty strings are perfectly legitimate argument values.