aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps/content/resources/guides/python-guides/parameters-and-arguments.md
blob: 45ad60b12863d195334f6643ec5fafe3548f925f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
---
title: Function Parameters and Arguments in Python
description: An in-depth look at function parameters and arguments, and how to use them.
toc: 1
---

A Python function is utilised in two steps:

1. The function definition/signature (used just once).
2. The function invocation/call (used many times).

The function definition uses parameters, whereas the function call uses arguments:

```python
def foo(this_is_a_parameter):
    print(this_is_a_parameter)

foo(this_is_an_argument)
```

An important detail to be aware of is that by default any argument used to call a function in Python can be used as both a positional and a keyword argument—just not at the same time.
A function call may contain a mixture of positional and keyword arguments, and—unless otherwise specified—an argument can reference the parameters in the function definition positionally, or by name (keyword).

# Positional Arguments

```python
def foo(a, b, c):
    print(a, b, c)

>>> foo(1, 2, 3)
1 2 3
```

In the above function definition we have three parameters `a`, `b`, and `c`.

When we invoke the function with the arguments `1`, `2`, and `3`, the function will map these values in the exact order given to the parameters in the function definition.
With no keyword reference given they become positional arguments.

# Keyword Arguments

```python
def foo(a, b, c):
    print(a, b, c)

>>> foo(1, 2, 3)
1 2 3

>>> foo(c=3, b=2, a=1)
1 2 3
```

As you can see, `foo(1, 2, 3)` and `foo(c=3, b=2, a=1)` are identical.
Referencing a function parameter by its name means that we are using a keyword argument.
The order in which keyword arguments are given does not matter.

# Mixing Positional and Keyword Arguments

So what happens if we want to mix the positional argument mapping with keyword arguments?

Python prioritises the mapping of positional arguments to their parameter names before the mapping of keywords.

```python
def foo(a, b, c):
    print(a, b, c)

>>> foo(1, c=3, b=2)
1 2 3
```

Passing a keyword argument using the name of a parameter that has already been given will not work:

```python
>>> foo(1, 2, a=3)
TypeError: foo() got multiple values for argument 'a'

>>> foo(1, b=2, b=3)
SyntaxError: keyword argument repeated
```

Attempting to pass positional arguments after a keyword argument will also not work:

```python
>>> foo(a=1, 2, 3)
SyntaxError: positional argument follows keyword argument
```

# Default Parameter Values

Although the syntax is similar, these are not to be confused with keyword arguments.<br>
Default parameter values appear within the function definition and allow us to conveniently set a default value. This means that if any argument is omitted, its default value will be used as the argument.

```python
def foo(a=0, b=0, c=0):
    print(a, b, c)

>>> foo()
0 0 0

>>> foo(1, 2, 3)
1 2 3

>>> foo(c=3, b=2)
0 2 3

>>> foo(1, c=3)
1 0 3
```

Using default parameter values does not change how a function can be invoked with arguments:

```python
>>> foo(1, 2, a=3)
TypeError: foo() got multiple values for argument 'a'

>>> foo(1, b=2, b=3)
SyntaxError: keyword argument repeated

>>> foo(a=1, 2, 3)
SyntaxError: positional argument follows keyword argument
```

You must specify any parameters without a default value before those with default values:

```python
def foo(a=0, b):
        ^
SyntaxError: non-default argument follows default argument
```

