Skip to content

Commit 8c53007

Browse files
committed
function stuff
1 parent b7d5ecb commit 8c53007

File tree

1 file changed

+88
-62
lines changed

1 file changed

+88
-62
lines changed

advanced/functions.md

Lines changed: 88 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Advanced things with functions
22

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
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
55
that have the same name. Like this:
66

77
```py
@@ -13,12 +13,12 @@ def print_box(message, border='*'):
1313
print_box("hello")
1414
```
1515

16-
In this chapter we'll learn more things we can do with defining
16+
In this chapter we'll learn more things we can do with defining
1717
functions and how they are useful.
1818

1919
## Multiple return values
2020

21-
Function can take multiple arguments, but they can only return one
21+
Function can take multiple arguments, but they can only return one
2222
value. But sometimes it makes sense to remove multiple values as well:
2323

2424
```py
@@ -28,22 +28,22 @@ def login():
2828
# how the heck are we going to return these?
2929
```
3030

31-
The best solution is to just return a tuple of values, and just unpack
32-
that wherever the function is called:
31+
The best solution is to return a tuple of values, and just unpack that
32+
wherever the function is called:
3333

3434
```py
3535
def login():
3636
...
3737
return username, password
3838

3939

40-
username, password = login():
40+
username, password = login()
4141
...
4242
```
4343

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
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
4747
class](../basics/classes.md) instead and store the values like
4848
`self.thing = stuff`.
4949

