|
| 1 | +# Advanced things with functions |
| 2 | + |
| 3 | +Now we know [how to define functions](../basics/defining-functions.md). |
| 4 | +Functions can take arguments, and they will end up with local variables |
| 5 | +that have the same name. Like this: |
| 6 | + |
| 7 | +```py |
| 8 | +def print_box(message, border='*'): |
| 9 | + print(border * (len(message) + 4)) |
| 10 | + print(border, message, border) |
| 11 | + print(border * (len(message) + 4)) |
| 12 | + |
| 13 | +print_box("hello") |
| 14 | +``` |
| 15 | + |
| 16 | +In this chapter we'll learn more things we can do with defining |
| 17 | +functions and how they are useful. |
| 18 | + |
| 19 | +## Multiple return values |
| 20 | + |
| 21 | +Function can take multiple arguments, but they can only return one |
| 22 | +value. But sometimes it makes sense to remove multiple values as well: |
| 23 | + |
| 24 | +```py |
| 25 | +def login(): |
| 26 | + username = input("Username: ") |
| 27 | + password = input("Password: ") |
| 28 | + # how the heck are we going to return these? |
| 29 | +``` |
| 30 | + |
| 31 | +The best solution is to just return a tuple of values, and just unpack |
| 32 | +that wherever the function is called: |
| 33 | + |
| 34 | +```py |
| 35 | +def login(): |
| 36 | + ... |
| 37 | + return username, password |
| 38 | + |
| 39 | + |
| 40 | +username, password = login(): |
| 41 | +... |
| 42 | +``` |
| 43 | + |
| 44 | +That gets kind of messy if there are more than three values to return, |
| 45 | +but I have never needed to return more than three values. If you think |
| 46 | +you need to return four or more values you probably want to use [a |
| 47 | +class](../basics/classes.md) instead and store the values like |
| 48 | +`self.thing = stuff`. |
| 49 | + |
| 50 | +## \*args |
| 51 | + |
| 52 | +Sometimes you might see code like this: |
| 53 | + |
| 54 | +```py |
| 55 | +def thing(*args, **kwargs): |
| 56 | + ... |
| 57 | +``` |
| 58 | + |
| 59 | +Functions like this are actually quite easy to understand. Let's make a |
| 60 | +function that takes `*args` and prints it. |
| 61 | + |
| 62 | +```py |
| 63 | +>>> def thing(*args): |
| 64 | +... print("now args is", args) |
| 65 | +... |
| 66 | +>>> thing() |
| 67 | +now args is () |
| 68 | +>>> thing(1, 2, 3) |
| 69 | +now args is (1, 2, 3) |
| 70 | +>>> |
| 71 | +``` |
| 72 | + |
| 73 | +So far we have learned that if we want to call a function like |
| 74 | +`thing(1, 2, 3)`, then we need to define the arguments when defining the |
| 75 | +function like `def thing(a, b, c)`. But `*args` just magically gets |
| 76 | +whatever positional arguments the function is given and turns them into |
| 77 | +a tuple, and never raises errors. Of course, we could also use whatever |
| 78 | +variable name we wanted instead of `args`. |
| 79 | + |
| 80 | +Our function with just `*args` takes no keyword arguments: |
| 81 | + |
| 82 | +```py |
| 83 | +>>> thing(a=1) |
| 84 | +Traceback (most recent call last): |
| 85 | + File "<stdin>", line 1, in <module> |
| 86 | +TypeError: thing() got an unexpected keyword argument 'a' |
| 87 | +>>> |
| 88 | +``` |
| 89 | + |
| 90 | +We can also save our arguments to a variable as a list, and then pass |
| 91 | +them to a function by adding a `*`. Actually it doesn't need to be a |
| 92 | +list or a tuple, anything [iterable](../basics/loops.md#summary) will |
| 93 | +work. |
| 94 | + |
| 95 | +```py |
| 96 | +>>> stuff = ['hello', 'world', 'test'] |
| 97 | +>>> print(*stuff) |
| 98 | +hello world test |
| 99 | +>>> |
| 100 | +``` |
| 101 | + |
| 102 | +## \*\*kwargs |
| 103 | + |
| 104 | +`**kwargs` is the same thing as `*args`, but with keyword arguments |
| 105 | +instead of positional arguments. |
| 106 | + |
| 107 | +```py |
| 108 | +>>> def thing(**kwargs): |
| 109 | +... print('now kwargs is', kwargs) |
| 110 | +... |
| 111 | +>>> thing(a=1, b=2) |
| 112 | +now kwargs is {'b': 2, 'a': 1} |
| 113 | +>>> thing(1, 2) |
| 114 | +Traceback (most recent call last): |
| 115 | + File "<stdin>", line 1, in <module> |
| 116 | +TypeError: thing() takes 0 positional arguments but 2 were given |
| 117 | +>>> def print_box(message, border='*'): |
| 118 | +... print(border * len(message)) |
| 119 | +... print(message) |
| 120 | +... print(border * len(message)) |
| 121 | +... |
| 122 | +>>> kwargs = {'message': "Hello World!", 'border': '-'} |
| 123 | +>>> print_box(**kwargs) |
| 124 | +------------ |
| 125 | +Hello World! |
| 126 | +------------ |
| 127 | +>>> |
| 128 | +``` |
| 129 | + |
| 130 | +Sometimes it's handy to capture all arguments our function takes. We can |
| 131 | +combine `*args` and `**kwargs` easily: |
| 132 | + |
| 133 | +```py |
| 134 | +>>> def thing(*args, **kwargs): |
| 135 | +... print("now args is", args, "and kwargs is", kwargs) |
| 136 | +... |
| 137 | +>>> thing(1, 2, a=3, b=4) |
| 138 | +now args is (1, 2) and kwargs is {'b': 4, 'a': 3} |
| 139 | +>>> |
| 140 | +``` |
| 141 | + |
| 142 | +This is often used for calling a function from another "fake function" |
| 143 | +that represents it. We'll find uses for this later. |
| 144 | + |
| 145 | +```py |
| 146 | +>>> def fake_print(*args, **kwargs): |
| 147 | +... print(*args, **kwargs) |
| 148 | +... |
| 149 | +>>> print('this', 'is', 'a', 'test', sep='-') |
| 150 | +this-is-a-test |
| 151 | +>>> fake_print('this', 'is', 'a', 'test', sep='-') |
| 152 | +this-is-a-test |
| 153 | +>>> |
| 154 | +``` |
| 155 | + |
| 156 | +## Keyword-only arguments |
| 157 | + |
| 158 | +Let's say that we have a function that moves a file. It probably takes |
| 159 | +`source` and `destination` arguments, but it might also take other |
| 160 | +arguments. For example, it might take an `overwrite` argument that makes |
| 161 | +it remove `destination` before copying if it exists already or a |
| 162 | +`backup` argument that makes it do a backup of the file just in case the |
| 163 | +copying fails. So our function would look like this: |
| 164 | + |
| 165 | +```py |
| 166 | +def move(source, destination, overwrite=False, backup=False): |
| 167 | + if overwrite: |
| 168 | + print("deleting", destination) |
| 169 | + if backup: |
| 170 | + print("backing up") |
| 171 | + print("moving", source, "to", destination) |
| 172 | +``` |
| 173 | + |
| 174 | +Then we can move files like this: |
| 175 | + |
| 176 | +```py |
| 177 | +>>> move('file1.txt', 'file2.txt') |
| 178 | +moving file1.txt to file2.txt |
| 179 | +>>> move('file1.txt', 'file2.txt', overwrite=True) |
| 180 | +deleting file2.txt |
| 181 | +moving file1.txt to file2.txt |
| 182 | +>>> |
| 183 | +``` |
| 184 | + |
| 185 | +This works just fine, but if we accidentally give the function three |
| 186 | +filenames, bad things will happen: |
| 187 | + |
| 188 | +```py |
| 189 | +>>> move('file1.txt', 'file2.txt', 'file3.txt') |
| 190 | +deleting file2.txt |
| 191 | +moving file1.txt to file2.txt |
| 192 | +>>> |
| 193 | +``` |
| 194 | + |
| 195 | +Oh crap, that's not what we wanted at all. We have just lost the |
| 196 | +original `file2.txt`! |
| 197 | + |
| 198 | +The problem was that now `overwrite` was `'file2.txt'`, and the |
| 199 | +`if overwrite` part [treated the string as |
| 200 | +True](../basics/what-is-true.md) and deleted the file. That's not nice. |
| 201 | + |
| 202 | +The solution is to change our move function so that `overwrite` and |
| 203 | +`backup` are keyword-only: |
| 204 | + |
| 205 | +```py |
| 206 | +def move(source, destination, *, overwrite=False, backup=False): |
| 207 | + ... |
| 208 | +``` |
| 209 | + |
| 210 | +Note the `*` between `destination` and `overwrite`. It means that the |
| 211 | +arguments after it must be specified as keyword arguments. |
| 212 | + |
| 213 | +Our new move function also makes it impossible to write things like |
| 214 | +`move('file1.txt', 'file2.txt', False, True)`. The problem with calling |
| 215 | +the move function like that is that nobody can guess what it does by |
| 216 | +just looking at it, but it's much easier to guess what |
| 217 | +`move('file1.txt', 'file2.txt', backup=True)` does. |
| 218 | + |
| 219 | +## When should we use these things? |
| 220 | + |
| 221 | +We don't need `*args` and `**kwargs` for most of the functions we write. |
| 222 | +Often functions just do something and arguments are a way to change how |
| 223 | +they do that, and by not taking `*args` or `**kwargs` we can make sure |
| 224 | +that we'll get an error if the function gets an invalid argument. |
| 225 | + |
| 226 | +When we need to make something that takes whatever arguments it's given |
| 227 | +or call a function with arguments that come from a list we need `*args` |
| 228 | +and `**kwargs`, and there's no need to avoid them. |
| 229 | + |
| 230 | +I don't recommend using keyword-only arguments with functions like our |
| 231 | +`print_box`. It's easy enough to guess what `print_box('hello', '-')` |
| 232 | +does, and there's no need to force everyone to do |
| 233 | +`print_box('hello', border='-')`. On the other hand, it's hard to guess |
| 234 | +what `copy('file1.txt', 'file2.txt', True, False)` does, so using |
| 235 | +keyword-only arguments makes sense and also avoids the file deleting |
| 236 | +problem. |
| 237 | + |
| 238 | +*** |
| 239 | + |
| 240 | +You may use this tutorial freely at your own risk. See |
| 241 | +[LICENSE](../LICENSE). |
| 242 | + |
| 243 | +[Previous](../basics/classes.md) | [Next](magicmethods.md) | |
| 244 | +[List of contents](../README.md#advanced) |
0 commit comments