diff --git a/README.md b/README.md index 0e7c467..311f84a 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/python/strictly-separate-positional-and-keyword-arguments.md b/python/strictly-separate-positional-and-keyword-arguments.md new file mode 100644 index 0000000..aca98d3 --- /dev/null +++ b/python/strictly-separate-positional-and-keyword-arguments.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 + 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_.