Here's a piece of code from this question, it's an alternative to a switch statement in Python.
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}[value](x)
I'm new to Python and I don't understand what it's doing. So I get that the first part is variable assignment - but what does [value](x) do?
The part between the braces {} defines a dictionary, with keys of 'a', 'b' and 'c'. The values for those keys are lambdas, which are anonymous functions (functions with no name). Each lambda defines a mapping of x to a function of x, with three different mappings, one for each key.
'[value]' looks up value in the dictionary. So if value = 'b', it will return lambda x: x + 7. This x is not related to the x in (x). The lambdas could have been defined as lambda y: y + 7 for example.
'(x)' then applies that lambda to whatever the value of x is.
So if value = 'b' and x = 8, the expression above will give 15 as the answer.
It is a convoluted way to replicate a switch statement. A cleaner way would be to define a helper function that will let you use a more straightfoward expression of what's going on:
def switch(v):yield lambda *c: v in c
Which you could use like this to get the equivalent result:
for case in switch(value):
if case('a'): result = x + 5
elif case('b'): result = x + 7
elif case('c'): result = x - 2
That is a very simple example which does little more than make the code more legible. When you have more complex or varied things to do in each case, then this switch pattern becomes a lot more valuable. You can also use it in a more C-Like style:
for case in switch(letter):
if case('a','A'):
y = complexCalculation(12,4)
result = y + 5
print(y)
break
if case('b','c','d'):
z = factorial(y) **2
result = z + 5
print(z)
break
else: # otherwise (all other cases)
print("invalid value provided")
Related
I am experimenting with how to condense if statements within my code. I have a project I am working on that has several "if" statements (too many to keep track of) and want to figure out a way to condense them. Obviously this involves a for loop, but I am having trouble adding additional operations within this loop.
I came up with the following working example to demonstrate my issue:
num=6
if_options = [num==5, num==6]
for i in range(len(if_options)):
if if_options[i]:
print(num)
I want to add an additional piece to the code. This additional piece will execute an operation within the if statement. See following non-working example as a framework for what I am trying to accomplish:
num=6
if_options = [num==5, num==6]
operations = [num=num+1, num=num-1]
for i in range(len(if_options)):
if if_options[i]:
operations[i]
print(num)
For whatever reason, it will not execute the operation portion of the code and fails with a syntax error. It does not let me declare the command "num=num+1" (without quotes) within a list, however this declaration is necessary for executing the command. I feel like I am missing one little thing and it should be an easy fix. Thank you in advance!!
The problem here is that the operations are evaluated when you create the list of them. You want to write them as strings, and then eval/exec them in the loop. I will assume you also want the conditions evaluated in the loop.
num = 6
if_options = ['num==5', 'num==6']
operations = ['num=num+1', 'num=num-1']
for i in range(len(if_options)):
if eval(if_options[i]):
exec(operations[i])
print(num)
why not functions?
def check(inp):
#you can do some logic and type checking here
return type(inp)==int # for example, or return arguments to pass to the operatiins
def operation(inp2):
if inp2: # check if true or not empty, as an example
#do some operations
# and then you do something like
for x in input_of_things:
operation( check( x ) )
You could use lambda expressions too.
num = 6
if_option_checks = [lambda x: x == 5, lambda x: x == 6]
operations = [lambda x: x + 1, lambda x: x - 1]
for check, operation in zip(if_option_checks, operations):
if check(num):
num = operation(num)
Or you could use dictionaries and lambda expressions
num = 6
if_option_checks = {"add": lambda x: x == 5, "sub": lambda x: x == 6}
operations = {"add": lambda x: x + 1, "sub": lambda x: x - 1}
for key, check in if_option_checks.items():
if check(num):
num = operations[key](num)
Perhaps a switch statement structure could help.
First define a switch function:
def switch(v): yield lambda *c: v in c
Then use is in a one-iteration for loop that yields a case function for the switch value:
for case in switch(num):
if case(5):
num = num + 1
break
if case(6):
num = num - 1
break
...
I am just learning about lambda expressions, and I am wondering how I could use it to count the number of vowels in a string. For example,
I am using the following:
result = lambda i, y: i + 1 for x in y if x in "aeoiuAEIOU"
print(result(0,s)
However, I recieve a syntax error.
Also, just to clarify my understanding, a lambda expression returns the expression following the colon. Could someone please point me in the right direction?
It's just the parse that's ambiguous. Consider
result = lambda i, y: (i + 1 for x in y if x in "aeoiuAEIOU")
Of course, now it will return a generator object, which won't print as nicely. If you want a list, you might try
result = lambda i, y: [i + 1 for x in y if x in "aeoiuAEIOU"]
Thank you Silvio for your help! Populating a list like you suggested gave me an answer that wasn't quite as clear as I wanted as it returned
[1, 1, 1, 1, 1]
While a generator returned
<generator object <lambda>.<locals>.<genexpr> at 0x0000028884166C00>
In the end, I used sum on the generator to get
result = lambda i, y: sum(i + 1 for x in y if x in "aeoiuAEIOU")
print(result(0, s))
Which gave me the answer of 5.
To me this is an improper use of lambda to begin with, as lambda's are supposed to be function definitions with no name or more or less a throw away function. Also the reason you got a Syntax Error is your generator expression is incorrect, use sum for something like this.
result = lambda y: sum(1 for x in y if x in "aeoiuAEIOU")
You can even use map for this:
result = lambda y: sum(map(y.count, "aeoiuAEIOU"))
But this could be done more legibly and imo more correctly with an actual function definition and you can include more statements
#more appropriately named count_vowels vs result and s vs y
def count_vowels(s):
_s = s.lower()
return sum(map(_s.count, 'aeiou')
You can also use re (regex) for this too:
import re
def count_vowels(s): return len(re.findall("[aeiou]", s, re.I))
The re.I ignores case so no need to worry about capsvs uncaps
You can also just use one parameter in lambda:
result = lambda y: sum(x in "aeoiuAEIOU" for x in y)
and the syntax error is not special with lambda:
In [10]: def bar(i, y):
...: return i + 1 for x in y if x in "aeoiuAEIOU"
File "<ipython-input-10-96fcd0959145>", line 2
return i + 1 for x in y if x in "aeoiuAEIOU"
^
SyntaxError: invalid syntax
the real issue is that i + 1 for x in y if x in "aeoiuAEIOU" is an invalid expression in Python.
The goal of this post is to put multiple print functions throughout a list comprehension to visually understand what's happening within.
Important notes:
This should not be used for anything other than educational purposes and trying to understand code.
If you are using Python 2.x, you need to add a future import (it's in the code I pasted) or else print won't work. Only functions work in list comprehension. Print in 2.x does not operate as a function. Or...just switch to Python 3.x.
This was the original question:
## Using future to switch Print to a function
from __future__ import print_function
reg = []
for x in [1,2,3]:
for y in [3,1,4]:
print('looping through',x,'then',y)
if x == y:
print('success',x,y)
reg.append((x,y))
print(reg)
Here's the equivalent list comprehension with no print statements.
from __future__ import print_function
comp = [(x,y) for x in [1,2,3] for y in [3,1,4] if x == y]
print(comp)
So is there any way to put in a bunch of print statements so both code print the same things?
Edit with solution to original question:
Using the methods in the comments - I've figured it out!
So say you want to convert this.
from __future__ import print_function
x = 1
y = 2
z = 1
n = 2
[[a,b,c] for a in range(x+1) for b in range(y+1) for c in range(z+1) if a + b + c != n]
Adding print statements to print each loop, showing if it failed or not.
from __future__ import print_function
x = 1
y = 2
z = 1
n = 2
[
[a,b,c] for a in range(x+1) for b in range(y+1) for c in range(z+1) if
(print('current loop is',a,b,c) or a + b + c != n)
and
(print('condition true at',a,b,c) or True)
]
So really the only thing that was changed was the conditional at the end.
(a + b + c != n)
to
(print('current loop is',a,b,c) or a + b + c != n)
and
(print('condition true at',a,b,c) or True)
Additional Information:
So there's good stuff in the comment section that I think would help others as well. I'm a visual learner so this website was great.
http://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/#colored-comprehension
(credits to Tadhg McDonald-Jensen)
I think you shouldn't running debug code inside list comprehensions, that said, if you wanted to do so, you could wrap your code inside a function like this:
from __future__ import print_function
def foo(x, y):
print('looping through', x, 'then', y)
if x == y:
print('success', x, y)
return (x, y)
comp = [foo(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x == y]
print(comp)
You need to evaluate your print function, but the return value isn't useful since it's always None. You can use and/or to combine it with another expression.
comp = [(x,y) for x in [1,2,3] for y in [3,1,4] if (print('looping through',x,'then',y) or x == y) and (print('success', x, y) or True)]
I really hope you're only doing this for educational purposes, because it's ugly as heck. Just because you can do something doesn't mean you should.
List comprehension was introduced with PEP 202 which states:
It is proposed to allow conditional construction of list literals
using for and if clauses. They would nest in the same way for loops
and if statements nest now.
List comprehension was designed to replace constructs that formed a list using only for loops, if conditionals and .append method once per iteration. Any additional structure is not possible in list comprehensions so unless you stuck your prints into one of the allowed components you cannot add them.
That being said, putting a print statement in the conditional - while technically possible - is highly not recommended.
[a for a in x if print("this is a bad way to test",a)]
I want to make a function that changes each elements in list with lambda function.
a = [1,5,2,4]
def func1(list,func2):
for x in range(len(list)):
list[x] = func2(list[x])
func1(a,lambda x: x>3 and 10 or x)
print a
The result is [1,10,2,10]
This is OK. But I change '10' to '0'
func1(a,lambda x: x>3 and 0 or x)
The result is [1,5,2,4]
Why doesn't the result be [1,0,2,0]?
I'm sorry that I'm poor at English.
The problem you have is that 0 is being evaluated as False which means that using the and-or trick as a conditional expression is failing.
Python 2.5 introduced "proper" conditional expressions of the form:
x = true_value if condition else false_value
So you can replace:
lambda x: x>3 and 0 or x
with:
lambda x: 0 if x > 3 else x
Also, you could use the map function to replace func1 if you're not bothered about updating the list in place:
a = map(lambda x: 0 if x > 3 else x,a)
print a
If you do want to modify the list in place you can use the enumerate function to simplify your code a little:
def func1(list,func2):
for i,x in enumerate(list):
list[i] = func2(x)
bool(0) -> False
bool(10) -> True
a and b or c
is equivalent (nearly, since your case proves it is not) to
b if a else c
So:
a = [1,5,2,4]
def func1(li,func2):
for x,el in enumerate(li):
li[x] = func2(el)
func1(a,lambda x: 0 if x>3 else x)
print a
Remark:
name list for a user's object is not good
use of iterate()
By the way, did you notice that you are changing in a function the value of an object external to the function ?
u = 102
def f(x):
x = 90
print "u==",u
result
u== 102
In your code, a is changed because it is a mutable object
In common case, a function has a return. Yours has not, because you change a mutable object.
x>3 and 0 or x always returns x because 0 is False.
Replace it with:
(x > 3 and [0] or [x])[0]
to get:
func1(a,lambda x: (x>3 and [0] or [x])[0])
Result:
[1, 0, 2, 0]
How it works:
The real return value is put into a single element list.
Any non-empty list is True, so the and or works.
Then use [0] to get the value back out.
You could turn it around:
func1(a,lambda x: x<=3 and x or 0)
(untested)
I find myself repeating this a lot:
val = x if x else y
Sometimes x goes several levels deep into a class or dictionary so it gets very long:
val = obj.elements[0].something if obj.elements[0].something else y
It looks ugly and forces me to type a lot more. Any known ways to shorten this? Perhaps a builtin like this exists?
val = first_try(x, y)
I guess I could easily write my own but was hoping for a built in.
first_try = lambda x,y: x if x else y
The or operator returns the first argument that converts to True:
val = x or y
E.g.:
>>> None or 'OK'
'OK'
This to me seems like a case of trying to make code a little too terse. I'd probably do something like this:
x = a.b.c.d[0]
val = x if x else y
One extra line, and a whole lot less to digest at once.
Use getattr with the optional default argument. In your long example above, this would be:
val = getattr(obj.elements[0], 'something', y)