lambda with nested if's weird syntax - python

I like using lambdas more then I should so more than once I wanted to solve nested if statement with it and failed using standard syntax.
But then I tried something like this:
lambda x: 1 if x > 100 else 2 if x > 50 else 3
Surprisingly it works, although proper function is supposed to have elif:
def foo(x):
if x > 100:
return 1
elif x > 50:
return 2
else:
return 3
Any ideas why?

You don't have to use elif. You can use if inside another else which is the same as what lambda function is doing:
def foo(x):
if x > 100:
return 1
else:
if x > 50:
return 2
else:
return 3

I'd recommend
lambda x: 1 if x > 100 else (2 if x > 50 else 3)
for readability because explicit is better than implicit.
Indeed, due to evaluation from left to right, the ternary operator is right associative as the following code demonstrates
def f(a, b):
return 1 if a else 2 if b else 3
def left(a, b):
return (1 if a else 2) if b else 3
def right(a, b):
return 1 if a else (2 if b else 3)
tpl = "{:^8}{:^8}{:^6}{:^6}{:^6}"
print(tpl.format('a', 'b', 'f', 'left', 'right'))
for (a, b) in [(False, False),(False, True),(True, False),(True, True)]:
print(tpl.format(repr(a), repr(b), f(a,b), left(a,b), right(a,b)))
""" my output ->
a b f left right
False False 3 3 3
False True 2 2 2
True False 1 3 1
True True 1 1 1
"""
The ternary expression always gives the same result as the expression where everything after the first else is parenthesized.
When the conditions are (x>100) and (x>50), the case True, False can never happen, so the three expressions give the same result. Nevertheless, explicit is better than implicit!

You are actually using something like elif by writing
else 2 if x > 50
in
lambda x: 1 if x > 100 else 2 if x > 50 else 3
Its just a different way of writing something like elif in lambda

lambda x: 1 if x > 100 else 2 if x > 50 else 3
Means :
# 1 if x > 100
if x > 100:
return 1
# else 2 if x > 50 else 3
else:
# 2 if x > 50 else 3
if x > 50:
return 2
else:
return 3
Which does the same thing that your second snippet with elif.
You cannot use elif in a lambda expression. As far as I know, it should raise a SyntaxError.

You have concatenated two conditional expressions.
One
2 if x > 50 else 3
provides the value for the else clause of the other.
1 if x > 100 else ...
You have the conditional expression
x if C else y
where
x = 1
C = 100
and
y = 2 if x > 50 else 3
From the docs, y can be an expression so y can be a conditional expression.

Related

Python IF statement to a single line

