Add Strictly Separate Positional And Keyword Arguments as a Python TIL

This commit is contained in:
jbranchaud
2026-07-20 11:32:17 -05:00
parent 1a83ccb852
commit 21132e9640
2 changed files with 54 additions and 1 deletions
+2 -1
View File
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1825 TILs and counting..._
_1826 TILs and counting..._
See some of the other learning resources I work on:
@@ -1104,6 +1104,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Sort Normalized Version Of Data](python/sort-normalized-version-of-data.md)
- [Start The Debugger When A Test Errors](python/start-the-debugger-when-a-test-errors.md)
- [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md)
- [Strictly Separate Positional And Keyword Arguments](python/strictly-separate-positional-and-keyword-arguments.md)
- [Test A Function With Pytest](python/test-a-function-with-pytest.md)
- [Turn Method Into Cached Property On Class Instance](python/turn-method-into-cached-property-on-class-instance.md)
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
@@ -0,0 +1,52 @@
# Strictly Separate Positional And Keyword Arguments
Typically when I define a function with arguments in Python, I can choose pass
the arguments to that function as positional or keyword arguments. I can even
mix and match as long as all positional arguments come before all keyword
arguments.
But what if I want to enforce a strict boundary between what arguments are
positional and what arguments are keyword?
By combining the _positional-only marker_ (`/`) and _keyword-only marker_ (`*`),
I can get exactly that effect.
Here I have defined a `connect` function that takes `host` and `port` arguments.
Because both appear before the `/` marker, they must be passed as positional. I
also have a `timeout` argument with a default. Because `timeout` comes after the
`*` marker, it must be passed as a keyword argument.
```python
def connect(host, port, /, *, timeout=30):
print(f"Connecting to #{host}:#{port}")
print(f" Timeout: {timeout}s")
# ...
```
Let's see it in action.
```python
>>> connect("localhost", 3000, timeout=20)
Connecting to #localhost:#3000
Timeout: 20s
>>> connect(host="localhost", port=4000)
Traceback (most recent call last):
File "/Users/lastword/dev/misc/python-experiments/arguments.py", line 37, in <module>
connect(host="localhost", port=4000)
TypeError: connect() got some positional-only arguments passed as keyword arguments: 'host, port'
```
This second attempt of calling `connect` with `host` and `port` as keyword
arguments presents as a runtime `TypeError` because they were expected as
"positional-only arguments".
In my editor that line also presents with two static typing errors `call-arg:
Unexpected keyword argument "port" for "connect"` (and the equivalent for
`host`).
Note: I did some additional reading just now in [Python in a Nutshell, 4th
Edition](https://www.oreilly.com/library/view/python-in-a/9781098113544/) and I
see that it is more appropriate to call them _Named Arguments_ instead of
_Keyword Arguments_.