1

I am looking for some kind of "binary" (non-ternary) if that allows to define list membership in Python, similar to how it works in list comprehensions. Consider the following piece of code:

abc = (1, 0, 2)
my_list = [i for i in abc if i]
# observed: [1, 2]

a = 1
b = 0
c = 2
my_list = ["d" if a; "e" if b; "f" if c]
# expected: ["d", "f"]

While the first block works, the second does not (not surprisingly). I would find this syntax quite pythonic, however. Is there anything that comes close to it, such as

my_list = ["d" if a else Nothing; "e" if b else Nothing; "f" if c else Nothing]
bers
  • 4,817
  • 2
  • 40
  • 59
  • Is there a practical problem you are trying to solve? A ``list`` is usually compiled from equivalent elements, i.e. an already existing sequence/iterable. If your items are strewn across several variables, you might want to start at the advice for [variable variables](https://stackoverflow.com/questions/1373164/how-do-i-create-variable-variables) instead of trying to salvage their awkward usage. – MisterMiyagi Jan 21 '22 at 14:20
  • @MisterMiyagi well, no practical problem that cannot be solved by two to three lines of codes, of course. I wonder about pythonic syntax eliminating the need to write these lines of code in the first place. – bers Jan 21 '22 at 14:34
  • I am not sure what you mean by "equivalent", though. `"d"` and `"f"` are not equivalent, and lists of equivalent elements sound pretty boring to me. – bers Jan 21 '22 at 14:35
  • I suspect the main issue is that (at least prior to the PEG parser introduced in 3.10) there are problems distinguishing syntax like the proposed from a conditional expression in a LL(1) grammar. – chepner Jan 21 '22 at 14:35

3 Answers3

5

The if in a list comprehension is not an expression itself, only part of the syntax of a generator expression that allows you to provide a single Boolean condition to filter what the generator produces.

Your second block can be written as

my_list = [value 
           for (value, condition) in zip(["d", "e", "f"],
                                         [a, b, c]) 
           if condition]

As pointed out by user2357112 supports Monica, this is captured by the compress type provided by the itertools module:

from itertools import compress


my_list = list(compress(["d", "e", "f"],
                        [a, b, c]))
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 2
    There's also a [function for this](https://docs.python.org/3/library/itertools.html#itertools.compress) in itertools. – user2357112 Jan 21 '22 at 14:14
  • That's one I always overlook, thanks. – chepner Jan 21 '22 at 14:16
  • @user2357112supportsMonica looks like `itertools.compress` is the base version of `numpy`s logical indexing. – bers Jan 21 '22 at 14:17
  • The problem with both of these solutions is that the longer the (manually written, possibly) lists grow, the further a condition and its respective content are apart. This is solved better in the dictionary solution by @Tzane. – bers Jan 21 '22 at 14:19
  • What do you mean by "apart"? – chepner Jan 21 '22 at 14:20
  • I mean, "visually". Look at `["d" if a; "e" if b; "f" if c]`: I can easily see that `d` is included `if a`, etc. In your solution, imagine some 5-10 conditions and then trying to make that match, visually. – bers Jan 21 '22 at 14:31
  • Remember that I am primarily interested in syntax - I am pretty aware how to solve my question using two or three lines of code (although I am constantly amazed by the degree of creativity of other authors' solutions). – bers Jan 21 '22 at 14:31
  • @bers: In practical use cases, you're usually not going to have a finite, hardcoded set of values like that. You'll be iterating. See how your original `my_list = [i for i in abc if i]` was a list comprehension instead of trying to do something like `[1 if 1, 0 if 0, 2 if 2]`? Iteration, not hardcoded values. – user2357112 Jan 21 '22 at 14:34
  • The dictionary shows the correspondence clearly when you've got a finite set of hardcoded values, but when you don't, that advantage vanishes. – user2357112 Jan 21 '22 at 14:36
  • @user2357112supportsMonica I think it can easily happen to want such a finite set of hardcoded values. Let me give you an `argparse` example with `store_true` actions for using a CNN. `operations = [TRAIN if args.train, VAL if args.val, TEST if args.test]` is something I would imagine useful, so I can loop over `operations` later. (I am not saying one cannot restructure the `argparse` arguments to accept such a list, but let's consider these given.) How else would you construct my list in a single line with visual matching between conditions and elements? – bers Jan 21 '22 at 14:41
1

Using a dictionary:

>>> d = {1: "d", 0: "e", 2: "f"} 
>>> [d[k] for k in d if k] 
['d', 'f']
Tzane
  • 2,752
  • 1
  • 10
  • 21
  • 3
    Assuming `k` is hashable, you still have to build the `dict` from the lists of values and conditions. – chepner Jan 21 '22 at 14:18
1

You could add individual sublists based on conditions multiplying the sublist by the boolean results:

my_list = ["d"]*(a>0) + ["e"]*(b>0) + ["f"]*(c>0)

Or you could write a general purpose function to create a list from value/condition pairs:

def varList(*items):
    return [n for n,c in zip(*[iter(items)]*2) if c]

my_list = varList("d",a,"e",b,"f",c)
Alain T.
  • 40,517
  • 4
  • 31
  • 51