Update: Just noticed you said type and not typo related! Still use strict does catch a lot of silly errors that unfortunately do slip into code with other dynamic languages.
Please don't assume that what was written was wrong, especially when it wasn't.
In python you just print
The "..." part means that the function has to figure out what the types of all the arguments are. printf accomplishes this by, at runtime, parsing the string format and deciding what types to use.
Contrast with a string representation function from a language like Haskell: show::(Show a) => a -> String
No matter what argument is provided to the function show, as long as it observes the type constraint (Show a), a sensible result will be returned. Since it is known at compile time whether the argument observes the class constraint, there's never a surprise that value is printed incorrectly.
So an application, say a web application in Python using some framework, what kind of type-related coding mistakes could you make? And which ones would be subtle enough to only be caught after it's in the customer's hands? (assuming a reasonable minimum of testing before that)
I'm asking not out of disbelief, but just because I'm having a real hard time coming up with an example that makes me "yeah I'd fall for that and only realize until after I delivered".
Is it possible that maybe, just general good coding style in Python makes it so that either you don't even consider sending a `str` to a function that expects a `float`, or that if you do (something like that) it fails almost immediately with a run-time Exception, way before you think of delivering?
For most of my Python programs, once they're done, if you'd analyze them, the types sent over all code paths probably would be static :) I'd think it pretty strange if a program would suddenly call functions with different parameter types, depending on user-input, unless you actively decided that it should do so by design (like something that handles arbitrary JSON-like structures), in which case you probably intend to restrict the amount of types it will be intended to handle.
So in that sense, you end up with what is mostly a statically typed program in the end, even if written in a dynamic language, no?
One thing I do agree that if your program is written dynamically typed and intends to keep on being very dynamic about its typing even after it's done during run-time then, yes, it's probably almost impossible to make sure it does not crash or throw exceptions, given arbitrary user input. But in that case, often this is expected, and not the kind of program you'd consider an end-user application, but rather a sort of tool script thing that can indeed crash on unexpected input. Which is also useful :)
So, and correct me if I'm wrong, but I'd conclude that the difference is that with dynamic typing, there's less red tape during development. But a well written program intended for end users probably ends up with mostly static types de facto because otherwise it'd be fairly unpredictable. However, if you'd have used a statically typed language, you would not have to assume "well written" because the static typing brings guarantees with it.
One hidden (and seldom discussed) feature of static type systems is that in multi-team projects, they can enforce and document abstraction layers between teams. I've even seen static type systems act as impartial arbiters when an argument arises as to if a given change broke one side of an abstraction barrier, or if the change merely exposed a latent silent bug on the other side of the abstraction barrier.
I can write a 500 line C# program and it works reliably first time with no crashes, type inference errors or framework exceptions thrown. The only errors will be algorithmic or functional (i.e. based on requirements).
The same is not true with python.
I have the same experience with both. Go figure.
What's it like after that testing? How's the long term maintainability? I'm much more interested in possible problems - and it could go either way - in the long term than before I've finished writing it.
I tend not to write oodles of test cases up front in favour of simple scenario based tests applied later on. I add pre/post/invariant-condition checks in the code as I write it. It is tested incrementally by hand.
The condition checks prevent the what if's and tell you why something broke. The scenario tests ensure that it does what is asked of it.
I rarely get bugs raised against my code (7 this year out of about 112,000 lines of c# written). Not bad!