Combining None and lists in one line Suppose we have variables, num and let, that may be (possibly nested) list, tuple, or None. The combination is to be such that:


Neither == None -> num + let
num == None -> let
let == None -> num
Both == None -> None
Return type is list or None
1-5 should hold for any number of variables, including one, and >2
Be a one-liner (one statement)


My approach, with test:

def join(_vars):
    return [x for v in _vars if v for x in v] or None

num = ((1, 2), None, [1, (2, 3)], None)
let = (('a', 'b'), ['a', ('b', 'c')], None, None)
vars_all = (let, num)

for i, _vars in enumerate(zip(*vars_all)):
    print(f"Case {i + 1}:", join(_vars))


Case 1: ['a', 'b', 1, 2]
Case 2: ['a', ('b', 'c')]
Case 3: [1, (2, 3)]
Case 4: None


Any better way to accomplish this? E.g. shorter, or more readable, or more intuitive than nested comprehensions. 

(For readability I'd opt for less abstract naming: [x for var in _vars if bool(var) for x in var]. Parentheses should also help, though unsure if can insert without changing results.)
As you mentioned, the nested comprehension is not very intuitive. I had to write it out for understanding.

I am also not sure I understood your requirements correctly. Please correct any wrong assumptions. These are the attempts:

from itertools import chain

def join(vars_):
    filtered = (element for element in vars_ if element)
    return chain.from_iterable(filtered)


num = ((1, 2), None, [1, (2, 3)], None)
let = (('a', 'b'), ['a', ('b', 'c')], None, None)
vars_all = (let, num)

for i, vars_ in enumerate(zip(*vars_all), start=1):
    print(f"Case {i}:", join(vars_))


It returns

Case 1: &lt;itertools.chain object at 0x00000221C73F3940&gt;
Case 2: &lt;itertools.chain object at 0x00000221C73F3940&gt;
Case 3: &lt;itertools.chain object at 0x00000221C73F3940&gt;
Case 4: &lt;itertools.chain object at 0x00000221C73F3940&gt;


So not what you really asked for. However, the chain object behaves like a normal list in most circumstances. It supports the Sequence interface (but not the MutableSequence interface):

from collections.abc import MutableSequence, Sequence
from itertools import chain


def join(vars_):
    filtered = (element for element in vars_ if element)
    return chain.from_iterable(filtered)


num = ((1, 2), None, [1, (2, 3)], None)
let = (("a", "b"), ["a", ("b", "c")], None, None)
vars_all = (let, num)

for i, vars_ in enumerate(zip(*vars_all), start=1):
    print(f"Case {i}:", list(join(vars_)))
    print("\tIs Sequence:", isinstance(vars_, Sequence))
    print("\tIs MutableSequence:", isinstance(vars_, MutableSequence))
    print(
        "\tIs MutableSequence (cast to list):", isinstance(list(vars_), MutableSequence)
    )


will print

Case 1: ['a', 'b', 1, 2]
        Is Sequence: True
        Is MutableSequence: False
        Is MutableSequence (cast to list): True
Case 2: ['a', ('b', 'c')]
        Is Sequence: True
        Is MutableSequence: False
        Is MutableSequence (cast to list): True
Case 3: [1, (2, 3)]
        Is Sequence: True
        Is MutableSequence: False
        Is MutableSequence (cast to list): True
Case 4: []
        Is Sequence: True
        Is MutableSequence: False
        Is MutableSequence (cast to list): True


So if a caller requires a list, they can cast to that type specifically. It is perhaps more pythonic to not do that in the function already (duck-typing).

Another point to note is that if both elements are None, an empty iterable is returned. This will still behave similar to None in boolean contexts. It allows for simpler code and maybe more predictable returns. See if that still fulfills your requirements. If not, this final suggestion will fulfill your tests as specified:

from itertools import chain


def join(vars_):
    if not any(vars_):
        return None
    filtered = (element for element in vars_ if element)
    return list(chain.from_iterable(filtered))


num = ((1, 2), None, [1, (2, 3)], None)
let = (("a", "b"), ["a", ("b", "c")], None, None)
vars_all = (let, num)

for i, vars_ in enumerate(zip(*vars_all), start=1):
    print(f"Case {i}:", join(vars_))


will return

Case 1: ['a', 'b', 1, 2]
Case 2: ['a', ('b', 'c')]
Case 3: [1, (2, 3)]
Case 4: None


for Python 3.8.1.
I hope you agree that this is more readable, intuitive and also debuggable.
The early return makes it easier to understand. The additional itertools import is very cheap.

Finally, a one-liner version of the above is

def join(vars_):
    return list(chain.from_iterable(elem for elem in vars_ if elem)) or None


Lastly, two notes:


notice the start keyword for enumerate
leading underscores denote (private) implementation details; use trailing underscores for variable names that otherwise shadow built-ins; better to avoid these all-together, though