Outline
Understanding the functional programming features of Python can help you to quickly add features and functionality to your module or application. Remembering that Python supports the concept of First Class functions will help you align your thinking for this section. A function is an instance of the Object Type. You can store the function in a variable, pass the function as a parameter to another function, return the function from a function, and you can store them in data structures such as hash tables, lists, etc.
Let's create some simple Python files and play around with some of the functional programming features. You can use the terminal or your chosen editor. I'm using Visual Studio Code with the Python extension installed. Using Shift+Cmd+p
and typing Python: Select Interpreter
, I chose my working project version of Python 3.7.2
. Then created an integrated terminal by hitting Shift+Cmd+p
and typing Terminal: Create new Integrated terminal
.
I've created a file called functions.py
and placed the following code in it to show that we can pass a function as an object in a parameter:
def add(x, y):
return x + y
def subtract(x, y):
return x - y
def do_binary_op(op, x, y):
return op(x, y)
Then, in the integrated terminal prompt, I enter python
Using the Python Interpreter:
$ python
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from functions import add, subtract, do_binary_op
>>> do_binary_op(subtract, 10, 3)
7
>>> do_binary_op(add, 10, 5)
15
Functions in Python can be referred to by name and get executed only when you call them with parenthesis.
Note: You can import the functions using *
like: from functions import *
. This can be handy when you are playing around with the interpreter but NOT recommended for production bound code. Using it would make it impossible to control changes to the modules you are importing and could have unintended results.
Functions are objects
There are a couple of ways that we can these assignments of functions into variables.
def shout(text):
return text.upper()
print(shout('Hello'))
yell = shout
print(yell('Hello'))
A decorator is relatively complicated the first time you look at them. They, however, are a highly useful feature of Python. It is important to remember that functions are objects that can be assigned to variables and passed to and from other functions. Add this to the ability to define a child function within a function (a closure) and capture the parent function's local state is what can make them so powerful.
I've created a file called decorators.py
and placed the following code from the write-up in it:
def bread(func):
def wrapper():
print("</''''''\>")
func()
print("<\______/>")
return wrapper
def ingredients(func):
def wrapper():
print('#tomatoes#')
func()
print('~salad~')
return wrapper
@bread
@ingredients
def sandwich(food='--ham--'):
print(food)
Now from your terminal window, import decorators
and call the sandwich()
function.
$ python
>>> from decorators import *
>>> sandwich()
</''''''\>
#tomatoes#
--ham--
~salad~
<\______/>
>>>
You can decorate the sandwich
function with the bread
and ingredients
by using @bread
then @ingredients
above it. This would be the same as sandwich = bread(ingredients(sandwich))
. We are trying to get away from those types of assignments by using decorators. The order of the decorators is important as well. Play around with switching the order and see how your sandwich ends up.
Functions
Functions are declared with the def
keyword. Arguments follow the function name. Default values are specified using an =
after the argument. They allow us to group related statements to perform a specific task. Functions allow our code to be reusable and helps to manage our code.
Function names follow the same naming convention as variable names. Function names should be lowercase, with words separated by underscores.
Using the argument name when calling a function allows you to provide them in any order.
def create_super_hero(superName, hiddenIdentity, alignment):
return (
f'Creating a new character named {superName}, '
f'whos identity will be {hiddenIdentity} in the public world. '
f'This superhero will be {alignment}.'
)
print(create_super_hero(
alignment='Evil',
superName='Bane',
hiddenIdentity='Secret Identity'
))
Functions can contain a return statement which is used to exit the function and return to the calling code's next statement.
*args and **kwargs
I had a hard time figuring out what these were the first time I saw them. *args
and *kwargs
can be used in function definitions which allow you to pass any number of arguments to a function. *args
is used when you are passing parameters without keywords (non-dictionary). It's a Python convention to use these names. You can use whatever variable name you want. The important piece is the *
. You can use *foo, **bar
if you want.
def superHeroTest(firstArg, *args):
print(f'this is the first argument value: {firstArg}')
for arg in args:
print(f'argument value in *args: {arg}')
superHeroTest('Batman', 'Superman', 'Green Lantern', 'James Gordon')
# Output
# this is the first argument value: Batman
# argument value in *args: Superman
# argument value in *args: Green Lantern
# argument value in *args: James Gordon
If you want to allow named arguments to be passed to your function, use **kwargs
. Again, you get the same functionality as *args
, allowing you to pass any number of keyworded arguments. For example:
def superHeroTest2(**kwargs):
for key, value in kwargs.items():
print(f'{key} = {value}')
superHeroTest2(name='Batman', favColor='really really dark grey')
# Output
# name = Batman
# favColor = really really dark grey