Is it possible to put this code into a single line?
if x == 0:
a += j["sum"]
elif x == 1:
b += j["sum"]
e.e. :D
This is not a working example just for demonstration purposes
a += j["sum"] if x == 0 else b += j["sum"]
You can do it this way if you have Python 3.8 or later for the assignment expression operator :=:
(a := a + j["sum"]) if x == 0 else (b := b + j["sum"]) if x == 1 else None
but really the original is best. It's preferable that code is clear and straightforward.
I may suggest you this one-line dual assignment:
a, b = a + (j["sum"] if x == 0 else 0), b + (j["sum"] if x == 1 else 0)
But you can also use the old good semi-colon to perform two instructions on one line:
a += (j["sum"] if x == 0 else 0); b += (j["sum"] if x == 1 else 0)
I would strongly discourage it, but I guess one could use boolean true as the multiplication identity to do:
a, b = a + (x==0) * j["sum"], b + (x==1) * j["sum"]
This seems to work as I expect in a little loop
a=100
b=200
jsum=10
for x in range(3):
a, b = a + (x==0) * jsum, b + (x==1) * jsum
print(a, b)
Giving:
110 200
110 210
110 210
While I would not code like this myself, one might throw caution to the wind and leverage exec():
exec(f"{['a','b'][x]} += j['sum']" if x in [0,1] else "pass")
or potentially even better (credit to #wjandrea):
if x in {0, 1}: exec(f"{['a','b'][x]} += j['sum']")
Note, if you know x is always either 0 or 1 this would simplify to:
exec(f"{['a','b'][x]} += j['sum']")
Your pseudo-code solution suggests that might be so but your questions suggests it is not.
example:
a=100
b=200
jsum=10
for x in range(3):
exec(f"{['a','b'][x]} += jsum" if x in [0,1] else "pass")
print(a, b)
giving:
110 200
110 210
110 210
#wjandrea has suggested a nice improvement that produces the same results and is likely faster given the ability to bypass exec() if x is out of range.
a=100
b=200
jsum=10
for x in range(3):
if x in {0, 1}: exec(f"{['a','b'][x]} += jsum")
print(a, b)

Python: shortcut conditional expression

the shortcut conditional expression :
expression1 if condition else expression2
x=1 if a>3 else 2
But: can I have 2 expressions at the start ?
x=1,b=3 if a>3 else 2
Thanks to > idontknow, solution is >
previousTime,BS_Count=(db_row_to_list[0][14],BS_Count+1) if db_row_to_list[0][14] is not None else (db_row_to_list[0][3],BS_Count)
Not quite. For something this, it would be possible to use an if statement.
if a > 3:
x = 1
b = 3
else:
x = 2
b = None
If you want everything to become a oneliner, you can use tuple unpacking in Python. What tuple unpacking does is basically take the elements from a tuple and store them as variables, instead of elements of a tuple.
An application of this concept would be something like this:
x, b = (1, 3) if a > 3 else (2, None)
Note that it is a oneliner! 🤗
EDIT: To answer your question in the updated context:
You can use the following, shorter code. I think the effect will be the same.
a = 3
b = 7
c = 6
a, b = (8, b+1) if c > 3 else (5, b)
print(a, b)
In your example:
x=1 if a>3 else 2
The conditional expression part is:
1 if a>3 else 2
So, you're assigning the result of that conditional expression to x.
In your second example, this is invalid syntax:
x=1,b=3 if a>3 else 2
That's because it's equivalent to:
(1,b)=3 if a>3 else 2
Or, as a simpler example:
(1,b)=3
This is invalid syntax, because you can't do something like 1 = 3 in Python, which is what that code is trying to do. Although if both elements of the tuple were not literals, you'd get a different error:
(b,a)=3
TypeError: cannot unpack non-iterable int object
So, if you want to do multiple assignments with a conditional expression, you can have the conditional expression return a tuple, and have your left-hand side be a tuple of the two variables you want to assign values to:
x,b=(1, 3) if a>3 else (2, 2)
This is equivalent to:
if a > 3:
x = 1
b = 3
else:
x = 2
b = 2
Which is what I assume you intended with your original code.
You can use tuples:
>>> a = 4
>>> (x, b) = (1, 3) if a > 3 else (2, 2)
>>> x
1
>>> b
3
>>>
The statement x = 1, b = 3 if a > 3 else 2 is not valid. You can still have a one-liner though:
x, b = 1 if a > 3 else 2, 3 if a > 3 else 2
This sets the value of x to 1 if a > 3 else 2 and b to 3 if a > 3 else 2
For context : This is the actual code >
if db_row_to_list[0][14] is not None:
previousTime=db_row_to_list[0][14]
BS_Count+=1
else:
db_row_to_list[0][3]
I can do this though : ie. use a function > but is it any better??
num=3
num1=7
def doIt():
print("doIt()")
global num,num1
num=8
num1+=1
return num
a=6
num=doIt() if a>3 else 5
print(num," ",num1)

Clear mathematical way to cast int value to 0 or 1

Maybe it's too trivial question but I can't find answer if there is a some more elegant way to cast int value to 0 or 1 without using condition or type casting?
Now I have only following variants and both are ugly:
x = 5
a = some_int_value * (1 if x > 0 else 0)
b = some_int_value * int(bool(x))
ADDED
x is a non-negative value.
To paraphrase your condition, x can be 0 or a value larger than 0. If x > 0, you want to use the value some_int_value, otherwise you want 0 (which is identical to x). Then do:
c = x and some_int_value
If x is 0, i.e. falsey, the and expression returns x. If x is truthy (non-zero), it returns some_int_value.
Arguably even more comprehensible would be:
d = some_int_value if x > 0 else 0
Or:
e = 0
if x > 0:
e = some_int_value
Another way without functions, casting, or conditionals, but with comparisons:
d = some_int_value * (x > 0)
However, in terms of readability, a ternary ... if ... else ... should probably be preferred.
If you want a purely mathematical way, you can use exponentiation with 1 - 0x, since 0**x is 1 only if x == 0 and 0 otherwise. But whether that is in any way clear is another question. (Also note that this gives a division-by-zero for x < 0, but you said that you don't have those.)
>>> x = 5
>>> 1 - 0**x
1
>>> x = 0
>>> 1 - 0**x
0

Stopping arithmetico-geometric sentence

I have a function u_terme that computes values of the sequence 3u + 1. I would like a stop function reached(M) that returns the lowest u value at which a given functional value is reached.
However, my code below doesn't work: it exits immediately and prints a single 0. Help?
def u_terme(x):
i = 0
u = 0
while i < x:
u = (3 * u) + 1
i = i + 1
print(u)
def reached(M):
x = 0
f = 0
while f >= M:
f = u_terme(x)
x = x + 1
print(x)
ANALYSIS:
u_terme fails to return any value.
reached exits immediately: your while loop quits as soon as f < M.
You have that logic reversed: you want to continue while f <= M.
Also, please use meaningful variable names.
REPAIR:
Make u_term return the computed value:
def u_terme(x):
u = 0
for i in range(x):
u = 3*u + 1
# print x, u
return u
Make reached iterate properly and return its result:
def reached(limit):
val = 0
func_val = 0
while func_val < limit:
val += 1
func_val = u_terme(val)
print val, func_val
return val
print reached(50)
Output:
1 1
2 4
3 13
4 40
5 121
5
Output:
17
Unfortunately the question is so unclear that it is difficult to provide a definitive answer. For example, what are allowable values of M?
At the first look, however, it is obvious that your u_terme() function has no return value (well, that is, it always return None). This means that f in the reached() function after the first iteration will be None and the loop will terminate.
Just because back-in-forth in comments doesn't work to well, I think this is the correct simplified, more efficient version of the code:
def u_terme(u):
return (3 * u) + 1
def reached(M):
x = 0
u = 0
while u < M:
u = u_terme(u)
x += 1
return x
print(reached(50)) # 5
EDIT
To help with the confusion in the comments. This new u_terme doesn't do what the previous one did. The previous one took a number x and computed u x times. This new one just returns the next value based on the previous one. This avoids duplicate work.
This code shows how to use it to get the expected values:
def u_terme(u):
return (3 * u) + 1
u = 0
for i in range(5):
print(i, u)
u = u_terme(u)
# Output:
# 0 0
# 1 1
# 2 4
# 3 13
# 4 40
EDIT 2
Just for fun, here's another way to do this. Why this is correct is an exercise left to the reader. :-)
def reached(M):
u = 0
x = 0
while u < M:
u += 3**x
x += 1
return x

Python - nesting if else inside return

Can an if-else expression be the argument of 'return'?
Here's an example of what I'm trying to do:
return m +
if a:
x
elif b:
y
else c:
z
I could write as:
addend = m
if a:
m += x
elif b:
m += y
else c:
m += z
return m
Well, you can use Python's ternary method, such as:
return m + (x if a else y if b else z)
But it may be more readable to just do something like:
if a: return m + x
if b: return m + y
return m + z
As an aside, else c: is not really sensible code: you use if/elif if you have a condition, or else for default action (no condition).
For example, in terms of the code you posted in a comment, you could opt for the succinct, yet still self-documenting:
def rental_car_costs(days):
basecost = days * 40
discount = 50 if days >= 7 else 20 if days >= 3 else 0
return basecost - discount

Categories