I have a big if-elif-else
block in my code like this
if counted == 2:
score = None
elif counted == 1:
score = (sum(values) - 3) / 6 * 100
elif counted == 0:
score = (sum(values) - 4) / 8 * 100
else:
raise Exception('Should not be reached!')
With python I assume there is a way to solve that with a dict
using the counted
values as keys. But what comes as items into that dict? The new case
expression is not an option here. IMHO this wouldn't be pythonic no matter that it's now part of Python standard.
Approach A
# Approach A
mydict = {
0: ((sum(values) - 4) / 8 * 100),
1: ((sum(values) - 3) / 6 * 100),
2: None
}
print(mydict)
print(mydict[counted])
The problem here is that all items are executed.
{0: 4625.0, 1: 6183.333333333334, 2: None}
Approach B
mydict = {
0: (lambda _: (sum(values) - 4) / 8 * 100),
1: (lambda _: (sum(values) - 3) / 6 * 100),
2: (lambda _: None)
}
print(mydict)
print(mydict[counted](None))
Here I have the fake argument I don't need. But it seems to be mandatory when creating a lambda
.
{0: <function <lambda> at 0x7ff8f3dce040>, 1: <function <lambda> at 0x7ff8f3b6a670>, 2: <function <lambda> at 0x7ff8f3b6a700>}
Is there a another way?
Full MWE
#!/usr/bin/env python3
import random
values = random.choices(range(100), k=10)
counted = random.choice(range(3))
print(f'counted={counted}')
if counted == 2:
score = None
elif counted == 1:
score = (sum(values) - 3) / 6 * 100
elif counted == 0:
score = (sum(values) - 4) / 8 * 100
else:
raise Exception('Should not be reached!')
print(f'score={score}')
# Approach A
mydict = {
0: ((sum(values) - 4) / 8 * 100),
1: ((sum(values) - 3) / 6 * 100),
2: None
}
print(mydict)
print(mydict[counted])
# Approach B
mydict = {
0: (lambda _: (sum(values) - 4) / 8 * 100),
1: (lambda _: (sum(values) - 3) / 6 * 100),
2: (lambda _: None)
}
print(mydict)
print(mydict[counted](None))
CodePudding user response:
First of all, you don't need any arguments for lambda:
mydict = {
0: (lambda: (sum(values) - 4) / 8 * 100),
1: (lambda: (sum(values) - 3) / 6 * 100),
2: (lambda: None)
}
print(mydict)
print(mydict[counted]())
That being said, arguably the most Pythonic version would be version 0, if/else chain. It is most readable, calculates only what is necessary and doesn't overcomplicate anything.
CodePudding user response:
Relying on globals is usually a bad idea:
mydict = {
0: (lambda values: (sum(values) - 4) / 8 * 100),
1: (lambda values: (sum(values) - 3) / 6 * 100),
2: (lambda _: None)
}
print(mydict)
print(mydict[counted](values))
CodePudding user response:
Using a dictionary with lambda values merely serves to obfuscate the code. Furthermore it's difficult to understand for lesser beings than the OP. It also doesn't perform very well.
Here's a consolidation of 3 possible approaches with output that demonstrates how they each perform:
from timeit import timeit
values = [1, 2, 3, 4,]
mydict = {
0: (lambda _: (sum(_) - 4) / 8 * 100),
1: (lambda _: (sum(_) - 3) / 6 * 100),
2: (lambda _: None)
}
def v1(): # dictionary / lambda approach
for i in range(3):
score = mydict[i](values)
return score
def v2(): # match / case approach
for i in range(3):
match i:
case 0:
score = (sum(values) - 4) / 8 * 100
case 1:
score = (sum(values) - 3) / 6 * 100
case _:
score = None
return score
def v3(): # simple if/else
for i in range(3):
if i == 0:
score = (sum(values) - 4) / 8 * 100
elif i == 1:
score = (sum(values) - 3) / 6 * 100
else:
score = None
return score
for func in v1, v2, v3:
print(func.__name__, timeit(func))
Output:
v1 0.5237932950003596
v2 0.4183016259994474
v3 0.40317739200327196
Conclusion:
Keep it simple and clear and you'll very likely gain in terms of performance and maintainability
CodePudding user response:
The lambda
s without the arguments works. Just needs the :
import random
values = random.choices(range(100), k=10)
mydict = {
0: lambda: (sum(values) - 4) / 8 * 100,
1: lambda: (sum(values) - 3) / 6 * 100,
2: lambda: None
}
counted = random.choice(range(3))
mydict[counted]()