You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
33 KiB
479 lines
33 KiB
3 months ago
|
<!doctype html>
|
||
|
<html>
|
||
|
<head>
|
||
|
<meta charset="utf-8">
|
||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
<title>My Learning Website</title>
|
||
|
<link href="/styles/styles.css" rel="stylesheet" type="text/css">
|
||
|
<link href="/programming/styles/styles.css" rel="stylesheet" type="text/css">
|
||
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||
|
<!--[if lt IE 9]>
|
||
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||
|
<![endif]-->
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<div class="banner">
|
||
|
<h1 class="courselink">Python Essential Training</h1>
|
||
|
<h2 class="lecturer">LinkedIn Learning : Bill Weinman</h2>
|
||
|
<h2 class="episodetitle">Functions</h2>
|
||
|
</div>
|
||
|
|
||
|
<article>
|
||
|
<h2 class="sectiontitle">Defining a Function</h2>
|
||
|
<p>Let's start by looking at an example that defines two functions.</p>
|
||
|
<pre class="inset">
|
||
|
#!/usr/bin/env python3
|
||
|
# Copyright 2009-2017 BHG http://bw.org/
|
||
|
|
||
|
def main():
|
||
|
kitten()
|
||
|
|
||
|
def kitten():
|
||
|
print('Meow.')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>Here, we are defining two functions called main and kitten. This is followed by a conditional statement that calls the main function. This type of structure is quite common in Python so it bears further investigation.</p>
|
||
|
<p>The condition in the if statements is comparing a variable called __name__ with a string literal. The variable is a special variable referencing the name of the current module so if we were using an imported module here, __name__ would be referencing that. In this case, we haven't imported anything. The code is running as the main unit of execution so the special variable has a special value, __main__, so essentially the if statement is checking whether we are using a module or the main function.</p>
|
||
|
<p>Notice that we have to put this after the functions have been defined because we wouldn't be able to call main if it had not already been defined and main calls kitten so this also has to be defined before main is called. Python doesn't allow you to declare your functions and define them later as you might do with, for example, C. So by defining both of the functions, we can then write the code that runs when the program is executed and it is able to use any of these functions. That is, we can call main and main can call kitten, even though kitten is defined after main. Actually, since no code is executed until the main function is called in the last line of the program, the order in which these functions are defined is immaterial. We can switch them around and the code will work in exactly the same way.</p>
|
||
|
<p>It's also worth noting that the code block associated with the if statement is only one line, so we can put it on the same line. This is usually considered bad practice but it is possible and this is an occassion where it is normal to see the if statement and its code block on a single line.</p>
|
||
|
<p>When the main function is called, it calls the kitten function and this prints out 'Meow' and then the program terminates.</p>
|
||
|
<p>The actual function definition is pretty somilar to other languages although it uses the keyword def rather than function. This is followed by the function name and then the argument list which must be present but can be empty. In other words, we need the parentheses even if there is nothing inside them. This is terminated with a colon because the function has a mandatory code block and then the code block.</p>
|
||
|
<p>Jast as variables are declared without a data type, when we declare function arguments in the function definition, we don't need to give the type there. For example, let's say that we want to pass a number to the kitten function which will be output in front of the 'Meow', the function definition would become</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(n):
|
||
|
print(f'{n}: Meow.')</pre>
|
||
|
<p>We would also need to amend the call to kitten in the main function because if the function is called without the correct number of arguments, so this might be rewritten as</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
kitten(5)</pre>
|
||
|
<p>This is not a realistic example because normally the argument passed over would be some value provided by the user. There isn't really much point in passing, in this example 5, as an argument to a function because if you are going to hard code the argument like that, it would be much easier to just hard code it into the function itself, but this is demonstrating the way in which arguments are passed to functions in Python. Note that if we pass a different type of value, for example</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
kitten('Thor')</pre>
|
||
|
<p>this would work fine and we would see the string, 'Thor', being output by the kitten function.</p>
|
||
|
<p>You may recall that all functions return a value. This can de done explicitly using the return keyword but in this example, we haven't explicitly returned anything and so None is returned. Some programs make a distinction between functions that return a value (these are usually referred to as functions) and functions that don't (usually referred to as procedures), but since all Python functions return a value, this distinction doesn't apply.</p>
|
||
|
<h2 class="sectiontitle">Function Arguments</h2>
|
||
|
<p>We have already seen how to pass an argument to a function and this is probably a little bit simpler in Python than in some other programming languages thanks to Python's use of Duck typing. We can pass as many arguments as we like, separated by commas but the number of arguments passed must be the same as the number of arguments expected by the function. The following example demonstrates this, with our kitten function amended to expect three arguments.</p>
|
||
|
<pre class="inset">
|
||
|
#!/usr/bin/env python3
|
||
|
# Copyright 2009-2017 BHG http://bw.org/
|
||
|
|
||
|
def kitten(a, b, c):
|
||
|
print(a, b, c)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
kitten(5, 6, 7)
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>The fact that we must provide all three arguments expected by the function implies that these are optional and from the point of view of the function, they are not. However, we can make them optional from the point of view of the call to the function and we do that by giving the arguments default values. Let's say we want c to have a default value of 0 and b to have a default vaule of 1. We do that by assigning these values in the argument list of the function definirion, like so</p>
|
||
|
<pre class="inset">
|
||
|
#!/usr/bin/env python3
|
||
|
# Copyright 2009-2017 BHG http://bw.org/
|
||
|
|
||
|
def kitten(a, b = 1, c = 0):
|
||
|
print(a, b, c)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
kitten(5, 6)
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>This will output</p>
|
||
|
<pre class="inset">
|
||
|
5 6 0
|
||
|
Meow.</pre>
|
||
|
<p>In this example, the function has three arguments, a, b and c and both b and c have default values. This means that when we call the function, we can call it with just a single argument because we have to provide a value for a. In this case, we have provided two arguments. Note that there is no way to pass an argument and specify that the value should be assigned to a specific value in the function, these are assigned in order. This means that when we pass two arguments to the function as we did here, these being 5 and 6, 5 is assigned to a, 6 is assigned to b and c takes its default value of 0.</p>
|
||
|
<p>There is no way that we can apply the second value to c and let b take its default value. Essentially, we can sum up the possibilities for passing values like this.</p>
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Arguments</th>
|
||
|
<th>Values of a, b and c</th>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td>5</td>
|
||
|
<td>a = 5, b = 1, c = 0</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td>5, 6</td>
|
||
|
<td>a = 5, b = 6, c = 0</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td>5, 6, 7</td>
|
||
|
<td>a = 5, b = 6, c = 7</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
<p>One consequence of this is that arguments that have default values need to be at the end of the argument list. Consider this example.</p>
|
||
|
<pre class="inset">
|
||
|
#!/usr/bin/env python3
|
||
|
# Copyright 2009-2017 BHG http://bw.org/
|
||
|
|
||
|
def kitten(a, b = 1, c = 0, d):
|
||
|
print(a, b, c)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
kitten(5, 6)
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>This function takes 4 arguments and two of those have default values, but these are the second and third arguments. The fourth argument does not have a default value. When we call the function with two arguments, you might expect that a will be assigned the value of the first argument and since d is the only other argument without a default value, it will be assigned the value of the second argument, but this is not the case. The second argument is assigned to b, c takes its default value. This leaves d without a value and so we get an error.</p>
|
||
|
<p>Actually, this is slightly misleading since it implies that this will work if we pass the function 4 arguments, but bear in mind that if we do that, this defeats the purpose of having default values of b and c since we must always provide 4 arguments for the function to work, so this will always give an error as follows.</p>
|
||
|
<pre class="inset-error">
|
||
|
SyntaxError: non-default argument follows default argument</pre>
|
||
|
<p>This neatly sums up the problem and is unusually clear for an error message! Simply put, in our argument list when we define the function, we cannot put an argument that doesn't have a default value (a non-default argument) after an argument that does have a default value (a default argument).</p>
|
||
|
<p>If you do need to give one or more of the arguments a default value, you can provide all arguments with a default value and that can help to avoid problems like this, but you must bear in mind that if you want to pass a specific argument to one of the arguments, you must also pass values for the arguments that preced it in the list.</p>
|
||
|
<p>To illustrate this, let's assume that you have a function that takes 4 arguments and each one has a default value of 0, like this</p>
|
||
|
<pre class="inset">
|
||
|
def some_function(a=0, b-0, c=0, d=0):</pre>
|
||
|
<p>Let's say that you want to call this and pass a value of 5 for c, but you don't want to pass values for any of the other arguments, you would call the function like this.</p>
|
||
|
<pre class="inset">
|
||
|
some_function(0, 0, 5)</pre>
|
||
|
<p>or you could do this, which mnay help you to be more consistent with your function calls</p>
|
||
|
<pre class="inset">
|
||
|
some_function(0, 0, 5, 0)</pre>
|
||
|
<p>So, if you are passing a value for c, you must also pass values for a and b but you don't have to pass a value for d.</p>
|
||
|
<p>Let's look at another example where the function takes a single argument.</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(a):
|
||
|
print(a)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
x = 5
|
||
|
kitten(x)
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>In this example, rather than passing a number to the function, we are passing a variable and it works in exactly the same way. We want to look at what happens to that variable when it is passed to the function like this so we will modify the code as follows.</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(a):
|
||
|
a = 3
|
||
|
print(a)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
x = 5
|
||
|
kitten(x)
|
||
|
print(f'x in main is {x}')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>If we think about that is happening inside main, we are giving x a value of 5 and then passing this over as an argument to kitten. When kitten is finished processing, we then output the value of x.</p>
|
||
|
<p>In the kitten function, the argument is represented by a (a is the formal parameter, x is the actual parameter). A has a value of 5 but it is then changed to 3 and printed out and the kitten function then outputs 'Meow'. The output from this is</p>
|
||
|
<pre class="inset">
|
||
|
3
|
||
|
Meow.
|
||
|
x in main is 5</pre>
|
||
|
<p>The last line of the output shows that after the function has been called and finsished executing, the value of x is still the same as it was before the function was called. This is because the argument was passed by value. That is, x had a value of 5 and it is that value that has been passed to the function. 5 is a literal and literals are immutable, so this can't be changed in the function. No matter what the function does, 5 is still 5 and so when we print out the value of x, we see that it is unchanged.</p>
|
||
|
<p>We will amend the code again so that we are passing the function a mutable value. You may recall from an earlier lessong that a list is a mutable sequence in that we can not only add or remove elements from it after it has been created, we can also change the values of individual elements. In this example, rather than giving x a simple integer value, we will initialise it with a list and we will then pass the first element of that list over to kitten.</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(a):
|
||
|
a[0] = 3
|
||
|
print(a)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
x = [5]
|
||
|
kitten(x)
|
||
|
print(f'x in main is {x}')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>The output we get this time is</p>
|
||
|
<pre class="inset">
|
||
|
[3]
|
||
|
Meow.
|
||
|
x in main is [3]</pre>
|
||
|
<p>This time, after the function is called and we print the value of, which is a list containing a single element, we find that the value of that element has been chnaged by the function. In this case, the argument has been passed by reference. The element of x that was passed over contains the same literal we saw in the previous example, but we are not passing that literal to the function. Rather, we are passing a reference to element in the list. This means that both x (in the main function) and a in the (kitten function) reference the same list so even though the change to the list element happened in kitten, the change still applies to x when the function is finished.</p>
|
||
|
<p>Be aware that if we change the assignment in kitten back to the original version</p>
|
||
|
<pre class="inset">
|
||
|
x = 3</pre>
|
||
|
<p>but still pass x over a mutable variable, the output is now</p>
|
||
|
<pre class="inset">
|
||
|
3
|
||
|
Meow.
|
||
|
x in main is [5]</pre>
|
||
|
<p>This is because we passed the argument by reference, but this assignment operation removes the reference, so a now has a value of 3 and no longer references the same list that x is referencing. The kitten function is now no longer making any changes to the list so when the function finishes, x is unchanged.</p>
|
||
|
<p>It should be noted that in fact, kitten isn't doing anything with the value that is passed to it and it's output in the last example (as well as in the example where the argument was passed by value) will be the same regardless of what argument is passed to it or, indeed, whether any argument is passed to it at all. Of course, you wouldn't ordinarily write a function like this. These examples are intended to reinforce the different between passing an immutable value like 5 to a function and passing a mutable value like a list or a list element. This can be summed up as follows.</p>
|
||
|
<table>
|
||
|
<tr>
|
||
|
<th>Type of value</th>
|
||
|
<th>Example</th>
|
||
|
<th>Mutable?</th>
|
||
|
<th>Argument passed by</th>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td>Literal number</td>
|
||
|
<td>5</td>
|
||
|
<td>Immutable</td>
|
||
|
<td>Passed by value</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td>List element</td>
|
||
|
<td>x[0]</td>
|
||
|
<td>Mutable</td>
|
||
|
<td>Passed by reference</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
<p>If you want to understand how other values are treated, you can check whether they are mutable or immutable (most, if not all, literal values will be immutable) or you can write a little bit of code like this to test whether it is changed by a function. For example, let's say that we want to know if a bool variable is passed by value or by reference. The bool values are immutable so we would expect them to be passed by value. If we run this code</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(a):
|
||
|
a = True
|
||
|
print(a)
|
||
|
print('Meow.')
|
||
|
|
||
|
def main():
|
||
|
|
||
|
x = False
|
||
|
kitten(x)
|
||
|
print(f'x in main is {x}')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>the output is</p>
|
||
|
<pre class="inset">
|
||
|
True
|
||
|
Meow.
|
||
|
x in main is False</pre>
|
||
|
<p>We can see that the value of the bool variable in main was not changed when the function executed and so we can that when a bool variable is used as an argument in a function, it is passed by value.</p>
|
||
|
<p>It is not just in function calls that we see this type of behaviour. Consider the following example.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
|
||
|
x = 5
|
||
|
y = x
|
||
|
|
||
|
print(id(x))
|
||
|
print(id(y))
|
||
|
|
||
|
y = 3
|
||
|
print('-------------')
|
||
|
|
||
|
print(id(x))
|
||
|
print(id(y))
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>When we run this, the output we get is</p>
|
||
|
<pre class="inset">
|
||
|
1966392240560
|
||
|
1966392240560
|
||
|
-------------
|
||
|
1966392240560
|
||
|
1966392240496</pre>
|
||
|
<p>the variable, y, is initialised with x and so both x and y reference the same integer and because of this, both variables have the same id. The value of y is then changed so it points to a different integer and x and y have different ids. If we try this again using a mutable value.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
|
||
|
x = [5]
|
||
|
y = x
|
||
|
|
||
|
print(id(x))
|
||
|
print(id(y))
|
||
|
|
||
|
y[0] = 3
|
||
|
print('-------------')
|
||
|
|
||
|
print(id(x))
|
||
|
print(id(y))
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>The output is now</p>
|
||
|
<pre class="inset">
|
||
|
2654850019072
|
||
|
2654850019072
|
||
|
-------------
|
||
|
2654850019072
|
||
|
2654850019072</pre>
|
||
|
<p>Since a list is mutable, we can change the value of an element, but it is the same list and we can see that being reflected in the fact that x and y have the same ids before we change the value and also after. Just as important is the fact that not only does x still have the same id as y, but they both still have the same id they had before the change. So the change means that the contents of the list have changed, but it is still the same list.</p>
|
||
|
<h2 class="sectiontitle">Argument Lists</h2>
|
||
|
<p>In Python, a function can accept a variable length argument list in much the same way that C can accept args[] as a variable length array of arguments. You may recall that a list is more or less Python's equivalent to an array and so the variable length argument list actually is a list!</p>
|
||
|
<p>Let's look at an example of that.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
kitten('meow', 'grrr', 'purr')
|
||
|
|
||
|
def kitten(*args):
|
||
|
if len(args):
|
||
|
for s in args:
|
||
|
print(s)
|
||
|
else: print('Meow.')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>In this example, *args as an argument and this is the variable length argument list. The name can be anything you like, but it is conventional in Python to call it args and this is also easier to identify if someone else is reading your code. The asterisk indicates that it is a list rather than just a single variable called args.</p>
|
||
|
<p>The kitten function uses the if statement to determine whether args is an empty list or not (if it is empty, it will have a length of 0 and 0, of course, is considered to be false. If not, length will be an integer greater than 0 amd so the if statements code block will be executed.</p>
|
||
|
<p>Since args is a list, we can iterate over it with a for loop which, in this case outputs each list element in turn. You can pass over as many arguments as are required in the list, inclduing none. If the list is empty, the code in the else clause will be executed.</p>
|
||
|
<p>We can also pass a pre-prepared list over to our function as an argument list, but you need to be a little bit careful with that. Consider this example.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
x = ('meow', 'grrr', 'purr', 'baaaaa', 'moo')
|
||
|
kitten(x)</pre>
|
||
|
<p>In this example, we have a tuple referenced by x which we are passing as an argument to the function. Remember that Python will work out what the data type of the argument is for us and pass it over to kitten, so in this case we are only passing a single argument over rather than a list of arguments. The argument we are passing over is a list, but it is not an argument list. We will see this in the output which is.</p>
|
||
|
<pre class="inset">
|
||
|
('meow', 'grrr', 'purr', 'baaaaa', 'moo')</pre>
|
||
|
<p>If we want to pass this over as abn argument list, we need to put an asterisk in front of the list reference.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
x = ('meow', 'grrr', 'purr', 'baaaaa', 'moo')
|
||
|
kitten(*x)</pre>
|
||
|
<p>Running this gives us the output shown below.</p>
|
||
|
<pre class="inset">
|
||
|
meow
|
||
|
grrr
|
||
|
purr
|
||
|
baaaaa
|
||
|
moo</pre>
|
||
|
<p>If we omit the asterisk, the list is sent over as a single entity so when the for loop executes, it goes through one iteration and outputs the first argument which is a list and then stops because there are no other arguments to process so we see the output on a single line and in the form of a list.</p>
|
||
|
<p>When the asterisk is added, this same list is passed over as a list of arguments (in this case with 5 elements) rather than a single list argument and so when the for loop executes, it runs 5 times and each argument is output separately on its own line.</p>
|
||
|
<p>It may not bw immediately obvious why we would need to send over an argument list like this rather than a list as an argument since we can still process this argument as a list inside the function. This is shown in the example below.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
x = ('meow', 'grrr', 'purr', 'baaaaa', 'moo')
|
||
|
kitten(x)
|
||
|
|
||
|
def kitten(args):
|
||
|
if len(args):
|
||
|
for s in range(len(args)):
|
||
|
print(args[s])
|
||
|
else: print('Meow.')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>This gives us the output</p>
|
||
|
<pre class="inset">
|
||
|
meow
|
||
|
grrr
|
||
|
purr
|
||
|
baaaaa
|
||
|
moo</pre>
|
||
|
<p>In this example, we have a list which is being passed as a single argument (a list argument rather than a list of arguments) to the function and then it is being iterated over as a normal list. Arguably, the syntax is a little bit easier when using a list of arguments but you are also having to remember to use the asterisk. For now, I would suggest that the important point is to be aware of the fact that either approach is possible, but you may encounter a scenarion where one is more suitable than the other. There is an interesting post covering this topic on stackabuse.com entitled <a href="https://stackabuse.com/variable-length-arguments-in-python-with-args-and-kwargs/">Variable-Length Arguments in Python with *args and **kwargs</a> (we will look at kwargs next).</p>
|
||
|
<h2 class="sectiontitle">Keyword Arguments</h2>
|
||
|
<p>Keyword arguments are similar to the list arguments seen in the previous section, but rather than passing a list of arguments to the function, we are passing a dictionary of named arguments. An example of this is shown below.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
kitten(Buffy = 'meow', Zilla = 'grr', Angel = 'rawr')
|
||
|
|
||
|
def kitten(**kwargs):
|
||
|
if len(kwargs):
|
||
|
for k in kwargs:
|
||
|
print('Kitten {} says {}'.format(k, kwargs[k]))
|
||
|
else: print('Meow.')
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>There are similarities to the previous example, the most obvious difference being that each argument is a key value pair so, for example, where we previously had a value like 'meow', now we have the same value along with a key that could potentially give us more information relating to that value (such as the name of the animal that makes that sound).</p>
|
||
|
<p>The other difference to note is that the dictionary argument is identified by two asterisks rather than 1. The loop in this example is similar to the previous example which looked like this.</p>
|
||
|
<pre class="inset">
|
||
|
def kitten(*args):
|
||
|
if len(args):
|
||
|
for s in args:
|
||
|
print(s)
|
||
|
else: print('Meow.')</pre>
|
||
|
<p>In that instance, we were iterating through the loop with a variable called s which represented an individual argument in each iteration. With the keyword arguments, we are using a variable called k which represents the key for each of the key value pairs and we can use this to access the corresponding value although we are actually doing that using a syntax that looks suspiciously like typical array notation. The difference here is that the index value rather than being a positional argument (eg, the first element, second element and so on) it is a key argument so we access the value corresponding to that key.</p>
|
||
|
<h2 class="sectiontitle">Return Values</h2>
|
||
|
<p>We have previously hinted at the fact that Python doesn't make a distinction between functions and procedures since all functions in Python return a value (even if it is none) whereas a procedure is essentially a function that doesn't return a value.</p>
|
||
|
<p>We can return more or less any type of value including both simple values (numbers, strings and so on) as well as more complex values (a list, a disctionary or any type of object). This can be returned explicitly with the return keyword and a value or implicitly, with the return keyword but no value or simply by omitting the return keyword. If the return value is implicit, we are returning (or allowing to be returned) none.</p>
|
||
|
<h2 class="sectiontitle">Generators</h2>
|
||
|
<p>I haven't encountered generators in any other programming language, so they may be peculiar to Python. Essentially, a generator is a function that returns a stream of values rather than a single argument. Let's say that we have a function to print out 25 integers like this.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
for i in range(25):
|
||
|
print(i, end = ' ')
|
||
|
print()
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>This will output the integers from 0 to 24. Sometimes, we might want our loop to start from 1 and run up to 25 and we have looked at a couple of ways of doing that earlier, but we can also do that using a generator.</p>
|
||
|
<pre class="inset">
|
||
|
def main():
|
||
|
for i in inclusive_range(25):
|
||
|
print(i, end = ' ')
|
||
|
print()
|
||
|
|
||
|
def inclusive_range(*args):
|
||
|
numargs = len(args)
|
||
|
start = 0
|
||
|
step = 1
|
||
|
|
||
|
# initialize parameters
|
||
|
if numargs < 1:
|
||
|
raise TypeError(f'expected at least 1 argument, got {numargs}')
|
||
|
elif numargs == 1:
|
||
|
stop = args[0]
|
||
|
elif numargs == 2:
|
||
|
(start, stop) = args
|
||
|
elif numargs == 3:
|
||
|
(start, stop, step) = args
|
||
|
else: raise TypeError(f'expected at most 3 arguments, got {numargs}')
|
||
|
|
||
|
# generator
|
||
|
i = start
|
||
|
while i <= stop:
|
||
|
yield i
|
||
|
i += step
|
||
|
|
||
|
if __name__ == '__main__': main()</pre>
|
||
|
<p>Where we had iterated through the for loop using range (where the loop ran once for every value in the range going from 0 up to 24 - giving a total of 25 iteraitions), this time we are calling the function, inclusive_range and passing it the value, 25. Ignoring how this function works for a moment, the result of running the code as shown is that we see the numbers 0 to 25 output. Note, however, that this function, like range, can take up to three arguments. If we pass in two arguments like this,</p>
|
||
|
<pre class="inset">
|
||
|
for i in inclusive_range(1, 25):</pre>
|
||
|
<p>the output is the numbers from 1 to 25. If we add a third parameter,let's say 2</p>
|
||
|
<pre class="inset">
|
||
|
for i in inclusive_range(1, 25, 2):</pre>
|
||
|
<p>the output we get from this is</p>
|
||
|
<pre class="inset">
|
||
|
1 3 5 7 9 11 13 15 17 19 21 23 25</pre>
|
||
|
<p>The main difference between using the generator and simply using the range function is that we don't need to remember to add 1 number to the argument. For example, if we wanted to output the numbers from 1 to 10 using the range function, the loop would look something like this.</p>
|
||
|
<pre class="inset">
|
||
|
for i in range (1, 11):</pre>
|
||
|
<p>We are specifying 1 as the starting point and this is where things might look a little weird, we seem to be specifying 11 iterations. In a sense we are, but that is 11 iterations if we didn't provide a starting point. Specfiying 1 as the starting point means we are skipping the first iteration giving is a total of 10, numbered 1 to 10.</p>
|
||
|
<p>Using the inclusive_range function, the call would look like this.</p>
|
||
|
<pre class="inset">
|
||
|
for i in inclusive_range(1, 10):</pre>
|
||
|
<p>So this syntax is a little more intuitive and therefore easier to use and less error-prone. Let's look a little deeper into how it works.</p>
|
||
|
<p>To start with, we are using a variable length list of arguments, which allows us to use as many arguments as required including 0 or more than 3 although the function itself is expecting between 1 and 3 arguments.</p>
|
||
|
<p>We are then intialising three variables.</p>
|
||
|
<pre class="inset">
|
||
|
• numargs - this is set to the length of the argument list so it represents the number of arguments passed to the function
|
||
|
• start - this is set to 0 and is the default value for the range's starting point
|
||
|
• step - this is set to 1 and the default step value for the range</pre>
|
||
|
<p>Notice that the default values are the same as the default values for range.</p>
|
||
|
<p>We then have an if statement followed by three elif clauses and an else clause. The if statement tests to see if no arguments have been passed over to the function and prints out an error message if that is the case.</p>
|
||
|
<p>If not, the first elif clause tests to see if one argument only was passed. If there was, we set the stop value to that argument.</p>
|
||
|
<p>Similarly, the other two elif clauses test for two or three arguments in turn and set values accordingly so if two arguments are passed, start is set to the value of the first argument and stop to the value of the second. If three, then in addition, step is set to the value of the third argument.</p>
|
||
|
<p>If none of the elif clauses are matched (in other words, if there are more than three argument), the else clause is executed and an error message is displayed. So, the function actually works in a very similar way to the range function and the arguments work in the same way.</p>
|
||
|
<p>Assuming the function has received between 1 and 3 arguments, we will then have values set for start, stop and step and that is the information we need in order to execute a for loop, but there is nothing special about this code and nothing that makes it a generator. The code that makes this function a generator comes next.</p>
|
||
|
<pre class="inset">
|
||
|
# generator
|
||
|
i = start
|
||
|
while i <= stop:
|
||
|
yield i
|
||
|
i += step</pre>
|
||
|
<p>This is a straightforward while loop that uses the value of start as a starting point and the loop executes while the counter (which has been initialised with the start value) is less than or equal to the stop. Note that it is the fact that the condition is less than or equal to rather than less than that makes this range inclusive. There are two lines of code in the loop and the second line increments the counter (i) by the value contained in step each time the loop runs. The other line is a yield statement and this is similar to a return statement. Remember that this function is being called from main so if a value is returned, it's going to be returned to main. If we did return a value, the function would terminate and this is how yield is different. It returns its calue to the main function, the main function uses that value in its for loop, but control is then passed back to the generator if there are more values to be generated (that is, if the while loop in the generator function is still running.</p>
|
||
|
<p>This is, in my opinion, not particularly intuitive so it might be quite difficult to have a clear idea of how and when you would need to use a generator in Python. In addition, it may not be immediately clear why we need to use a generator if the only difference is that the upper boundary is inclusive (that is, if 25 is the upper boundary, as it is in these examples) the count will go up to 25. However, generators do seem to be important in Python. There is even a LinkedIn Learning course called <a href="https://www.linkedin.com/learning/learning-python-generators">Learning Python Generators</a> although it is quite short at around 44 minutes.</p>
|
||
|
<p>Something that is more interesting here, for me, is the fact that our generator can run with 1, 2 or 3 arguments (strictly speaking, it can run with any number of arguments but it will return an error message for any number not between 1 and 3) so ew have, in effect, method overloading.</p>
|
||
|
<p>As it happens, Python doesn't really support method overloading, certainly not natively, so we need to use some trickery in order to overload methods and a variable length argument list is one, relatively simple way to do that. For more information on this, there is an interesting article by user ShivamKD on GeeksforGeeks.org entitled <a href="https://www.geeksforgeeks.org/python-method-overloading/">Python | Method Overloading</a> that describes another way to do this.</p>
|
||
|
<p>Returning to the original point, a generator, as we have seen is a particular type of function that generates a series of values (range itself is actually implemented using a generator function).</p>
|
||
|
<h2 class="sectiontitle">Decorators</h2>
|
||
|
</article>
|
||
|
|
||
|
<div class="btngroup">
|
||
|
<button class="button" onclick="window.location.href='loops.html';">
|
||
|
Previous Chapter - Loops
|
||
|
</button>
|
||
|
<button class="button" onclick="window.location.href='structured.html';">
|
||
|
Next Chapter - Structured Data
|
||
|
</button>
|
||
|
<button class="button" onclick="window.location.href='pythonessentialtraining.html'">
|
||
|
Course Contents
|
||
|
</button>
|
||
|
<button class="button" onclick="window.location.href='/programming/programming.html'">
|
||
|
Programming Page
|
||
|
</button>
|
||
|
<button class="button" onclick="window.location.href='/index.html'">
|
||
|
Home
|
||
|
</button>
|
||
|
</div>
|
||
|
</body>
|
||
|
</html>
|