# Positional-only Parameters
[Python 3.8](https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters) / [PEP 570](https://www.python.org/dev/peps/pep-0570/) introduces the possibility to specify which parameters are required to be positional-only via a bare `/` parameter within a function definition.

```python
def foo(a=0, b=0, /, c=0, d=0):
    print(a, b, c, d)
```

The parameters defined before the bare `/` are now considered to be positional-only and keyword mapping will no longer work on them.<br>
In the above function definition `a` and `b` are now positional-only parameters.

These function calls will still work:

```python
>>> foo()
0 0 0 0

>>> foo(1)
1 0 0 0

>>> foo(1, 2, 3, 4)
1 2 3 4

>>> foo(1, 2, d=4, c=3)
1 2 3 4

>>> foo(1, d=4, c=3)
1 0 3 4

>>> foo(c=3, d=4)
0 0 3 4
```

However, attempting to pass keyword arguments for `a` or `b` will fail:

```python
>>> foo(1, b=2, c=3, d=4)
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'b'
```

### Q: Why is this useful?

#### Keyword Argument Freedom

Passing a keyword argument using the name of a parameter that has already been given will not work.
This becomes an issue if we require keyword arguments that use the same parameter names as defined in the function signature, such as via callback functions.

```python
def foo(a, **kwargs):
    print(a, kwargs)

>>> foo(a=1, a=2)
SyntaxError: keyword argument repeated

>>> foo(1, a=2)
TypeError: foo() got multiple values for argument 'a'
```

#### Backwards Compatibility

Because Python allows that an argument by default can be either positional or keyword, a user is free to choose either option.
Unfortunately, this forces the author to keep the given parameter names as they are if they wish to support backwards compatibility, as changing the parameter names can cause dependent code to break.
Enforcing positional-only parameters gives the author the freedom to separate the variable name used within the function from its usage outside of it.

```python
def calculate(a, b):
    # do something with a and b

>>> calculate(1, 2)
```

A user could call this function using `a` or `b` as keywords, which the author may have not intended:

```python
>>> calculate(a=1, b=2)
```

However, by using `/`, the user will no longer be able to invoke using `a` or `b` as keywords, and the author is also free to rename these parameters:

```python
def calculate(x, y, /):
    # do something with x and y

>>> calculate(1, 2)
```

# Keyword-only Parameters

Similarly to enforcing positional-only parameters, we can also enforce keyword-only parameters using a bare `*` parameter.
The parameters defined after the bare `*` are now considered to be keyword-only.

```python
def foo(a=0, b=0, /, c=0, *, d=0):
    print(a, b, c, d)

>>> foo()
0 0 0 0

>>> foo(1, 2, 3)
1 2 3 0

>>> foo(1, 2, d=4, c=3)
1 2 3 4

>>> foo(1, d=4, c=3)
1 0 3 4
```

Although `c` can be either a positional or keyword argument, if we attempt to pass `d` as a non-keyword argument, it will fail:

```python
>>> foo(1, 2, 3, 4)
TypeError: foo() takes from 0 to 3 positional arguments but 4 were given
```

At least one named parameter must be provided after a bare `*` parameter.
Writing a function definition similar to what is shown below would not make sense, as without the context of a named parameter the bare `*` can simply be omitted.

```python
def foo(a=0, *, **kwargs):
                ^
SyntaxError: named arguments must follow bare *
```

### Q: Why is this useful?

The main benefit of using keyword-only parameters is when they are used together with positional-only parameters to remove ambiguity.

However, it may sometimes also be desirable to use keyword-only arguments on their own.<br>
If we were to expose a function as part of an API, we may want the parameter names to carry explicit meaning.

Without using keyword names when invoking the function it can be unclear as to what the  provided arguments are for.
Additionally, a user could also choose to interchange positional arguments with keyword arguments, which can potentially add to the confusion.

```python
def update(identity=None, name=None, description=None):
    # handle the parameters

>>> update("value 1", "value 2", "value 3")

>>> update(1234, "value 1", description="value 2")
```

Enforcing the keyword names is clearer, as it carries context without needing to look at the function definition:

```python
def update(*, identity=None, name=None, description=None):
    # handle the parameters

>>> update(identity=1234, name="value 1", description="value 2")
```

# Summary

* Unless otherwise specified, an argument can be both positional and keyword.
* Positional arguments, when provided, must be in sequence.
* Positional arguments must be used before keyword arguments.
* Keyword arguments may be in any order.
* A default parameter value is used when the argument is omitted.
* A bare `/` used as a parameter in a function definition enforces positional-only parameters to its left.
* A bare `*` used as a parameter in a function definition enforces keyword-only parameters to its right.