DEV Community

Leandros Georgiou
Leandros Georgiou

Posted on

Coming from Java: functions are values in Python

I have recently started to learn python, and coming from Java there are a few interesting things I have found about functions.

The first thing that stuck out to me is that in Python, functions are values. What I mean by that, is a function is a thing that you can store, just like you can store a number or a string. For example, let's look at this simple function below which squares a number:

def square(item: int) -> int:
    return item * item
Enter fullscreen mode Exit fullscreen mode

This simple function can be assigned to a new variable (an alias):

square_2 = square
Enter fullscreen mode Exit fullscreen mode

Therefore, if we wanted to square a number we could either use square_2 or square as they both do the same thing.

You can even pass a function into another function like this:

print(apply_to_each(square, nums))
Enter fullscreen mode Exit fullscreen mode

So when a function doesn't have parentheses after it, it can be passed around and stored as a value. But, if we have a parentheses after, it runs the function instead. Therefore, if we wrote square_2 = square(), that would run the function square() and store the result, not the function itself.

In Java, if I wanted to pass some behaviour into another method like this, I'd usually have to wrap it up in an object first (a class that mimics function behaviour). In python, I can just pass the function straight in, which feels a lot simpler.

Here is the full example of passing a function into another function:

from typing import Callable

nums = [1, 2, 3, 4, 5, 6, 7]

def apply_to_each(fn: Callable, items: list[int]) -> list[int]:
    new = []
    for i in items:
        new.append(fn(i))
    return new

def square(item: int) -> int:
    return item * item

print(apply_to_each(square, nums)) # [1, 4, 9, 16, 25, 36, 49]
print(apply_to_each(lambda x: x-5, nums)) # [-4, -3, -2, -1, 0, 1, 2]
print(apply_to_each(str, nums)) # ['1', '2', '3', '4', '5', '6', '7']
Enter fullscreen mode Exit fullscreen mode

This is really useful because it allows us to write one general function (apply_to_each) that changes what it does based on the function we pass in. So we don't need to write a new loop every time we might want to do something different to a list.

Another thing I learnt about functions is that when we have a particularly small function, we don't even need to name it. We can use something called a lambda. Here is an example:

lambda x : x * 2

Let's break it down. Lambda here just means I am creating a simple function. Everything before the colon (the x) is the input, and everything after the colon (the x * 2) is what gets given back. So if x = 2, then lambda x : x * 2 gives us 4.

Coming from Java, treating functions as values felt strange at first, but it makes a lot of code shorter and simpler.

Top comments (1)

Collapse
 
raknaos profile image
Raknaos

Nice framing for the jump from Java, and the square vs square() distinction is exactly where people lose a whole afternoon. Passing str straight into apply_to_each is a good illustration too: that's the case with no clean Java equivalent, since a method reference there always has to carry a receiver.

Two things that bit me when I made the same move. Never define a lambda inside a loop and expect the loop variable to be captured per iteration — it's late-bound, so they all see the final value. And prefer Callable[[int], int] over a bare Callable in annotations, otherwise the type checker accepts anything and you lose the guard rail you had in Java.