Too much redundant code indicates poor programming style. So how to avoid redundant code?
Functions make source code more general. Suppose you want to calculate the square root of 145. You could either calculate it for the specific value 145 or define a function that calculates the square root for any value x.
We say that a function encapsulates a sequence of program instructions. The ideal function solves a single semantic high-level goal. For instance, you can encapsulate a complex task into a function, such as searching the web for specific keywords. In this way, the complex task becomes a simple one-liner: calling the function. Functions enable others to reuse your code and allow you to reuse other people's code. You are standing on the shoulders of giants.
You can define a function with the keyword def, followed by a name and the arguments of the function. The Python interpreter maintains a symbol table that stores all function definitions, i.e., mappings from function names to function objects. In this way, the interpreter can relate each occurrence of the function name to the defined function object. Just remember: a single function object can have zero, one, or even many names.
In the puzzle, we assign the function object to the name func and then reassign it to the new name f. We then use both the names in the code. Upon the function call, the Python interpreter will find the function in the symbol table and execute it. This can make your code more readable when calling the same function in different contexts.