Is it possible to have multiple statements in a python lambda expression?

Viewed 199051

I am a python newbie trying to achieve the following:

I have a list of lists:

lst = [[567,345,234],[253,465,756, 2345],[333,777,111, 555]]

I want map lst into another list containing only the second smallest number from each sublist. So the result should be:

[345, 465, 333]

For example if I were just interested in the smallest number, I could do:

map(lambda x: min(x),lst)

I wish I could do this:

map(lambda x: sort(x)[1],lst)

but sort does not chain. (returns None)

neither is something like this allowed:

map(lambda x: sort(x); x[1],lst) #hence the multiple statement question

Is there a way to do this with map in python but without defining a named function? (it is easy with anonymous blocks in ruby, for example)

22 Answers

There are several different answers I can give here, from your specific question to more general concerns. So from most specific to most general:

Q. Can you put multiple statements in a lambda?

A. No. But you don't actually need to use a lambda. You can put the statements in a def instead. i.e.:

def second_lowest(l):
    l.sort()
    return l[1]

map(second_lowest, lst)

Q. Can you get the second lowest item from a lambda by sorting the list?

A. Yes. As alex's answer points out, sorted() is a version of sort that creates a new list, rather than sorting in-place, and can be chained. Note that this is probably what you should be using - it's bad practice for your map to have side effects on the original list.

Q. How should I get the second lowest item from each list in a sequence of lists?

A. sorted(l)[1] is not actually the best way for this. It has O(N log(N)) complexity, while an O(n) solution exists. This can be found in the heapq module.

>>> import  heapq
>>> l = [5,2,6,8,3,5]
>>> heapq.nsmallest(l, 2)
[2, 3]

So just use:

map(lambda x: heapq.nsmallest(x,2)[1],  list_of_lists)

It's also usually considered clearer to use a list comprehension, which avoids the lambda altogether:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]

You can in fact have multiple statements in a lambda expression in python. It is not entirely trivial but in your example, the following works:

map(lambda x: x.sort() or x[1],lst)

You have to make sure that each statement does not return anything or if it does wrap it in (.. and False). The result is what is returned by the last evaluation.

Example:

>>> f = (lambda : (print(1) and False) or (print(2) and False) or (print(3) and False))
>>> f()
1
2
3

While reading this long list of answers, I came up with a neat way of abusing tuples:

lambda x: (expr1(x), expr2(x), ..., result(x))[-1]

This will produce a lambda that evaluates all the expressions and returns the value of the last one. That only makes sense if those expressions have side-effects, like print() or sort().

Normal assignments (=) aren't expressions, so if you want to assign a variable in a lambda you have to use := operator (added in Python 3.8) that also returns the assigned value:

lambda: (answer := 42, question := compute(answer), question)[-1]

But the above only works for local variable assignments. To assign e.g. a dict element you will have to use d.update({'key': 'value'}), but even with a list there is no similar option, l.__setitem__(index, value) has to be used. Same with deletion: Standard containers support x.pop(index) that is equivalent to del x[item] but also returns deleted value, but if it is unavailable you have to use x.__delitem__(index) directly.

To affect global variables, you have to modify the dict returned by globals():

lambda: (g := globals(), g.__setitem__('answer', 42), g.__delitem__('question'), None)[-1]

Lists, dicts or even sets can be used instead of tuples to group expressions into one, but tuples are faster (mostly because immutable) and don't seem to have any disadvantages.

Also, DON'T USE ANY OF THIS IN PRODUCTION CODE! Pretty please!

Or if you want to avoid lambda and have a generator instead of a list:

(sorted(col)[1] for col in lst)

After analyzing all solutions offered above I came up with this combination, which seem most clear ad useful for me:

func = lambda *args, **kwargs: "return value" if [
    print("function 1..."),
    print("function n"),
    ["for loop" for x in range(10)]
] else None

Isn't it beautiful? Remember that there have to be something in list, so it has True value. And another thing is that list can be replaced with set, to look more like C style code, but in this case you cannot place lists inside as they are not hashabe

I made class with methods using lamdas on one line:

