Sure, but now you have to rewrite your code to accept a ValidatedPosition as input and not two floats. In Rust that might be "zero cost", but in Python it certainly is not, even if you implement all this in Cython. And even in the context of a Cython/Rust/whatever extension, what if this is calling a BLAS function or some other external procedure that has specific preconditions?
It's usually fine to use the type state pattern as described by sibling commenters, but sometimes it's expensive or infeasible.
Another situation is less related to performance and more related to managing dynamic state. Consider the following annoyance that arises when we try to attach static types to an old-school Python idiom:
Users will end up with a difficult-to-debug auth failure if they forget to call `login` first. Also, Mypy won't catch this without contorting your code.
We naturally would want to add a test case like this:
def test_error_if_not_logged_in():
app_client = AppClient(url="https://api.example.net/v1")
with pytest.raises(RuntimeError):
app_client._post({})
Thus we'll need to guard every single usage of `self._auth_token` with:
if self._auth_token is None:
raise RuntimeError(...)
And there are three usual approaches for handling this:
1. Disregard the article's advice and push the "if" down, into the _post method, to ensure that we only actually need to write that guard condition once.
2. Write a _check_logged_in method that encapsulates the if/raise logic, use that to at least make it easier to apply the guard where needed.
3. Refactor the internals to use something akin to the type state pattern, which is a funky and cool idea that I support. But it could easily end up turning into an "enterprise fizzbuzz" exercise where your little client library expands into a type-driven onion that obfuscates the business logic.
I'm proposing a 4th way:
4. Declare that _post may only be called from inside a specific dynamic context, and ensure that that dynamic context is only established by login().
Is it better? I'm not sure. But I've never seen it before, and it's kind of appealing as I continue to think about it.
Something I've seen a few times in Go and java: Make login() a constructor for the AppClient, or declare what's essentially a factory called "AppClientConfig" with a login() function to create the AppClient.
I've only recently started to think about using factories like that, so I'm very sure there are patterns of dynamic state I wouldn't have a recipe for, but it's a prety appealing idea to me.
It's usually fine to use the type state pattern as described by sibling commenters, but sometimes it's expensive or infeasible.
Another situation is less related to performance and more related to managing dynamic state. Consider the following annoyance that arises when we try to attach static types to an old-school Python idiom:
Users will end up with a difficult-to-debug auth failure if they forget to call `login` first. Also, Mypy won't catch this without contorting your code.We naturally would want to add a test case like this:
Thus we'll need to guard every single usage of `self._auth_token` with: And there are three usual approaches for handling this:1. Disregard the article's advice and push the "if" down, into the _post method, to ensure that we only actually need to write that guard condition once.
2. Write a _check_logged_in method that encapsulates the if/raise logic, use that to at least make it easier to apply the guard where needed.
3. Refactor the internals to use something akin to the type state pattern, which is a funky and cool idea that I support. But it could easily end up turning into an "enterprise fizzbuzz" exercise where your little client library expands into a type-driven onion that obfuscates the business logic.
I'm proposing a 4th way:
4. Declare that _post may only be called from inside a specific dynamic context, and ensure that that dynamic context is only established by login().
Is it better? I'm not sure. But I've never seen it before, and it's kind of appealing as I continue to think about it.