AMZ DIGICOM

Digital Communication

AMZ DIGICOM

Digital Communication

Python and Functional Programming

PARTAGEZ

If the famous programming language Python is rather known for object-oriented programming, it also lends itself to functional programming. Find out what functions are available and how to use them.

What characterizes functional programming?

The term « functional programming » refers to a type of programming that uses functions as code base unit. There is a gradation between purely functional languages ​​(like Haskell or Lisp) and languages ​​that are based on several paradigms, like Python. The border between languages ​​that support functional programming or not is therefore quite fluid.

For a language to support functional programming, it must treat functions as first-class objects. first-class citizens). This is the case in Python, where functions become objects in the same way as character strings, numbers and lists. These functions can serve as parameters to other functions, or be returned as return values ​​from other functions.

Functional programming is declarative

So-called declarative programming consists of describing a problem and let the programming environment find the solution. In contrast, so-called imperative programming consists in describing step by step the path to follow towards the solution. Functional programming forms part of the so-called declarative approach, and Python allows you to follow both paradigms.

Let’s take a concrete example in Python. Starting from a list of numbers, we seek to calculate their corresponding squares. Here is the method according to the imperative approach :

# Calculate squares from list of numbers
def squared(nums):
  # Start with empty list
  squares = []
  # Process each number individually
  for num in nums:
    squares.append(num ** 2)
  return squares

Python

With the List Understandings, Python takes a declarative approach that combines well with functional techniques. It is possible to create the list of squares without an explicit loop. The resulting code is significantly lighter and without indentations:

# Numbers 0–9
nums = range(10)
# Calculate squares using list expression
squares = [num ** 2 for num in nums]

# Show that both methods give the same result
assert squares == squared(nums)

Python

Primacy of pure functions over procedures

A PureFunctionor “pure function”, can be compared to mathematical functions basic. This term refers to a function that has the following characteristics:

• The function arrives at the same result for the same arguments;

• The function only has access to its arguments;

• The function does not trigger any side effects.

In short, these properties mean that calling a pure function does not cause any change in the system environment. The classic square function example f(x) = x * x can be easily implemented as a pure function in Python:

def f(x):
  return x * x

# let’s test
assert f(9) == 81

Python

Procedures, widely used in old languages ​​such as Pascal or Basic, are opposed to pure functions. Like the function, the procedure is a block of code with a name, which can be called several times. With one difference though: a procedure returns no value. Instead, the procedure directly accesses non-local variables to modify them as needed.

In C and Java language, the procedures are implemented in the form of a function with the void return type. With Python, functions always return a value : if there is no return statement, the program returns the special value “None”. When we talk about a procedure in Python, we are talking about a function without a return statement.

Here are some examples of functions pure and impure in Python. The following function is impure because it returns a different result on each call:

# Function without arguments
def get_date():
  from datetime import datetime
  return datetime.now()

Python

The following procedure is also impure because it accesses data defined outside the function:

# Function using non-local value
name="John"
def greetings_from_outside():
  return(f"Greetings from {name}")

Python

The following function is impure because it modifies a mutable argument when called and therefore affects the system environment:

# Function modifying argument
def greetings_from(person):
  print(f"Greetings from {person['name']}")
  # Changing 'person' defined somewhere else
  person['greeted'] = True
  return person

# Let’s test
person = {'name': "John"}
# Prints 'John'
greetings_from(person)
# Data was changed from inside function
assert person['greeted']

Python

The following function is pure because it gives the same result for the same argument without side effects:

# Pure function
def squared(num):
  return num * num

Python

Recursion as an alternative to iteration

In functional programming, recursion is the equivalent of iteration. A recursive function calls itself repeatedly until the result is obtained. For this to work without the function looping infinitely, two conditions must be met:

  1. The recursion must have a stopping condition;
  2. Recursive execution of the function should lead to reducing the problem.

Python supports recursive functions. Here is a famous example of the calculation of the Fibonacci sequence by a so-called “naive” approachinefficient for large values ​​of n but which can be optimized by caching:

def fib(n):
  if n == 0 or n == 1:
    return n
  else:
    return fib(n - 2) + fib(n - 1)

Python

Python and Functional Programming: do they go well together?

Python is a multi-paradigm language, i.e. writing code can follow different programming paradigms. In addition to functional programming, it is also possible to use object-oriented programming in Python without problems.

Python has many tools dedicated to functional programming. However, unlike purely functional languages ​​like Haskell, their scope remains limited. The degree of functional programming of a Python program depends in the first place on the person in charge of the development. Here is an overview of the main functional features of Python.

In Python, functions are first-class objects

Python’s rule: Everything is an object (translation: everything is an object). This is also the case for functions. They can be used anywhere in the language, at least where objects are allowed. Let’s look at a concrete example: programming a calculator that supports various mathematical operations.

First, here is the imperative approach. This uses the classic tools of structured programming, such as conditional branches and assignment statements:

def calculate(a, b, op='+'):
  if op == '+':
    result = a + b
  elif op == '-':
    result = a - b
  elif op == '*':
    result = a * b
  elif op == '/':
    result = a / b
  return result

Python

Now consider a declarative approach to solve this same problem. Instead of the if branch, we represent operations as a Python dict. The operation symbols are keys that refer to the corresponding function objects, which we import from the operator module. The resulting code is clearer and does not require branching:

def calculate(a, b, op='+'):
  # Import operator functions
  import operator
  # Map operation symbols to functions
  operations = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.truediv,
  }
  # Choose operation to carry out
  operation = operations[op]
  # Run operation and return results
  return operation(a, b)

Python

All that remains is to test the declarative calculate function. The assert statements show that the code is working:

# Let’s test
a, b = 42, 51
assert calculate(a, b, '+') == a + b
assert calculate(a, b, '-') == a - b
assert calculate(a, b, '*') == a * b
assert calculate(a, b, '/') == a / b

Python

In Python, lambdas are anonymous functions

If the functions under Python are defined by the keyword def, the language also recognizes “lambdas”. These are short, anonymous functions that define an expression with parameters. Lambdas can be used anywhere a function is expected, or bound to a name by assignment:

squared = lambda x: x * x
assert squared(9) == 81

Python

Using lambdas we can improve the calculate function. Instead of hard-coding the operations available in the function, we pass a dict with lambda functions as values. This makes it easy to add new operations later:

def calculate(a, b, op, ops={}):
  # Get operation from dict and define noop for non-existing key
  operation = ops.get(op, lambda a, b: None)
  return operation(a, b)

# Define operations
operations = {
  '+': lambda a, b: a + b,
  '-': lambda a, b: a - b,
}

# Let’s test
a, b, = 42, 51
assert calculate(a, b, '+', operations) == a + b
assert calculate(a, b, '-', operations) == a - b
# Non-existing key handled gracefully
assert calculate(a, b, '**', operations) == None

# Add a new operation
operations[‘**’] = lambda a, b: a ** b
assert calculate(a, b, '**', operations) == a ** b

Python

Higher Order Function in Python

Lambdas are used a lot for higher order functions like map() and filter(). Thus, the elements of an iterable can be transformed without going through loops. The map() function takes a function and an iterable as parameters, and executes the function for each item in the iterable. Let’s see how it works with square number generation:

nums = [3, 5, 7]
squares = map(lambda x: x ** 2, nums)
assert list(squares) == [9, 25, 49]

Python

Higher-order functions higher-order functions) are functions that are based on function parameters or that return a function as a value.

The filter() function allows to filter elements of an iterable. Let’s go back to the example to generate only the even squares:

nums = [1, 2, 3, 4]
squares = list(map(lambda num: num ** 2, nums))
even_squares = filter(lambda square: square % 2 == 0, squares)
assert list(even_squares) == [4, 16]

Python

Iterables, comprehensions and generators

Iterables are a key concept in Python: they are a collection abstraction whose elements can be displayed individually. These can be character strings (thongs), tuples, lists, and dicts that all follow the same rules. For example, the len() function allows to obtain the size of an iterable:

name="Walter White"
assert len(name) == 12
people = ['Jim', 'Jack', 'John']
assert len(people) == 3

Python

Built from iterables, we can also use the understandings. These lend themselves well to functional programming and have largely replaced the use of lambdas with map() and filter().

# List comprehension to create first ten squares
squares = [num ** 2 for num in range(10)]

Python

As with purely functional languages, Python offers an approach lazy evaluation with generators. This means that data generation only takes place at the time of access, which in particular saves a lot of memory. Here is a generator expression that calculates each number squared when accessed:

# Generator expression to create first ten squares
squares = (num ** 2 for num in range(10))

Python

The yield statement allows you to manage lazy evaluations in Python. Here is a function that returns positive numbers up to a given limit:

def N(limit):
  n = 1
  while n <= limit:
    yield n
    n += 1

Python

Alternatives to Python for Functional Programming

Very popular, functional programming has established itself as the most important countercurrent to object-oriented programming. Combining immutable (“*immutable*”) data structures with pure functions yields code easy to parallelize. Functional programming is thus very interesting for transforming data into data pipelines.

Among the most popular are strongly typed purely functional languages ​​such as Haskell or Clojure, the dialect of Lisp. JavaScript is also considered a functional language in essence. TypeScript provides a modern alternative with strong typing.

Want to work online with Python? Take advantage of top-level web hosting for your project!

Télécharger notre livre blanc

Comment construire une stratégie de marketing digital ?

Le guide indispensable pour promouvoir votre marque en ligne

En savoir plus

Souhaitez vous Booster votre Business?

écrivez-nous et restez en contact