This whole situation begs the next query: Will we indeed need all those type hints, static type checkers, and runtime type checkers?
I’m not going to reply to this query. This is especially because I’m removed from being one among those individuals who think they know every little thing about… well, about every little thing, or almost every little thing. But I hope to ask you to take into consideration this yourself.
I’ll, nonetheless, remind you — and myself — that Python’s dynamic typing, also called duck typing, lies behind the success of this language. Below is the favored explanation of how duck typing works:
If it walks like a duck and it quacks like a duck, then it should be a duck.
Duck typing could be very powerful without type hints and runtime type checking. I’ll show you this on quite simple examples, and without further ado, let’s jump into this straightforward example:
Here, we’re checking varieties of x
and y
, and each must be strings (str
). Note that this fashion, we’re form of checking whether what we offer to the str.join()
method is tuple[str, str]
. Actually, we don’t have to examine if this method gets a tuple, since we’re creating it ourselves; enough to examine the varieties of x
and y
. When either of them just isn’t a string, the function will raise TypeError
with an easy message: "Provide a string!"
.
Great, isn’t it? We’re secure that the function shall be run only on values of correct types. If not, we’ll see a customized message. We could also use a custom error:
Now, let’s remove the kind check and see how the function works:
Ha. It appears to be working in quite the same way… I mean, an exception is raised principally in the identical place, so we’re not risking anything. So…
Indeed, here the function foo_no_check()
uses duck typing, which uses an idea of implicit types. On this very example, the str.join()
method assumes it takes a tuple of strings, so each x
and y
must be strings, and in the event that they aren’t, the implicit type for tuple[str, str]
has not been implemented. Hence the error.
You can say: “But hey! Take a look at the message! Before, we could use a custom message, and now we are able to’t!”
Can’t we indeed? Look:
We will now see each messages: the built-in (sequence item 1: expected str instance, in found
) and custom (Provide string!
).
You can ask: What’s the difference? So, I check types. What’s the issue?
Well, there is quite a difference: performance. Let’s benchmark the three versions of the function, using the perftester
package:
Listed below are the benchmarks:
For all benchmarks in this text, I used Python 3.11 on a Windows 10 machine, in WSL 1, 32GM of RAM and 4 physical (eight logical) cores.
Within the second line, I set the default variety of experiments to 10, and inside each run, each function it to be run 100 million times. We take the perfect out of the ten runs, and report the mean time in seconds.
The foo()
function, so the one with the runtime type checks, is significantly slower than the opposite two. The foo_no_check()
function is the fastest, although foo_no_check_tryexcept()
is barely a bit of slower.
Conclusion? Runtime type checks are expensive.
You can say: “What? Are you kidding me? Expensive? It’s only a minor a part of a second! Not even a microsecond!”
Indeed. It’s not much. But this can be a quite simple function with only two checks. Now imagine an enormous code base, with many classes, methods and functions — and a looooot of runtime type checks. Sometimes, this may increasingly mean a big decrease in performance.
Runtime type checks are expensive.
When reading about duck typing, you’ll normally see examples with cats that meow, and dogs that don’t, and cows that moo. Once you hear an animal meowing, it’s neither a dog nor a cow, it’s a cat. But not a tiger. I made a decision to make use of an atypical example, and I hope it was clear enough so that you can see the strengths of duck typing.
As you see, Python exception handling does an excellent job in runtime type checking. You may help it by adding additional type checks when needed, but all the time do not forget that they may add some overhead time.
Conclusion? Python has great exception-handling tools that work quite well. Oftentimes, we should not have to make use of runtime type checking in any respect. Sometimes, nonetheless, we may have to. When two types have similar interfaces, duck typing can fail.
As an illustration, imagine you ought to add two numbers (x + y
), however the user provided two strings. It will not mean an error because you’ll be able to add two strings. In such instances, you might must add a runtime type check, for those who don’t want this system to proceed with these incorrect values. In the end it might probably break anyway, so the query is whether or not you would like this system to proceed until then or not. If yes, you might risk an exception shall be raised much later, so adding a sort check can actually help save time. As well as, raising the exception later in this system flow can mean difficulties find a real reason behind the error.
All in all, I are not looking for to inform you that runtime type checking should never be used. But oftentimes, such checks are added after they should not needed.