@@ -56,58 +56,58 @@ def thing(*args, **kwargs):
5656
...
5757
```
5858

59-
Functions like this are actually quite easy to understand. Let's make a
59+
Functions like this are actually quite easy to understand. Let's make a
6060
function that takes `*args` and prints it.
6161

6262
```py
6363
>>> def thing(*args):
6464
... print("now args is", args)
65-
...
65+
...
6666
>>> thing()
6767
now args is ()
6868
>>> thing(1, 2, 3)
6969
now args is (1, 2, 3)
70-
>>>
70+
>>>
7171
```
7272

7373
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
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
7777
a tuple, and never raises errors. Of course, we could also use whatever
7878
variable name we wanted instead of `args`.
7979

80-
Our function with just `*args` takes no keyword arguments:
80+
Our function with nothing but `*args` takes no keyword arguments:
8181

8282
```py
8383
>>> thing(a=1)
8484
Traceback (most recent call last):
8585
File "<stdin>", line 1, in <module>
8686
TypeError: thing() got an unexpected keyword argument 'a'
87-
>>>
87+
>>>
8888
```
8989

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
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
9393
work.
9494

9595
```py
9696
>>> stuff = ['hello', 'world', 'test']
9797
>>> print(*stuff)
9898
hello world test
99-
>>>
99+
>>>
100100
```
101101

102102
## \*\*kwargs
103103

104-
`**kwargs` is the same thing as `*args`, but with keyword arguments
104+
`**kwargs` is the same thing as `*args`, but with keyword arguments
105105
instead of positional arguments.
106106

107107
```py
108108
>>> def thing(**kwargs):
109109
... print('now kwargs is', kwargs)
110-
...
110+
...
111111
>>> thing(a=1, b=2)
112112
now kwargs is {'b': 2, 'a': 1}
113113
>>> thing(1, 2)
@@ -118,49 +118,50 @@ TypeError: thing() takes 0 positional arguments but 2 were given
118118
... print(border * len(message))
119119
... print(message)
120120
... print(border * len(message))
121-
...
121+
...
122122
>>> kwargs = {'message': "Hello World!", 'border': '-'}
123123
>>> print_box(**kwargs)
124124
------------
125125
Hello World!
126126
------------
127-
>>>
127+
>>>
128128
```
129129

130-
Sometimes it's handy to capture all arguments our function takes. We can
130+
Sometimes it's handy to capture all arguments our function takes. We can
131131
combine `*args` and `**kwargs` easily:
132132

133133
```py
134134
>>> def thing(*args, **kwargs):
135135
... print("now args is", args, "and kwargs is", kwargs)
136-
...
136+
...
137137
>>> thing(1, 2, a=3, b=4)
138138
now args is (1, 2) and kwargs is {'b': 4, 'a': 3}
139-
>>>
139+
>>>
140140
```
141141

142-
This is often used for calling a function from another "fake function"
142+
This is often used for calling a function from another "fake function"
143143
that represents it. We'll find uses for this later.
144144

145145
```py
146146
>>> def fake_print(*args, **kwargs):
147147
... print(*args, **kwargs)
148-
...
148+
...
149149
>>> print('this', 'is', 'a', 'test', sep='-')
150150
this-is-a-test
151151
>>> fake_print('this', 'is', 'a', 'test', sep='-')
152152
this-is-a-test
153-
>>>
153+
>>>
154154
```
155155

156156
## Keyword-only arguments
157157

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:
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 that customize how it moves the file. For example, it might
161+
take an `overwrite` argument that makes it remove `destination` before
162+
moving if it exists already or a `backup` argument that makes it do a
163+
backup of the file just in case the moving fails. So our function would
164+
look like this:
164165

165166
```py
166167
def move(source, destination, overwrite=False, backup=False):
@@ -179,61 +180,86 @@ moving file1.txt to file2.txt
179180
>>> move('file1.txt', 'file2.txt', overwrite=True)
180181
deleting file2.txt
181182
moving file1.txt to file2.txt
182-
>>>
183+
>>>
183184
```
184185

185-
This works just fine, but if we accidentally give the function three
186+
This works just fine, but if we accidentally give the function three
186187
filenames, bad things will happen:
187188

188189
```py
189190
>>> move('file1.txt', 'file2.txt', 'file3.txt')
190191
deleting file2.txt
191192
moving file1.txt to file2.txt
192-
>>>
193+
>>>
193194
```
194195

195-
Oh crap, that's not what we wanted at all. We have just lost the
196+
Oh crap, that's not what we wanted at all. We have just lost the
196197
original `file2.txt`!
197198

198199
The problem was that now `overwrite` was `'file2.txt'`, and the
199200
`if overwrite` part [treated the string as
200201
True](../basics/what-is-true.md) and deleted the file. That's not nice.
201202

202-
The solution is to change our move function so that `overwrite` and
203+
The solution is to change our move function so that `overwrite` and
203204
`backup` are keyword-only:
204205

205206
```py
206207
def move(source, destination, *, overwrite=False, backup=False):
207208
...
208209
```
209210

210-
Note the `*` between `destination` and `overwrite`. It means that the
211-
arguments after it must be specified as keyword arguments.
211+
The `*` between `destination` and `overwrite` means that `overwrite` and
212+
`backup` must be given as keyword arguments.
213+
214+
```py
215+
>>> move('file1.txt', 'file2.txt', overwrite=True)
216+
deleting file2.txt
217+
moving file1.txt to file2.txt
218+
>>> move('file1.txt', 'file2.txt', True)
219+
Traceback (most recent call last):
220+
File "<stdin>", line 1, in <module>
221+
TypeError: move() takes 2 positional arguments but 3 were given
222+
>>>
223+
```
212224

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.
225+
As you can see, our new move function also forces everyone to use it
226+
like `move('file1.txt', 'file2.txt', overwrite=True)` instead of
227+
`move('file1.txt', 'file2.txt', True)`. That's good because it's hard to
228+
guess what a positional `True` does, but it's easy to guess what
229+
`overwrite=True` does.
218230

219231
## When should we use these things?
220232

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
233+
There's nothing wrong with returning a tuple from a function, and you
234+
are free to do that whenever you need it.
235+
236+
We don't need `*args` and `**kwargs` for most of the functions we write.
237+
Often functions just do something and arguments are a way to change how
238+
they do that, and by not taking `*args` or `**kwargs` we can make sure
224239
that we'll get an error if the function gets an invalid argument.
225240

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`
241+
When we need to make something that takes whatever arguments it's given
242+
or call a function with arguments that come from a list we need `*args`
228243
and `**kwargs`, and there's no need to avoid them.
229244

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.
245+
I don't recommend using keyword-only arguments with functions like our
246+
`print_box`. It's easy enough to guess what `print_box('hello', '-')`
247+
does, and there's no need to deny that. On the other hand, it's much
248+
harder to guess what `move('file1.txt', 'file2.txt', True, False)` does,
249+
so using keyword-only arguments makes sense and gets rid of the file
250+
deleting problem.
251+
252+
## Summary
253+
254+
- If you want to return multiple values from a function you can return
255+
a tuple.
256+
- Defining a function that takes `*args` as an argument makes `args` a
257+
tuple of positional arguments. `**kwargs` is the same thing with
258+
dictionaries and keyword arguments.
259+
- Adding a `*` in a function definition makes all arguments after it
260+
keyword-only. This is useful when using positional arguments would
261+
look implicit, like the True and False in `move('file1.txt',
262+
'file2.txt', True, False)`.
237263

238264
***
239265

0 commit comments

Comments
 (0)