What Is Functional Programming? | ||||||||||
a pure function is a function whose output value follows solely from its input values without
any observable side effects in functional programming, a program consists primarily of the evaluation of pure functions computation proceeds by nested or composed function calls without changes to state or mutable data advantages of functional programming
|
||||||||||
Python Support Functional Programming | ||||||||||
to support functional programming, it's beneficial if a function in a given programming language
can do these two things
can assign a function to a variable >>> def func(): ... print("I am function func()!") ... >>> func() I am function func()! >>> another_name = func >>> another_name() I am function func()!function composition - a function taking another function as an argument >>> def inner(): ... print("I am function inner()!") ... >>> def outer(function): ... function() ... >>> outer(inner) I am function inner()!a function can also specify another function as its return value >>> def outer(): ... def inner(): ... print("I am function inner()!") ... # Function outer() returns function inner() ... return inner ... >>> function = outer() >>> function <function outer.<locals>.inner at 0x7f18bc85faf0> >>> function() I am function inner()! >>> outer()() I am function inner()!can call inner() indirectly through function with no argument can also call it indirectly using the return value from outer() without intermediate assignment outer()() is the same as outer(inner) |
||||||||||
Defining an Anonymous Function With lambda | ||||||||||
a lambda expression defines an anonymous function on the fly without having to give it a name syntax lambda <parameter_list>: <expression>
takes arguments as specified by <parameter_list> returns a value as indicated by <expression> >>> lambda s: s[::-1] <function <lambda> at 0x7fef8b452e18> >>> callable(lambda s: s[::-1]) Truecallable() returns True if the argument passed to it appears to be callable and False otherwise the parameter list consists of the single parameter s the expression s[::-1] is slicing syntax which returns the characters in s in reverse order this lambda expression defines a temporary, nameless function
can assign it to a variable and then call the function using that name >>> reverse = lambda s: s[::-1] >>> reverse("I am a string") 'gnirts a ma I'lambda function is no different than named function to use a lambda function it's not necessary to assign it to a variable >>> (lambda s: s[::-1])("I am a string") 'gnirts a ma I'more complex examples >>> (lambda x1, x2, x3: (x1 + x2 + x3) / 3)(9, 6, 6) 7.0 >>> (lambda x1, x2, x3: (x1 + x2 + x3) / 3)(1.4, 1.1, 0.5) 1.0readabilty would be better if a function with a descriptive name was defined advantage of using lambda expressions shows when using them for short andstraightforward logic >>> animals = ["ferret", "vole", "dog", "gecko"] >>> def reverse_len(s): ... return -len(s) ... >>> sorted(animals, key=reverse_len) ['ferret', 'gecko', 'vole', 'dog']same functionality using a lambda expression >>> animals = ["ferret", "vole", "dog", "gecko"] >>> sorted(animals, key=lambda s: -len(s)) ['ferret', 'gecko', 'vole', 'dog']generally a lambda expression will have a parameter list but it's not required can define a lambda function without parameters the return value is not dependent on any input parameters >>> forty_two_producer = lambda: 42 >>> forty_two_producer() 42the return value from a lambda expression can only be one single expression a lambda expression can't contain statements like assignment or return can't contain control structures such as for, while, if, else, or def can contain conditional expressions >>> (lambda x: "even" if x % 2 == 0 else "odd")(2) 'even' >>> (lambda x: "even" if x % 2 == 0 else "odd")(3) 'odd'implicit tuple packing doesn't work with an anonymous lambda function >>> (lambda x: x, x ** 2, x ** 3)(3) <stdin>:1: SyntaxWarning: 'tuple' object is not callable; ⮑ perhaps you missed a comma? Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not definedthe lambda expression above tries to return a tuple contain the numbers 3, 6 and 9 an explicitly return a tuple from a lambda function denote the tuple with parentheses can also return a list or a dictionary from a lambda function # tuple >>> (lambda x: (x, x ** 2, x ** 3))(3) (3, 9, 27) # list >>> (lambda x: [x, x ** 2, x ** 3])(3) [3, 9, 27] # dict >>> (lambda x: {1: x, 2: x ** 2, 3: x ** 3})(3) {1: 3, 2: 9, 3: 27}a lambda expression has its own local namespace its parameter names don't conflict with identical names in the global namespace a lambda expression can access variables in the global namespace but it can't modify them If you find a need to include a lambda expression in a f-string, then need to enclose it in explicit parentheses >>> print(f"- {lambda s: s[::-1]} -") File "<stdin>", line 1 print(f"- {lambda s: s[::-1]} -") ^^^^^^^^^ SyntaxError: f-string: lambda expressions are not allowed ⮑ without parentheses >>> print(f"- {(lambda s: s[::-1])} -") - <function <lambda> at 0x7f97b775fa60> - >>> print(f"- {(lambda s: s[::-1])('I am a string')} -") - gnirts a ma I - |
||||||||||
Applying a Function to an Iterable With map() | ||||||||||
with map() can apply a function to each element in an iterable in turn returns an iterator which yields the results a map() statement can often take the place of an explicit loop Calling map() With a Single Iterable
can call map() with one iterable or with many iterablesthe syntax for calling map() on a single iterable map(<f>, <iterable>)returns in iterator that yields the results of applying function >>> animals = ["cat", "dog", "hedgehog", "gecko"] >>> map(reverse, animals) <map object at 0x7fd3558cbef0>using a loop versus map() >>> iterator = map(reverse, animals) >>> for animal in iterator: ... print(animal) ... tac god gohegdeh okceg >>> iterator = map(reverse, animals) >>> list(iterator) ['tac', 'god', 'gohegdeh', 'okceg']the second snippet of code above can be combined as a single line >>> list(map(lambda s: s[::-1], ["cat", "dog", "hedgehog", "gecko"])) ['tac', 'god', 'gohegdeh', 'okceg']if the iterable contains items which aren't suitable for the specified function Python will raises an exception >>> list(map(lambda s: s[::-1], ["cat", "dog", 3.14159, "gecko"])) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <lambda> TypeError: 'float' object is not subscriptable Calling map() With Multiple Iterables
can use map() when passing more than one iterable after the function argumentsyntax map(<f>, <iterable₁>, <iterable₂>, ..., <iterableₙ>)map() applies <f> to the elements in each <iterablei> in parallel returns an iterator which yields the results the number of <iterable₁> arguments specified to map() must match the number of arguments that <f> expects <f> acts on the first item of each <iterablei> that result becomes the first item that the return iterator yields then <f> acts on the second item in each <iterablei> which becomes the second yielded item etc. >>> def add_three(a, b, c): ... return a + b + c ... >>> list(map(add_three, [1, 2, 3], [10, 20, 30], [100, 200, 300])) [111, 222, 333]above uses the n-th element of each iterable as arguments to each call to add_three() add_three() is short enough it can be used a the first argument to map() >>> list( ... map( ... lambda a, b, c: a + b + c, ... [1, 2, 3, 4], ... [10, 20, 30, 40], ... [100, 200, 300, 400], ... ) ... ) [111, 222, 333, 444] |
||||||||||
Selecting Elements From an Iterable With filter() | ||||||||||
filter() allows selecting/filtering items from an iterable based on evaluation of
the given function syntax filter(<f>, <iterable>)filter() applies the function to each element of the iterable returns an iterator that yields all items for which <f> is truthy filters out all items for which <f> is falsy >>> def greater_than_100(x): ... return x > 100 ... >>> list(filter(greater_than_100, [1, 111, 2, 222, 3, 333])) [111, 222, 333]below greater_than_100(x) is truthy if x > 100 >>> def greater_than_100(x): ... return x > 100 ... >>> list(filter(greater_than_100, [1, 111, 2, 222, 3, 333])) [111, 222, 333]again can use lambda function directly >>> list(filter(lambda x: x > 100, [1, 111, 2, 222, 3, 333])) [111, 222, 333]example using range() >>> list(range(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> def is_even(x): ... return x % 2 == 0 ... >>> list(filter(is_even, range(10))) [0, 2, 4, 6, 8] >>> list(filter(lambda x: x % 2 == 0, range(10))) [0, 2, 4, 6, 8]can also use filter() with other data types filter a list of animals so that only uppercase values remain >>> animals = ["cat", "Cat", "CAT", "dog", "Dog", "DOG", "emu", "Emu", "EMU"] >>> def all_caps(s): ... return s.isupper() ... >>> list(filter(all_caps, animals)) ['CAT', 'DOG', 'EMU'] >>> list(filter(lambda s: s.isupper(), animals)) ['CAT', 'DOG', 'EMU'] |
||||||||||
Reducing an Iterable to a Single Value With reduce() | ||||||||||
reduce() applies a function to the items in an iterable two at a time,
progressively combining them to produce a single result reduce() is no longer a built-in function it's still available for import from a standard-library module called functools from functools import reduce Calling reduce() With Two Arguments
reduce() call takes one function and one iterable
>>> def f(x, y): ... return x + y ... >>> from functools import reduce >>> reduce(f, [1, 2, 3, 4, 5]) 15this is a rather roundabout way of summing the numbers in the list the sum() function returns the sum of the numeric values in an iterable >>> sum([1, 2, 3, 4, 5]) 15can concatenate strings in a list >>> reduce(f, ["cat", "dog", "hedgehog", "gecko"]) 'catdoghedgehoggecko'more Pythonic way >>> "".join(["cat", "dog", "hedgehog", "gecko"]) 'catdoghedgehoggecko'the factorial of the positive integer n is defined as n! = 1 x 2 x ... x ncan implement a factorial function using reduce() and range() >>> def multiply(x, y): ... return x * y ... >>> from functools import reduce >>> def factorial_with_reduce(n): ... return reduce(multiply, range(1, n + 1)) ... >>> factorial_with_reduce(4) # 1 * 2 * 3 * 4 24 >>> factorial_with_reduce(6) # 1 * 2 * 3 * 4 * 5 * 6 720simpler to use factorial() function >>> from math import factorial >>> factorial(4) 24 >>> factorial(6) 720find the maximum value in a list using built-in max() and reduce() functions >>> max([23, 49, 6, 32]) 49 >>> def greater(x, y): ... return x if x > y else y ... >>> from functools import reduce >>> reduce(greater, [23, 49, 6, 32]) 49in each of the above examples the function passed to reduce() is a one-line function in each case could have used a lambda function instead >>> from functools import reduce >>> reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]) 15 >>> reduce(lambda x, y: x + y, ["cat", "dog", "hedgehog", "gecko"]) 'catdoghedgehoggecko' >>> def factorial_with_reduce(n): ... return reduce(lambda x, y: x * y, range(1, n + 1)) ... >>> factorial_with_reduce(4) 24 >>> factorial_with_reduce(6) 720 >>> reduce((lambda x, y: x if x > y else y), [23, 49, 6, 32]) 49 Calling reduce() With an Initial Value
reduce() can be called with an additional arg which specifies an initial value for the reduction sequence
reduce(<f>, <iterable>, <initializer>)<initializer> specifies an initial value for the combination in the first call to <f> the arguments are <initializer> and the first element of <iterable> that result is then combined with the second element of <iterable> etc. >>> def f(x, y): ... return x + y ... >>> from functools import reduce >>> reduce(f, [1, 2, 3, 4, 5], 100) # (100 + 1 + 2 + 3 + 4 + 5) 115 >>> # Using lambda: >>> reduce(lambda x, y: x + y, [1, 2, 3, 4, 5], 100) 115can do the same thing using the sum() function >>> sum([1, 2, 3, 4, 5], start=100) 115 |