Starred expression in ternary operator python

Question:

I wrote a python program to print the ascii value of up to every 3 numbers in a string or "ERROR" if the length is not divisible by three. I was golf the code when I ran into a SyntaxError.

Code:

c=input()
p=len(c)
c=[int(c[i:i+3])for i in range(0,len(c),3)]
print("ERROR"if p%3else*map(chr,c),sep='')#SyntaxError here

But this works:

c=input()
p=len(c)
c=[int(c[i:i+3])for i in range(0,len(c),3)]
print(*map(chr,c),sep='')

Putting a space before the * or after the 3 doesn’t work. I could just use ''.join but it’s one character longer. My question is why can’t I use a starred expression in a ternary operator?

Asked By: Joyal Mathew

||

Answers:

print(*(["ERROR"] if p%3 else map(chr,c)),sep="!")

keep it outside of the ternary

Answered By: Joran Beasley

Because the * has to apply to the whole expression that produces the set of arguments, not a part of it, and not conditionally. Internally, CPython uses different bytecodes for calling with unpacking vs. normal calls, so your conditional would require it to change the byte code to call print based on the arguments to print, essentially rewriting what you wrote into pseudocode like:

if p % 3:
    call(print, "ERROR", sep='')
else:
    call_varargs(print, *map(chr, c), sep='')

which is beyond the compiler’s capability.

If you wanted to make this work, you could do the following:

print(*(("ERROR",) if p%3 else map(chr,c)), sep='')

which ensures the whole ternary evaluates to an unpackable sequence and unpacks whatever survives unconditionally, avoiding the confusion.

Answered By: ShadowRanger

The * expander transforms a single enumerable variable into individual variables. E.g.

li = [1,2,3]
print(*li)

produces: 1 2 3 instead of [1, 2, 3].

One value vs. multiple values

It appears to remove the brackets and pass a single string to print, but this is only an appearance, it actually replaces the single list variable by 3 variables and is actually equivalent to:

print(li[0], li[1], li[2])

It works because print accepts a variable number of arguments, so in our case it can deal with the single list or with these three integers.

The conditional expression is a one-value operator

However in your code you use the star operator within a conditional expression:

c = '065066067068'
p = len(c)
c = [int(c[i:i+3]) for i in range(0, p, 3)]
print('ERROR' if p%3 else *map(chr, c), sep='!')

print would be able to accept both evaluations of the expression, a single string value ('ERROR') or multiple char values from map.

But the conditional expression prevents returning multiple values according to the condition (p%3). The expression output has to be a single value. So you have no other choice than to return the list from map as an enumerable, and un-nest it only outside of the ternary operator, e.g. in the print statement.

A string is an enumerable, not a scalar value

However this solution now introduces another problem: Un-nesting will also convert the constant string ERROR into single chars, as a string is considered by Python an enumerable of single chars (as you know since you use this property for your input string). When the condition is true, the output would be:

E!R!R!O!R

Therefore the string must be first converted to an enumerable of strings, e.g. a tuple

Final solution

if p%3: s = ('ERROR',)
else: s = map(chr, c)
print(*s, sep='!')

The outputs will be:

A!B!C!D
ERROR
Answered By: mins
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.