(code := lambda *exps, ret = None: [exp for exp in list(exps) + [ret]][-1])(Car := type("Car", (object,), {"__init__": lambda self, brand, color, electro = False: code(setattr(self, "brand", brand), setattr(self, "color", color), setattr(self, "electro", electro), setattr(self, "running", False)), "start": lambda self: code(code(print("Car was already running, it exploded.\nLMAO"), quit()) if self.running else None, setattr(self, "running", True), print("Vrooom")), "stop": lambda self: code(code(print("Car was off already, it exploded.\nLMAO"), quit()) if not self.running else None, setattr(self, "running", False), print("!Vrooom")), "repaint": lambda self, new_color: code(setattr(self, "color", new_color), print(f"Splash Splash, your car is now {new_color}")), "drive": lambda self: code(print("Vrooom") if self.running else code(print("Car was not started, it exploded.\nLMAO"), quit())), "is_on": lambda self: code(ret = self.running)}), car := Car("lamborghini", "#ff7400"), car.start(), car.drive(), car.is_on(), car.drive(), car.stop(), car.is_on(), car.stop())

more readable variation:

(
    code :=
    lambda *exps, ret = None:
    [
        exp
        for exp
        in list(exps) + [ret]
    ][-1]
)(

Car := type(
    "Car",
    (object,),
    {
        "__init__": lambda self, brand, color, electro = False: code(
            setattr(self, "brand", brand),
            setattr(self, "color", color),
            setattr(self, "electro", electro),
            setattr(self, "running", False)

        ),
        "start": lambda self: code(
            code(
                print("Car was already running, it exploded.\nLMAO"),
                quit()
            ) if self.running
            else None,
            setattr(self, "running", True),
            print("Vrooom")
        ),
        "stop": lambda self: code(
            code(
                print("Car was off already, it exploded.\nLMAO"),
                quit()
            ) if not self.running
            else None,
            setattr(self, "running", False),
            print("!Vrooom")
        ),
        "repaint": lambda self, new_color: code(
            setattr(self, "color", new_color),
            print(f"Splash Splash, your car is now {new_color}")        
        ),
        "drive": lambda self: code(
            print("Vrooom") if self.running
            else code(
                print("Car was not started, it exploded.\nLMAO"),
                quit()
            )
        ),
        "is_on": lambda self: code(
            ret = self.running
        )
    }
),

car := Car("lamborghini", "#ff7400"),
car.start(),
car.drive(),
car.is_on(),
car.drive(),
car.stop(),
car.is_on(),
car.stop()
)

I use lambda function here that takes any number of arguments and return ret argument default to None to be able to have more expressions on one line splitted by ",".

Yes. You can define it this way and then wrap your multiple expressions with the following:

Scheme begin:

begin = lambda *x: x[-1]

Common Lisp progn:

progn = lambda *x: x[-1]

There are better solutions without using lambda function. But if we really want to use lambda function, here is a generic solution to deal with multiple statements: map(lambda x: x[1] if (x.sort()) else x[1],lst)

You don't really care what the statement returns.

Yes it is possible. Try below code snippet.

x = [('human', 1), ('i', 2), ('am', 1), ('.', 1), ('love', 1), ('python', 3), ('', 1),
  ('run', 1), ('is', 2), ('robust', 1), ('hello', 1), ('spark', 2), ('to', 1), ('analysis', 2), ('on', 1), ('big', 1), ('data', 1), ('with', 1), ('analysis', 1), ('great', 1)
]

rdd_filter = rdd1_word_cnt_sum.filter(lambda x: 'python' in x or 'human' in x or 'big' in x)
rdd_filter.collect()

to demonstrate the lambda x:[f1(),f2()] effect which enables us to execute multiple functions in lambda. it also demonstrates the single line if else conditions if you really want to shrink the code.

  • note that f1() can be a lambda function also(recursive lambda or lambda within lambda). and that inner lambda can be a statement/function of your choice.
  • you can also put exec('statement') for example lambda x:[exec('a=[1]'),exec('b=2')]

a python implementation of touch(linux) command which creates empty files if they are not already existing.

def touch(fpath):
    check= os.path.exists(fpath)
    (lambda fname1:[open(fname1,"w+",errors="ignore").write(""),print('Touched',fname1)] 
    if not check else None) (fpath)

will print [ Touched fpath ] where fpath is file path given as input. will do nothing if file already exist.

the (lambda x: [ f(x), f2(x) ] ) (inp) <- we pass the 'inp' as input to lambda which in this case is the fpath.

I can offer you these three ways:

map(lambda x: sorted(x)[1], lst))
map(lambda x: min(x[:x.index(min(x))]+x[x.index(min(x))+1:]), lst)
map(lambda x: min([num for num in x if num != min(x)]), lst)

I know this is an old thing but it has more relevant answers. So, based on narcissus313's answer I have a small correction to make:

Original: map(lambda x: sorted(x)[1], lst))

Actual: map(lambda x: (sorted(x)[1], lst))

I know this is a small thing and it's rather obvious but it won't work without the missing bracket. The thing is that lambda expressions can't take multiple arguments but they can take a list/tuple of multiple actions.

Related