Extreme Python

Logo

Extremely good python content :)

About

Articles

Python function comparison: lambda functions vs normal functions

by Alan

There’s two types of functions that you can create in python: normal functions and lambda functions.

Here’s an example of a normal function, which is what you commonly see in programs.

def square(x):
    return x * x 

Here’s an example of a lambda expression:

lambda x: x * x

And here’s a comparison table of the two:

Trait Normal Lambda
First class? Yes Yes
Accepts parameters? Yes Yes
Can be named? Yes Yes
Can be anonymous? No Yes
Can contain any number of expressions or statements? Yes Only a single expression

These two function types are fundamentally the same in behavior: first class citizenship, parameterization, and the ability to be named. Anonymity is really the primary factor for deciding whether to use one or the other.

Lets first go over the sets of traits they have in common and then we’ll go over the differences.

Things that are the same

Naming

Guess what? Lambda functions can also be named! Just like normal functions!

Here’s a definition of the square function using a lambda:

square = lambda x: x * x

In my experience, this is a nice technique to use if you have a lambda that you need to re-use and you dont want the definition to span multiple lines.

First class

Python functions are first class. They’re objects that can be passed around as arguments just like any other object.

def do_map(fn, elements):
    result = []
    for i in elements:
        result.append(fn(i))
    return result

def square(x):
    return x * x

do_map is a custom map function which iterates over a list, applies the function fn to each item in the list and stores the result in a new list. In practice you’ll probably want to either use the built-in map or a list comprehension, but this custom definition makes it explicit that the function expects a function object fn.

Now lets invoke do_map by passing in square:

>>> do_map(square, [2, 4])
[4, 16]

We can also do this with an anonymous lambda:

>>> do_map(lambda x: x * x, [2, 4])
[4, 16]

Arguments and parameters

Functions support both positional parameters and named parameters.

Positional

>>> def foo(a):
...     print(a)
...
>>> foo(1)
1
>>> def foo_two(a, b):
...     print(a)
...     print(b)
...
>>> foo_two(1, 2)
1
2
>>>

Named

>>> def foo(a=None):
...     print(a)
...
>>> foo(1)
1
>>> foo()
None
>>> def foo_two(a=None, b=2):
...     print(a)
...     print(b)
...
>>> foo_two(1, 3)
1
3
>>> foo_two()
None
2

Both positional and named

>>> def foo(a, b, c=1, d=2):
...     print(a)
...     print(b)
...     print(c)
...     print(d)
...
>>> foo('hello', 'world')
hello
world
1
2
>>> foo('hello', 'world', c=5, d=7)
hello
world
5
7

And here’s a quick example with a lambda:

>>> (lambda x, y=5: x * y)(3)
15

Parameter unpacking

This is useful when you need to pass in a variable number of either positional arguments or keyword arguments.

Random fact: prior to python 3.7 the limit to the number of arguments was 255.

Unpacking positional arguments:

>>> def foo(*args):
...     print(args)
...
>>> foo(1, 2, 3, 4)
(1, 2, 3, 4)

Unpacking keyword arguments:

>>> def foo(**kwargs):
...     print(kwargs)
...
>>> foo(a=5, b=12)
{'a': 5, 'b': 12}

Unpacking both positional and keyword arguments:

>>> def foo(*args, **kwargs):
...     print(args)
...     print(kwargs)
...
>>> foo(1, 2, 3, a=4, b=5)
(1, 2, 3)
{'a': 4, 'b': 5}
>>>

And here’s a lambda example:

>>> (lambda *x, **y: y)(1, 2, a=5)
{'a': 5}

Parameter ordering

The only order requirements for paramters is that positional parameters must come before keyword parameters. This is what will happen if you attempt to define name arguments before positional ones:

>>> def foo(name=5, c):
...     print(name)
...
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument

Things that are different

Function body

The bodies of normal functions can contain any number or types of expressions or statements - including other function definitions.

def parent():
    def child():
        print("im the child")
    child()
    print("im the parent")

As you can see, a function doesn’t necessarily need to return a value. However, it does need to have something in the body.

If you absolutely need a placeholder for the actual implementation (a common need for some TDD practitioners), just use the keyword pass. This will make the function to return None.

Unlike compound functions, you’re only allowed to have a single expression in the body of a lambda, and statements (such as assignment statements or control flow statements) are not permitted.

For example:

>>> lambda x: a = x
  File "<stdin>", line 1
SyntaxError: can't assign to lambda

Relationship to classes

Normal functions can be defined as methods or functions that belong to objects, which you cannot do with lambda definitions.

This defines a function that gets passed the object instance when invoked on the object:

class Mouse():
    def greet(self):
        print("Squeak")

This only defines a class variable:

class Mouse():
    greet = lambda self: "Squeak"

You can still call it on the instance object Mouse() but it will not be treated as if it were an instance method (which are provided with a reference to the instance itself).

tags: