Here and there, python is everywhere!
Now, Learning Python Functional Programming is just a scroll down away! All you need to do is sit down and read everything carefully. Practice this and become a pro python developer in 2021.
Informative Fact: Python is now the third most popular programming language in the world, with a usage rate of 12.21% and in February 2020, more than 65,000 developers took Stackoverflow’s annual developer survey. Isn’t that amazing?
So, What is functional programming in Python?
A problem is decomposed into a series of functions in purely Functional programming.
Functions in python are called first-class citizens. Which means that their characteristics match. These first-class functions are handled as if it is a variable. When a first-class variable is passed to another function as a parameter, it can be stored in a data structure or can be returned from functions.
It is considered the opposite side of object-oriented programming language. Object-oriented languages work really well when you have a particularly fixed set of operations. Objects are small capsules that contain some internal state as well as a set of method calls that allow you to alter it, and programs are made up of the correct set of state changes.
The well-known functional programming language also has a Machine learning family including OCaml, Standard ML, etc. Python programs are written in the functional language style which doesn’t avoid all I/O or all assignments; instead of that, they have a functional-looking interface but use non-functional functionality internally.
Here, in Functional Programming in python, a good example you can take is: The implementation of a function will also use local variable assignments, but it will not change global variables or have any other side effects.
It may seem like Functional Languages can be tough to work with, But, why are you instance saying that learning the functional language would be easy?
Ikr! This is not as tough at all; All you have to do is follow every step in this article till the end.
Here, we are going to talk about how you can actually approach the functional programming paradigms in python. Firstly, you need to understand what Pure Functions are.
Let’s roll!!!
Pure Functions
It is just a function that does not have a side effect, and it returns the exact same thing every single time, you give it the same inputs. Thus, every time you call these functions with the same input, it always gives you the same output and it affects nothing else outside of it.
Now, this is a bit theoretical of what pure functions are. But, the easiest way to understand the pure function is to take some examples and to write some. Examples of pure functions are pow(), strlen(), sqrt(), etc.
There are many practical advantages of using functional programming paradigm, that includes the following:
Easy Debugging
Pure functions have very few factors affecting them which allows you to find the bugged section easily. Immutable data makes it easy to find the values set for the variable.
Modularity
Pure function is easier to reuse the same code in other contexts because functions do not depend on any external state or variable. This function will only complete a single operation at a time to confirm that the same function without importing external code.
Lazy Evaluation
In the functional programming paradigm; only evaluates computations when they are needed. This allows one to reuse results from previously saved runtime and computations.
Parallel Programming Paradigm
Since immutable variables minimize the amount of change within the program, the functional programming paradigm makes it easier to construct parallel programs. Each function just attempts to coordinate with user input, and the program’s state will largely remain the same!
Intensified Readability
Since each function’s action is immutable and separate from the program’s state, functional programs are simple to interpret. As a consequence, you can always guess what each function would do just by looking at its name!
Iterators
An iterator is a form of object that represents a stream of data and it will return each entity one by one. It must have a method called __next__(), which takes no arguments and it will always return the next element of the stream. If the stream does not contain any objects, __next__() should throw the StopIteration exception on the spot. It does not have to be finite; in fact, writing an iterator that generates an infinite stream of data is perfectly rational.
The functional style iter() takes an object and attempts to return an iterator that will contain the object’s contents or items, throwing a TypeError if the object does not really allow iteration. Iteration is supported by several functional data types, the most common of which are lists and dictionaries. If you can obtain an iterator for an object, it is said to be iterable.
Here, You can manually experiment with the iteration interface.
Iteration Interface
Input:
L = [3, 4, 5] it = iter(L) it it.__next__()
Output:
3
Iterators can convert into lists or tuples using the list() and tuple() functions. Object functions include:
Input:
L = [3, 4, 5] iterator = iter(L) t = tuple(iterator) t
Output:
>>> L [3, 4, 5]
The built-in max() and min() will take a single argument from the iterator and return the smallest or largest element. Iterators are supported by the “not in” and “in” and operators: X in iterator is true if X is found in the stream returned by the iterator. When the iterator is infinite, max() and min() will never return, and the “in” and “not in” operators will never return if the element X never appears in the stream.
Note: Keep in mind that in an iterator, one can only go forward; you can’t get the previous part, copy it, or reset the iterator. These additional capabilities are optional for iterator objects, but the iterator protocol only specifies the __next__() method. The iterator protocol only specifies the __next__() method, so these additional capabilities are optional for iterator objects. Functions can thus consume the iterator’s entire output, and if you need to do something different with the same stream, you must create a new iterator.
Different Data Types that Supports Iterator
Now that we’ve already gone through how tuples and lists support iterators. When you call iter() on a dictionary, you get an iterator that loops through the dictionary’s keys:
Input:
m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, ... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} for key in m: print(key, m[key])
Output:
Jan 1 Feb 2 Mar 3 Apr 4 May 5 Jun 6 Jul 7 Aug 8 Sep 9 Oct 10 Nov 11 Dec 12
Note: Starting with Python 3.9, a dictionary’s iteration order assures to be much like its insertion order. Previously, the behavior was ambiguous and could change depending on the implementation.
dict(iter(key, value))
Iter() always loops through the keys when applied to a dictionary, but dictionaries possess techniques that return other iterators. To iterate over values or key/value pairs, just use values() or items() methods to obtain a suitable iterator.
The iterator that will return a finite stream of (key, value) tuples can pass to the dict() function :
Input:
L = [('Pakistan', 'Islamabad'), ('India', 'Delhi'), ('US', 'Washington DC')] dict(iter(L))
Output:
>>> L [('Pakistan', 'Islamabad'), ('India', 'Delhi'), ('US', 'Washington DC')]
Generators
Generators are a subclass of functions that make writing iterators easier. It returns an iterator that iterates through a stream of values, while regular functions compute and return a value.
You’re probably familiar with how regular Python or C functions call work. By calling the functions, it gets a private namespace in which it stores its local variable. Here, the local variables demolish and the function reaches the return statement where that value returns to the caller.
A subsequent call to the same function generates a set of local variables and a new private namespace. What if the local variables do not discard at the time when the function is exited? What if you could later pick up where you left off with the function?
Generator function example:
Input:
seq1 = 'def' seq2 = (4, 5, 6) [(x, y) for x in seq1 for y in seq2]
Output:
[('d', 4), ('d', 5), ('d', 6), ('e', 4), ('e', 5), ('e', 6), ('f', 4), ('f', 5), ('f', 6)]
Function that contains the yield keyword is a generator function; Python’s bytecode compiler identifies it, which compiles the function differently as a result.
Moreover, when you call a generator function, it will return a generator that supports the iterator protocol rather than a single value.
Similar to a return statement, because when the yield statement runs, the generator will return the value of I. When a yield is achieved, the generator’s state of execution stops, and variables declared are retained. On the next call to the generator’s __next__() method, the functions will resume execution.
eg.
generate_ints() generator:
Input:
def generate_ints(N): for i in range(N): yield i
Output:
>>> gen = generate_ints(3) >>> gen >>> next(gen) 0 >>> next(gen) 1 >>> next(gen) 2 >>> next(gen) Traceback (most recent call last): File "<pyshell#11>", line 1, in next(gen) StopIteration </pyshell#11>
Here, writer generates ints() or a, b, c = generate ints can also be used here.
In a generator function, return value causes the __next__() method to raise StopIteration(value). The sequence of beliefs comes to an end when this happens, or when the bottom of the function calls reaches the maximum limit. Thus, the generator can no longer produce any more values.
You could achieve the effect of generators manually by composing your own class and stashing all of the generator’s local variables as instance variables.
Example:
Returning a list of integers, can complete when setting self.count to 0 and having the __next__() method increment and return self.count. Writing a corresponding class for a moderately complicated generator, on the other hand, can be much more difficult. Where this test suite comes in Python Library.
Here’s one generator that recursively implements an in-order tree traversal using generators.
def inorder(t): if t: for x in inorder(t.left): yield x yield t.label for x in inorder(t.right): yield x
The N-Queens problem, pinning N queens on a NxN chessboard so that no queen threatens another and the Knight’s Tour problem are solved in test generators.py to find a way that will take knight to each every square of an NxN chessboard without visiting any square twice.
Python Built-in Functions
In functional programming, python comes with numerous pre-defined functions that come with ready-to-use mode.
To use these functions, we don’t need to define them; we can simply name them. Built-in refers to this type of feature.
Python comes with a number of functions for functional programming. Here, we’ll go through a quick and easy overview of some of the functional functions that allow you to build fully functional programs.
Iteration is supports several built-in Python data types, the most popular of which are lists and dictionaries. Iterable entities are those that can give an iterator.
Here, we took many examples of built-in functions like abs, dir, len, zip, map, and filter.
Python abs()
To get the accurate value of the given number, abs() function comes in use. If the number is a complex number.
Get absolute value of a number using abs()
The syntax of abs() method is : abs(num)
Input:
integer = -15 print('Absolute value of -15 is:',abs(integer)) floating = -1.11 print('Absolute value of -1.11 is:', abs(floating))
Output:
Absolute value of -15 is: 15 Absolute value of -1.11 is: 1.11
Python dir()
The dir() will return all of the defined object’s properties and methods, but not their values.
This will return all properties and methods, including functional properties that are set to default for all objects.
Syntax: dir(object)
Display the content: dir()
Input:
class Person: name = "Coco" age = 22 country = "USA" print(dir(Person))
Output:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'country', 'name']
Python filter()
The filter() method generates an iterator from elements of an iterable that return true when a function is called. In more simple words, filter function method just filters the iterator that is already given, with the help of a function that will test each and every element in it to be true or not. It returns an iterator that is by default filtered.
Syntax: filter(function, iterable)
Input: Let’s write code.
ages = [30,5,8,22,10,32] def myFunc(x): if x<18: return False else: return True adults = filter(myFunc, ages) for x in adults: print(x)
Output:
There are mainly 2 types of filter() Parameters:
30 22 32
Functions: It is a function in python that tests whether the element of iterable returns either true or false.
If not, the function goes to the identity functions – which will return the value “False” if any elements are false.
These are going to get filtered; they can be, sets, tuples, containers, or lists of any iterator.
Python len()
The only work of len() function is to return the number of items in an object.
The len() function returns the number of characters in a string when the object is a string.
Syntax: len(object)
Input:
testList = [] print(testList, 'length is', len(testList)) testList = [1, 2, 3, 4, 6, 11] print(testList, 'length is', len(testList)) testTuple = (1, 5, 3, 4) print(testTuple, 'length is', len(testTuple)) testRange = range(100, 269) print('Length of', testRange, 'is', len(testRange))
Output:
[] length is 0 [1, 2, 3, 4, 6, 11] length is 6 (1, 5, 3, 4) length is 4 Length of range(100, 269) is 169
Python map()
The map() function returns a set of figures after applying a given function to each object of an iter (list, sets, tuple, etc.). It returns an iterator of map class in its return type.
Syntax: map(function, iterable, ……..)
Input:
# Return double of n def addition(n): return n + n # We double all numbers using map() numbers = (4, 5, 3, 2) result = map(addition, numbers) print(list(result))
Output:
[8, 10, 6, 4]
Python zip()
The zip() function takes an iterator, which can be zero or anything, aggregates it into a tuple, and returns it.
Syntax: zip(*iterables)
Input:
number_list = [1, 2, 3] str_list = ['Chocolate', 'Butterscoth', 'Mango'] # No iterables are passed result = zip() # Converting iterator to list result_list = list(result) print(result_list) # Two iterables are passed result = zip(number_list, str_list) # Converting iterator to set result_set = set(result) print(result_set)
Output:
[] {(3, 'Mango'), (2, 'Butterscoth'), (1, 'Chocolate')}
Let’s Wrap Up!
From all this, you can tell that python helps you to write in a functional style but it will not force you to do it. Writing in functional style will only enhance your code and make it more detailed documentation. Ultimately, it will make it more tread-safe. The biggest support of FP in python is the usage of the list comprehension, generators, and iterator. It also comes from itertools and functools import.
When you look at the whole scenario, it still lacks an important part of FP i.e. Tail Recursion and Pattern Matching. However, more work done on tail recursion will benefit and encourage developers to use recursion.
Wanna check out our Articles related to Python? Check out!
Why Python is Best fit for Big Data? and Why Python for a Startup is the Best Choice in 2021?