Order Matters in Positional Arguments
Mixing the order of positional parameters in a function call might provide strange consequences. See the above code with the wrong positional argument to understand.
In the preceding code example, we first call the pet's name and then its type, resulting in an odd output where the pet type receives the pet's name and the pet name gets the animal kind. We send the name as a first argument to the aforesaid function, and the pet name as a second argument saves the animal_type.
Keyword Arguments
A keyword argument is a name-value pair sent to a function. This approach immediately associates the name and value within the parameter, so passing it in the function is clear. It defines the purpose of each value in the function call and allows us to ignore the order of the parameters. Here's the same Python code, but we call the method with a keyword parameter. The function definition is unchanged, but the call is modified. In the code sample, we identify which parameter each argument belongs to when calling the function. Python assigns "Cat" to the "animal_type" argument and "Tom" to the "pet_name" field. Thus, the report properly indicates we have a cat named Tom. This method offers exact parameter assignment, enabling correct function execution.
The sequence of arguments doesn't matter because Python places each value dependent on its parameters. Specifying arguments in the function call lets Python appropriately assign values to their places. Python binds values to arguments, thus the following two function calls work similarly. Python functions are more versatile and usable due to parameter order flexibility:
Note: Keyword arguments require that the function definition's parameter names match the function calls. This guarantees Python appropriately links each argument with its parameter, preventing function execution issues.
Default Values
Python routines may need default parameter values. Python uses an argument's value in a function call. Python uses the parameter's default value if no argument is given. By setting default parameter values, we simplify function calls and clarify function usage.
It's beneficial when we call a function repeatedly and the parameters are usually the same. If we have to use the previous example again and the “animal_type” parameter is usually “Dog” then we can set “animal_type = Dog” default so we just need to give the pet's name parameter when inserting a dog's information. Non-default parameters come before default parameters, hence pre-defined parameters appear after undefined functions. Use Python to examine this:
If we want to describe other than a dog, we could use a function call like this:
pets(pet_name="harry", animal_type='Cat')
Note: Default values must be placed after non-default options. This ordering helps Python parse positional arguments without ambiguity.
Equivalent Function Calls
Python functions can use positional, keyword, and default parameters. The function can be called in several ways due to its flexibility. Consider pets(), which has one default value:
def pets(pet_name, animal_type="Dog"):
According to the definition, we must always pass a value to pets(), which might be positional or keyword. If we need to describe the animal kind (not Dog), we must provide the “animal_type” parameter to the call, which can be positional or keyword.
Below is the code with different function calls that would work for the above function:
Note: Personal preference determines calling style. We care most about function calls producing the intended result. Thus, we should choose the most simple and understandable calling style.
Avoiding Argument Errors
A typical problem when working with functions is mismatched parameters. Unmatched argument errors arise when we provide a function fewer or more parameters than it needs. Let's say we call a function without an argument yet define it with two arguments.
The error notice suggests checking our function call for errors on line 4. The last line of error tells us the error type is TypeError and describes the problem with our software. The notification says pets() needs pet_name and animal_type.
Return Values and Return Statements
Len() calculates and returns the parameter length as an integer. The len() method returns 5 since "Hello" comprises five characters. Function returns are known as return values.
We may provide a function's return value using a return statement. Specific restrictions govern the return statement: -
- The return keyword
- The value or expression that the function should return
When combined with an expression, a return statement outputs the expression. This suggests that the function return value is the expression output. To demonstrate, let's create a function that creates a unique string from numerical input. Python imports random first. The method `getAnswer()` is then defined. Since its code block is declared, not called, it's not run immediately. In the third-to-last line, the `random.randint()` function is called with inputs 1 and 9, and its result is placed in `r`. This variable contains a random integer from 1 to 9.
The software gives "r" as input to the `getAnswer()` method. The function stores "r" in "answerNumber". The function then returns one of many string values dependent on "answerNumber". Later in the program, the `getAnswer()` method is called again, storing the string in the "fortune" variable. Finally, the "fortune" variable is sent to the `print()` method, showing the outcome using the specified code.
As seen below, we may reduce the last three lines of the code into one line by giving return values as parameters to other function calls.
print(getAnswer(random.randint(1,9)))
Making an Argument Optional
Sometimes an argument must be optional to provide users the option to enter more information in the function. When filling out an online form, we may choose our first, middle, and last names. Such middle names may be optional, so users might choose to use them. We can do this by making the parameter optional with default values.
The musician_name() method takes first, last, and middle names. Starting with a necessary middle name, the function is defined as follows:
This function accepts three strings as arguments and combines them to produce the output, but only if all three parameters are provided.
Without a middle name parameter, the above code will fail as the middle name is not always required. The function can work without a middle name by setting an empty default value. We allow users to omit the middle_name option unless they give a value by changing its default value to an empty string and placing it at the end of the list. Here's the Python implementation:
This function accepts three strings as arguments and combines them to produce the output, but only if all three parameters are provided.
Without a middle name parameter, the above code will fail as the middle name is not always required. The function can work without a middle name by setting an empty default value. We allow users to omit the middle_name option unless they give a value by changing its default value to an empty string and placing it at the end of the list. Here's the Python implementation.
Python Classmethod() Function
Python classmethod() is related to the class not to the object. they can be called both class and object. We can call this method via a class or an object.
Recursion in Python
The meaning of recursion
is that defining something in terms of itself. In other words, we can say that recursion
in Python means that a function calls itself indirectly or directly. Below is
the very basic code on recursion.
In the above code ‘factorial’
function is used to calculate the factorial of a non-negative value ‘n’. it uses
recursion by calling itself with a smaller argument (‘n-1’) unit the base case
(n==0’) is not met. When it meets the condition n=0 then it returns ‘1’. Then,
it unwinds the recursive calls, multiplying each result by ‘n’ until it reaches
the original call.
Returning a Dictionary
A function can return different values to meet program demands. Includes simple data types and complex data structures like lists and dictionaries. Python methods that accept name components and create a dictionary describing an individual is an example:
The "build_dict" function stores first and last names in a dictionary allocated to "person". First and last names are recorded in this dictionary under the "First" and "Last" keys. Use "return" to return the person's details dictionary to end the function. Printing the returned dictionary shows the original information.
Expanding the "build_dict" function to include age is easy. To accommodate this additional data, we may add an optional age parameter to the function specification. Age values are added to the dictionary under the relevant key. This change lets the function keep the person's age and first and last names. The function smoothly accommodates no age value, ensuring data structure flexibility.
The function specification includes an optional argument "age" with an empty default value. The person dictionary stores the "age" argument value when the function is called. The function always stores a person's name and allows for extra information, allowing for the preservation of different personal facts.
Using a Function with a while Loop
Python components we've covered can merge functions. For example, we may create a while loop method to greet users. We may first welcome people by first and last names: The example shows a simpler `build_dict()` code without middle names. In the code, the while loop requests the user's first and last names consecutively.
No quit condition is given in our while loop, which is flawed. When asking multiple inputs, where should a quit condition go? Every prompt should provide a way to stop so users may do so easily. Break statements make the loop exiting at any prompt easy. Look at this code:
The code includes a message to help people leave the loop. If the user enters 'q' at either prompt, the loop breaks out, allowing the application to run until the user quits by entering 'q' at any time during name entry.
Passing a List
Programmers often pass lists to functions, whether they include names, numbers, or dictionaries. A function has direct access to list items when supplied a list. Create a list-efficient function to demonstrate this. We obtain a list of users and print individualized greetings for everyone. Despite its simplicity, this function shows how lists may be modified within functions. Let's analyze Python code: The code creates a function called "greetings" that takes a list of names, "names". The method prints a personalized welcome for each user by looping over the list's names. To test the function, the "greetings()" method receives a list of usernames called "usernames" as input.
Modifying a List in a Function
We may change a list inside a function, knowing it will survive after execution. We may apply modifications directly to the list supplied as an input to the function, which helps us work effectively while managing large datasets.
Suppose we have to print a 3D model from the user's design. We keep a list of designs to print and move them to another list after finishing. Below are two codes without functions and one with functions. Using a function makes our code easier to understand and execute anytime we want.
The software starts with a collection of print designs. After printing designs, an empty list called completed_designs is formed. While loop iterates until unprinted_designs still have designs. In each iteration, a design is deleted from unprinted_designs, indicating printing. The current printed design is shown and added to the completed_designs list. After printing, the application displays a list of completed designs.
Refactoring the code into two task-specific functions improves efficiency. The first function prints designs, while the second summarizes them. This simplifies and improves code readability without changing functionality:
The list of designs to print and the list of completed models are sent to "printing_models()". It iterates through the list, shifting designs from unprinted to completed models to simulate printing. The single-parameter "showing_completed_models()" method iterates through the list of finished models to output model names. This job division into two functions improves code modularity and clarity.
The result is unchanged, but the code is more organized. Splitting the code into two functions, each with unique duties clarifies the program's primary functionality. Modular code is easier to understand and maintain.
The software initializes a list of unprinted drawings and an empty list of produced models. With important tasks outsourced to distinct functions, the main code body calls them and passes parameters. The list of unprinted designs and completed models are passed to printing_models() initially. Each design's printing is simulated using this function. After that, the showing_completed_models() method displays the successfully printed models using the list of finished models. Descriptive function names improve code readability and comprehension without comments.
Programming with functions is far more extensible and maintainable. If we need to print more designs, we use "printing_models()" again. If any printing process adjustments are needed, we can modify them once in the function and have them instantly applied elsewhere. This is far more efficient than updating the code at various points in the program.
The code example above shows how each function is dedicated to a purpose. Here, the first function prints each design, and the second displays the finished models. This method is better than integrating both chores into one. If a function performs numerous jobs, it's best to split it into two or more functions that focus on different parts of the process. Following this method organizes and simplifies code. Additionally, invoking one function from another can help divide complicated operations into simple chunks.
Preventing a Function from Modifying a List
The original list may need to be protected from function changes in some cases. As an example, we may want to save the list of unprinted designs after printing. Transferring all design names to the `unprinted_designs` list within the software has left the original list empty, leaving just the updated version. We can provide the function a copy of the list to avoid data loss. This guarantees that function changes affect the duplicated list, not the original. We can replicate a list for a function with one line of code:
function_name(list_name[:])
The slice notation [:] lets us copy the list and give it to the function without changing it. In the example, we may execute printing_models() to prevent depleting the unprinted designs list:
printing_models(unprinted_design[:]. completed_models)
Since it accesses all unprinted design names, printing_models() works. Instead of the original unprinted_designs list, it uses a copy. Thus, the function does not change the unprinted designs list, but the completed_models list will still include the names of the printed models.
Except in certain cases, functions should get the original list. Although sending a duplicate can retain the list's contents, especially when adjustments are done within the function, it's more resource-efficient, especially with big lists, to let a function operate directly with the existing list. This method reduces the computational expense of duplicating the list, optimizing program speed.
Passing an Arbitrary Number of Arguments
Sometimes, while programming functions, the number of parameters is unknown. Fortunately, Python allows functions to take any amount of parameters from the call. This feature lets us gracefully handle varied argument counts without parameter declarations.
This notion can be best understood with an example. Imagine a pizza-assembling function. A person may pick how many toppings to put on a pizza. *toppings is a function argument in the code below. This parameter is unique since it can receive any function call argument. See the Python code:
The asterisk in function parameter *toppings tells Python to construct an empty tuple called "toppings" and collect any values. According to the output, Python can handle function calls with one or more values using the print statement in the function body. Python treats both call types equally. Python packs a tuple even if the function receives one value.
The above software may be modified to loop over the toppings and describe the pizza requested:
Even though the function responds appropriately, whether it receives one value or three values as we can see in the above result.
Mixing Positional and Arbitrary Arguments
If a function may receive many parameters, the parameter that accepts an arbitrary number must be in the last definition. Python matches positional and keyword arguments first, then collects any leftover arguments in the last parameter.
For instance, update the above example. The pizza size parameter must come before the toppings parameter in this new example.
Python stores the first value in the argument “size” in the function definition above. All subsequent values are kept in tuple tops. The function calls start with a size parameter and then as many toppings as needed.
Each pizza has a size and many toppings, and the information is printed in the correct order, starting with the size and then the toppings.
Using Arbitrary Keyword Arguments
Sometimes we wish to take an arbitrary amount of arguments but don't know what the function will get up front. The calling statements let us build a function that accepts numerous key-value pairs. One example is generating user profiles, when we know we'll obtain user information but don't know what. For clarity, let's create a function called building_profile() that receives a first name, last name, and an arbitrary number of keyword parameters:
Above, building_profile() demands a first and last name and then lets us or the user give in as many name-value combinations as we wish. The above method uses double asterisks before **user_info** in the last argument. Python creates an empty dictionary named user_info and packs name-value pairs into it. In the function, we may access user_info name-value pairs like any other dictionary.
In building_profile(), we create an empty dictionary named profile to store the user's profile. After constructing the dictionary, we included the user's first and last names (which are the following two lines after declaring the dictionary) because we always receive them. For each key-value pair in user_info, the for loop adds it to the profile dictionary. Lastly, we return the profile dictionary to the function call line.
We run building_profile() with the first name ‘albert’, last name ‘einstein’, and key-value pairs location=’princepton’ and field=’physics’. User_profile is saved and printed with the returned profile.
Location and study area are included in the dictionary along with the user's first and last names. No matter how many key-value combinations are given, the function will work.
We can mix positional, keyword, and arbitrary values in our functions. Knowing these argument types is useful since we'll see them often while reading other people's code. Practice is needed to master the many types and know when to employ them. Use the simplest, most effective technique for now. As we progress, we'll learn to always take the best practical action.
Local and Global Scope
Calling a function assigns parameters and variables to that function exclusively, or in its local scope. The global scope includes variables assigned outside all declared functions. Variables defined inside the local scope are termed local variables, whereas those declared outside all functions are called global variables. Local and global variables cannot coexist.
Think about scopes as variable containers. All scope values are lost if the scope is deleted. Only one global scope was generated when our program began. Our application deleted its global scope and forgot all variables after execution. If they aren't removed, their values will be kept from the previous time we ran it, which might impact the program or produce an error.
Calling a function creates the local scope. Variables declared in this function are strictly local. After the function returns, the local scope is deleted and these variables are lost. If we call the function again, the local variables don't remember or don't know their prior value.
Scopes matter for some reasons:
- Code in the global can access global variables.
- However, a local scope can access global variables.
- Code in a function’s local scope cannot use variables in any other local scope.
- We can use the same name for different variables if they are in different scopes. That is, there can be a local variable named x and a global variable also named x.
Python has several scopes instead of global variables. The function can only interact with the rest of the program through its arguments and return values when variables are modified by the code in a specific call. It reduces the number of code lines that might fail. If our application exclusively uses global variables and we identify a fault that causes a variable to have an incorrect value, it would be difficult to trace where it was set. This variable value may be set anywhere in our program, which may include hundreds or thousands of lines. Local variables can create errors, but we can easily discover the code in that function that sets the variable incorrectly.
Global variables are OK for tiny applications, but they're horrible for long ones.
Local Variables Cannot Be Used in the Global Scope
Let’s first look at a program then by explaining it we learn more about why we cannot use local variables in the global scope.
The preceding code fails because the eggs variable only exists in the local scope generated by spam(). The local scope is deleted when the program execution returns from spam, thus eggs no longer exist. When we execute print(eggs), Python gives us an error because eggs are not defined. When a program is executed with the global scope, no local scopes are generated or exist, hence there are no local variables. Thus, only global variables may be utilized globally.
Local Scopes Cannot Use Variables in Other Local Scopes
A new local scope is formed when we call a function, including functions called from another function. Show and explain a code to better comprehend it:
When the program starts, the spam() function (the final line of out) is run, the local scope is formed, and eggs are set to 99. Then we call becon() to generate another local scope. Many local scopes can coexist. Unlike spam()'s local scope, this new local scope sets ham to 101 and contains new local variable eggs. This local variable egg is 0.
This call's local scope is removed when bacon() returns. The program proceeds in the spam() method, which prints the value of eggs. Because the local scope for spam() is still present, the eggs variable is set to 99. This is software output.
One function's local variables are fully independent from the other's.
Global Variable Can be Read from a Local Scope
Let's understand how a global variable can be read from the local scope with the help of Python code: Since there is no argument called eggs or code that assigns a value to eggs, Python considers eggs as a reference to the global variable eggs in the spam() method. This is why the previous program prints 42.
Local and Global Variables with the Same Name
Avoid using local variables with the same name as global variables and other local variables to simplify programs. Python is technically fine for this. We'll see what happens using Python code:
In the above program there are three different variables, but to make us confused we named them all eggs. The variables are as follows:
- A variable named eggs that exists in a local scope when spam() is called.
- A variable named eggs that exists in a local scope when bacon() is called.
- A variable named eggs that exists in the global scope.
As shown above, all three variables have the same names, making it difficult to determine which one is being utilized. This is the major reason to avoid reusing variable names across scopes.
The Global Statement
To edit a global variable within a local variable, use the global statement. If we write “global eggs” at the top of a function, Python knows to not create a local variable with the same name. Look at a code to understand: When we run the program, the final print() call will output “spam”.
The eggs are declared global at the start of the spam () method, thus setting eggs to "spam" assigns it to the global scoped spam. Thus, no local spam variable exists.
There are four rules to tell whether a variable is in a local scope or global scope:
- A variable is always considered global if it is utilized in the global scope, which means it is used outside of all functions.
- A variable is considered global if a function has a global statement for it.
- The variable is local if it is not utilized in an assignment statement within the function.
- However, a variable is global if it is not used in an assignment expression.
To understand these rules much better let’s look at a Python code:
In the spam() method, eggs are the global variable because there is a global statement at the beginning. The bacon() method has an assignment statement, therefore eggs are a local variable. Since there is no assignment statement or global statement for eggs in the third function, ham(), eggs are the global variable. Run the application and get “spam” back.
We know that function variables are either global or local. A function cannot utilize a local variable named eggs and then the global eggs variable.
To edit a global variable's value from a function, we must use a global statement.
Python will warn if we utilize a local variable inside a function without assigning a value. Let's check a program for errors.
Python believes eggs are local because spam() has an assignment statement for eggs. But print(eggs) is run before eggs are assigned a value, therefore local value eggs don't exist. Python will not use global eggs.
Exception Handling
Getting an error or exception in Python crashes the entire application. We don't want this in real-world programs. Our application should identify issues, fix them, and keep running.
For example, let’s create a program that divides a number by zero: The spam function above took an argument and returned 42 split by it. We print the function's value with several parameters to see its output. The first two values are appropriately divided, but 42 by 0 yields a ZeroDivisionError. Whenever we divide by zero, this happens. According to the error message's line number, spam()'s return statement causes the fault.
The above program has an error, but attempting and except statements can fix it. This puts error-prone code in a try clause. Errors cause the program to start the following unless clause.
The prior divide-by-zero code can be in a try clause, and an except clause can address this issue.
The preceding code instantly executes the except clause if the try clause fails. The output shows that after running that code, the remaining code executes normally.
We must also know that try block function call errors are caught. The same Python code as previously, but with try and except clauses in function calls.
The reason print(spam(1)) is never executed is that after the execution jumps to the code in the except clause, it never comes back to the try clause. Instead, it continues moving forward/down as normal.
Storing Your Functions in Modules
Features of functions include separating code from the main application. Using descriptive function names will make our main program simpler to read. We may also save our functions in a module and import it into our main application. An import statement in Python makes code available in the program file's executing module.
Putting our functions in separate files lets us hide the code and focus on the program's logic. This lets us utilize the same function in several apps. If we put our functions in separate files, we may share only the files related to or needed by other programmers without sharing our full program. If we know how to import functions, we can use other programmers' function libraries.
Importing an Entire Module
We must construct a module before importing functions. A module is a.py (or.ipynb) file that contains the code we wish to import into our application. Let's examine a module with our function. To construct this module, we must delete everything from that file except the function. This code creates a file named making_pizza.py. It usually has to be in the same directory as pizza.py. The second file imports the newly generated module and executes make_pizza() twice.
Python examines the line import pizza and opens pizza.py to transfer all its routines into the current program or program it is called in. Python copies code silently as the application runs. We just need to know that making_pizza.py does not contain pizza.py functions.
If we need to call a function from an imported module or file, we enter the module name, pizza, followed by the function name, make_pizza(), separated by a dot. This result matches the original output without importing a module.
In the first method, we type import followed by the module name to import all its functions into our application. This import line imports module_name.py and makes all its functions available:
module_name.function_name()
Importing Specific Functions
If we want to only import a specific function from a module, then we can do this also. Below is the general syntax for this approach:
from module_name import function_name
By the above syntax, we can import as many functions as we want from a module, the functions are separated by commas. Let’s look at a common syntax example for this:
from module_name import function_0, function_1, function_2
If we take the above example for importing only the function from the pizza.py module then the code will be written as: The aforementioned solution eliminates the necessity for dot notation when calling functions. Since we specifically imported make_pizza() in the import section, we can call it by name when we use it.
Using as to Give a Function an Alias
If the function we're importing clashes with an existing name in our application or is lengthy, we may use a short, unique alias—like a function's nickname. We must import the function with this specific moniker.
Import make_pizza as mp and alias it to mp(). Functions are renamed using the specified alias when using the as keyword:
The import statement renames make_pizza() to mp() in the above code. Any time we need to use make_pizza(), we only need to type mp() in the program instead of the complete name, and Python will run the code. This avoids confusion with other make_pizza() functions in the program file.
from module_name import function_name as fn
Using to Give a Module an Alias
We may also alias module names if needed. If we give a module a short alias, say p for pizza in the following code, we may call its method faster. Calling p.make_pizza() is shorter and easier than a pizza.make_pizza():
In the preceding code, pizza is given the alias p in the import statement, while the other module's functions keep their names. Calling the functions with p.make_pizza() is shorter than pizza.make_pizza() and focuses attention away from the module name to the descriptive function names. These function names make it clear what each function does, improving code readability more than the module name.
The common or general syntax for this approach is:
import module_name as mn
Importing All Functions in a Module
If we need to import all the functions from a module then we need to use an asterisk (*) operator. Let’s look at the code and explain it for better understanding. The asterisk in the import statement tells Python to transfer all pizza module functions into the current program file. This approach lets us call each function by name without using the dot notation because every function is imported. We shouldn't utilize this strategy when working with huge modules we haven't constructed or written since if the module's function name matches ours, we may obtain odd outcomes. Python may replace functions or variables with the same name instead of importing them.
The best option is to import the appropriate function or functions or the complete module and use dot notation. This produces clean, easy-to-read code. This section helps you spot import statements like this in other people's programs:
from module_name import *
Styling Functions
Function names should be meaningful, using lowercase letters and underscores. Using descriptive labels helps you and others understand your code. Module names should follow these guidelines.
Using program comments, we must clearly explain each function. The function comment must follow the function declaration and use docstring format. A well-documented function can help other programmers use it appropriately by reading the docstring. If students know the function name, its inputs, and the value it returns, they should be able to utilize the code in their applications. The remark describes what the code will perform.
Function parameters with default values should not include spaces on either side of the equal sign. An example syntax follows:
def function_name(parameter_0, patameter_1=’default value’)
The same convention should be used for keyword arguments in function calls:
function_name(value_0, parameter_1=’value’)
PEP8 recommends limiting code lines to 79 characters to fit in a reasonable editing window. If a function's definition exceeds 79 characters due to parameters, press Enter after the opening parenthesis. Press the tab twice to separate the function body, which will be indented one level, from the parameters on the next line.
Almost every editor automatically aligns subsequent argument lines with the indentation on the first line of function, loop, program, etc. Divide numerous functions in your program or module by two blank lines to make it easier to identify where one ends and the next begins.
File opening statements should include import statements. The only exception is if you describe the program in the file's beginning remarks.
Summary
In this chapter, we learned about Python functions, which are blocks of code that execute certain tasks. By breaking down repetitious activities into functions, they improve code reuse, readability, and modularity.
We learned how to construct functions using the def statement, call them by name, and give parameters to dynamically change their behavior. Python matches arguments with parameters by order, allowing function calls and declarations to flow smoothly.
Function arguments, positional and keyword arguments, and parameter interactions must be understood. We saw how Python required matching parameter counts for function execution and handled multiple positional arguments smoothly.
We explored how return values improve program functionality and modularity by providing function results. We also examined real examples of return values generating dynamic outputs from input parameters.
We also used default parameter values to identify optional inputs, improving function flexibility and usability. We stressed parameter order and how to handle optional parameters in functions.
Additionally, we studied how functions may efficiently handle and interact with lists by receiving lists as parameters or altering them in the function body. Function specialization and modularization improve code clarity and maintainability.
Finally, we learned how to handle arbitrary parameters, letting functions handle variable input data. Variable scopes were also stressed to define local and global variables within functions.
Python developers may use functions to solve varied programming problems by grasping these ideas and writing fast, adaptable, and maintainable code.
No comments:
Post a Comment