Python Functions

4 minute read

Recently I gave an explanation to a friend about the use of lambda functions, and why they exist. In python, a function/method/definition is an object, and it helps to keep this in mind. Throughout this post, I will make references to C# in order to contrast the difference between python and another language’s implementation.

In python, functions are objects that have a __call__ method (source) that is “called” when an instance of a function is invoked. Because of this, python allows you to pass function signatures as objects.

from typing import *

def GreetingFormatter(name: str) -> str:
  return f"Hello there, {name}!"

def PrintList(strList: List[str], formatter: Callable[[str], str] = None) -> None:
  if formatter is None:
    formatter = lambda x: x

  for item in list(map(formatter, strList)):
    print(item)

names = ["Phillip", "Jim", "Pam"]
PrintList(names, GreetingFormatter)

Output:

Hello there, Phillip!
Hello there, Jim!
Hello there, Pam!

This code has two functions: one to format a string, another to print a list of strings after applying some kind of formatting operation. Using the typing module to provide type hinting, you can see that the PrintList function takes an argument that is a function on a string, which returns a string. Our GreetingFormatter function is just that! It takes one string argument, and returns one, too.

The final line, PrintList(names, GreetingFormatter), is where we pass out formatter function as a parameter, just like we do the object names, which is a list of strings.

Within the function PrintList, there is an additional check for the second argument, to check it is not None, since it is an optional argument with a default of None. You can see the use of a lambda expression, here!

if formatter is None:
  formatter = lambda x: x

The right-hand side of the assignment has the lambda, which is also known as an anonymous function. Simply, it is a function that has no calling signature. Although, even that definition doesn’t truly do it justice, since it is assigned to a variable. One difference is that unlike a function, the lambda will be collected by the garbage collector when it falls out of scope. Thus, we can assign a simple function in-line without the need to add another function to the codebase, which is typically preferable if it is something that isn’t repeated; or, as is the case in the above example, is incredibly simplistic for our None check.

Good practice as a software engineer is to follow good practices, such as self-documenting code. When adhering to that standard, having a lambda expression can be perfectly reasonable for short in-line snippets of code. A similar argument can be made on a similar topic: expression builders.

numberStrList = [str(x) for x in range(1, 10001)]

Using python’s notably pythonic syntax, expression builders allow for in-line functions to build an object. This example enumerates numbers 1 to 1000 (inclusive), converts the int to a str, and then forms a list from the elements. The alternative would be something like:

def MakeNumberStrList(upper: int) -> List[str]:
  strList = []
  for i in range(1, upper):
    strList.append(str(i))
  return strList

In the case of code that is short and only requires a collection built once, it may seem like overkill to make a function for every type of list needing to be built. Another example would be to use conditionals:

factorsFiveSeven = [x for x in range(1000) if x % 5 == 0 or x % 7 == 0]

Once again, readable code does not require it’s own function, and an expression builder is perfectly valid!

Python has many benefits in that functions are objects. That isn’t to say other languages do not contain their own implementations to achieve similar effects, however:

public IList<string> FormatAList(IList<string> myList, Func<string, string> formatter)
{
  return myList.Select(x => formatter(x)).ToList();
}

This C# method uses LINQ to enumerate a list, select each item, and apply the function. Then, the resulting enumerable is converted to a list and then returned. In C#, Func objects are function objects. I can invoke this function in several ways. Two of which could be:

public string GetFirstCharacter(string input)
{
  return input.FirstOrDefault();
}

// ...

var names = new List<string> { "Phillip", "Jim", "Pam" };

var formatted = FormatAList(names, GetFirstCharacter);
var formattedAlt = FormatAList(names, x => x.FirstOrDefault());

Both of these ways are analogous to similar implementations in python. However, in C#, methods are delegates. One difference in python’s object-implementation can be seen when observing decorators. A decorator is a kind of wrapper, which uses syntactic-sugar to keep code readable.

def LogFunctionStartEnd(function):
  def Wrapper(*args, **kwargs):
    print("Function starting soon!!")
    function()
    print("Aaaaand we're done!")
  return Wrapper

@LogFunctionStartEnd
def PrintHello():
  print("Hello")

PrintHello()

In short, this reassigns PrintHello to be LogFunctionStartEnd(PrintHello). Then, calling PrintHello() is calling Wrapper(). In C#, there are similar decorators in the form of attributes, but these are objects:

public class CoolFunctionAttribute : Attribute
{
  // Some implementation here
}

// ...

[CoolFunction]
public string ReturnCool()
{
  return "Cool";
}

In closing, lambdas, expression builders, and functions in general, are really clever and flexible in python. That isn’t to say that other languages such as C# aren’t so in their own ways, either, such that python cannot compete; however, understanding the use and importance at even a high-level will make coding much smoother — especially for a novice programmer!

Tags:

Updated: