0

I'm running Python 3.7.4 and I noticed some undesireable behavior while working on something, which I then reduced to this:

>>> x = 5
>>> x -= 1 if False else print("blah")
blah
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -=: 'int' and 'NoneType'

Unless there's something obvious I'm just missing? Why is it even trying to eval the -= if it's fallen through to the else?

Paul H
  • 65,268
  • 20
  • 159
  • 136
schmitty
  • 239
  • 3
  • 8
  • no. `x + None` should raise an error. If you had written `x -= 1 if else 5`, you'd expect `x -= 5` to happen at some point – Paul H Oct 19 '20 at 21:40
  • Sorry for being dense, but why x + None? it should only decrement x if the statement evals to true right? So why if we're hitting the false case is it even evaluating the -=? Should just print "blah"? – schmitty Oct 19 '20 at 21:41
  • x-=print('blah') doesn't work. If it's true the expression works, if it's false you're telling it to do something that doesn't work. – user3452643 Oct 19 '20 at 21:42
  • ah, gotcha. I'm thinking about this inline if wrong. – schmitty Oct 19 '20 at 21:43
  • This `if`-`else` is a [conditional expression](https://docs.python.org/3/reference/expressions.html#conditional-expressions), not a [conditional statement](https://docs.python.org/3/tutorial/controlflow.html#if-statements). – Fred Larson Oct 19 '20 at 21:43
  • Yep. Got it now thanks. Python newbie. – schmitty Oct 19 '20 at 21:44
  • 1
    The answer to "is this a bug in the language" is almost always NO. – Mark Ransom Oct 19 '20 at 21:47
  • 1
    :-D In my heart of hearts I knew that. – schmitty Oct 19 '20 at 21:47
  • Well, python newbie reborn, since I haven't touched it for a while to be exact... – schmitty Oct 19 '20 at 21:58

3 Answers3

5

This is grouped as:

(x) -= (1 if False else print("blah"))

Not:

(x -= 1) if False else (print("blah"))

Although since -= and other assignment statements don't themselves evaluate to a value, they can't appear as a part of a conditional expression. The left and right-hand arguments to the assignment are evaluated separately, then assigned to the left-hand argument:

An augmented assignment evaluates the target [x] . . . and the expression list [1 if False else print("blah")], performs the binary operation specific to the type of assignment on the two operands [-], and assigns the result to the original target.

The conditional expression on the right evaluates to None (because print returns None), then you attempt to subtract the resulting None from x.

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • Can u please explain, how `(1 if False else print("blah"))` gives none ? – Vishal Kamlapure Oct 19 '20 at 21:48
  • @VishalKamlapure `(1 if condition else print("blah"))` evaluates to `1` if the `condition` is truthy, and `print("blah")` if it's falsey. Since you have the condition as `False`, `(1 if False else print("blah"))` evaluates to `print("blah")`, which evaluates to `None`, since that's what `print` returns. – Carcigenicate Oct 19 '20 at 21:50
  • Conditional expressions are used to produce one of two values based on a condition. They aren't intended to conditionally do something (like conditionally do an assignment). – Carcigenicate Oct 19 '20 at 21:51
  • 1
    @VishalKamlapure maybe you can get some inspiration from [Does Python have a ternary conditional operator?](https://stackoverflow.com/a/394814/5987). – Mark Ransom Oct 19 '20 at 21:53
  • You should make clear that `x -= 1` is not an expression, and so cannot be part of a conditional expression, so explicit parentheses would not solve the problem. – chepner Oct 19 '20 at 22:00
1

There are two ways to do what you're after:

if <condition>:
    x -= 1

or

x -= 1 if <condition> else 0

In the first case, if the condition is met, 1 will be subtracted from x. Otherwise, no operation is performed on the value of x.

In the second case, some value is always subtracted from x. When the condition is met, that value is 1, otherwise it is 0 (effectively a no-op for the value of x).

Paul H
  • 65,268
  • 20
  • 159
  • 136
  • it's definitely not a no-op, because `` could easily have side effect: `a=[5]; x-=(1 if a.append(100) else 0)` – Z4-tier Oct 19 '20 at 22:12
  • @Z4-tier that's fair, but I think pretty beyond the scope of the OP's situation. In your example, it's still a no-op as far the value of `x` is concerned – Paul H Oct 19 '20 at 22:15
  • 1
    interestingly, I can't actually come up with anything that will make `dis.dis()` output a true `NOP` operation (opcode 9). None of `pass`, `0`, `True`, `None`, `False`, `exit()`, `1*1`. I get what you mean for the purpose of this question/answer, but now I'd really like to know what will generate an opcode 9 operation. I guess that is a good question in itself. – Z4-tier Oct 20 '20 at 00:49
  • @Z4-tier that's very interesting! I admit I was using the term loosely. – Paul H Oct 20 '20 at 01:25
0

That is not how you do ternary expressions in Python. You can do it like this:

x = (x-1 if False else print('foo'))

Only thing to keep in mind, this will set x=None since that is what the print statement returns.

If you mean to set x to the string foo, this will work

x=(x-1 if False else 'foo')

Or, as @PaulH suggested, x is to remain unchanged:

x=(x-1 if False else x and print('foo') or x)

doing that last one might not be the clearest way of doing this, but it works:

>>> x=5
>>> x=(x-1 if False else x and print('foo') or x)
foo
>>> x
5
>>> x=(x-1 if True else x and print('foo') or x)
>>> x
4
Z4-tier
  • 7,287
  • 3
  • 26
  • 42
  • 2
    that will assign `None` to the variable `x`, which I doubt the OP wants. – Paul H Oct 19 '20 at 21:44
  • @PaulH I can't really see anything else that OP might be after in this case, besides setting x to None. – Z4-tier Oct 19 '20 at 21:45
  • It seems to me that the OP wants to substract 1 from x if a condition is met. If that condition is not met, the value of x should remain unchanged – Paul H Oct 19 '20 at 21:46
  • 1
    Thanks all for the time and help. – schmitty Oct 19 '20 at 22:02
  • 1
    Really, none of these alternatives is an improvement over a simple `if/else` statement. – chepner Oct 19 '20 at 22:03
  • @chepner The first 2 aren't necessarily worse, either. The last one is definitely hokey though. But this is how the question was posed. – Z4-tier Oct 19 '20 at 22:05
  • 1
    The OP was clearly trying to use the conditional expression to execute *either* `x -= 1` or `print("blah")`. Not assign the return value of `print` to `x`, or to assign the string `"foo"` to `x`. The last one has the desired semantics, but is, frankly, an abomination. – chepner Oct 20 '20 at 11:57
  • @chepner There's not enough regular expressions for the last one to be an abomination. – Z4-tier Oct 20 '20 at 23:04