> what it has is "type hints" which is way to have richer integration with type checkers and your IDE, but will never offer more than that as is
Python is strongly typed and it's interpreter is type aware of it's variables, so you're probably overreaching with that statement. Because Python's internals are type aware, it's how folks are able to create type checkers like mypy and pydantic both written in Python. Maybe you're thinking about TS/JSDoc, which is just window dressing for IDEs to display hints as you described?
I don't think you can say that a language is strongly typed if only the language's internals are. The Python interpreter prevents you from summing an integer to a string, but only at runtime when in many cases it's already too late. A strongly typed language would warn you much sooner.
Your example is bang on when describing a "strongly typed" language. That said, strongly typed is different from "static typing", which is what you described later in your post. Python is both strongly typed and dynamically typed. It is all rather confusing and just a big bowl of awful. I have to look up if I haven't referenced it in a while, because the names are far too similar and there aren't even good definitions around some of the concepts.
Then we have very different ideas of what proper typing is :D
Look at this function, can you tell me what it does?
def plus(x, y):
return x+y
If your answer is among the lines of "It returns the sum x and y" then I would ask you who said that x and y are numbers. If these are strings, it concatenates them. If instead you pass a string and a number, you will get a runtime exception. So not only you can't tell what a function does just by looking at it, you can't even know if the function is correct (in the sense that will not raise an exception).
Python types are strictly specified, but also dynamic. You don't need static types in order to have strict types, and indeed just because you've got static types (in TS, for example) doesn't mean you have strict types.
A Python string is always a string, nothing is going to magically turn it into a number just because it's a string representation of a number. The same (sadly) can't be said of Javascript.
There's no magic going on here, just an attribute lookup. It's still possible to write terrible Python code -- as it is in any language -- and the recommendation is still "don't write terrible code", just as it is in any language. You don't have to like it, but not liking it won't make it any different.
The older I get, the more I like writing statically-typed code. I wrote a lot more Python (for my own use) in my youth, and tend towards Rust nowadays. Speaking of which: if you dislike the dynamic typing of Python then you must hate the static typing of Rust -- what does
fn add<T:Add<U>, U>(a: T, b: U) -> T::Output { a + b }
Yeah and even with static typing, a string can be many things. Some people even wrap their strings into singleton structs to avoid something like sending a customerId string into a func that wants an orderId string, which I think is overkill. Same with int.
It's very hard to write long-running daemons in python partially for this reason, when you make a typo on a variable or method name in an uncommon code path.
It can matter also in practice. Once I was trying some Python ML model to generate images. My script ran for 20 minutes to then terminate with an exception when it arrived at the point of saving the result to a file. The reason is that I wanted to concatenate a counter to the file name, but forgot to wrap the integer into a call to str(). 20 minutes wasted for an error that other languages would have spotted before running the script.
> you can't tell what a function does just by looking at it
You just did tell us what it does by looking at it, for the 90% case at least. It might be useful to throw two lists in there as well. Throw a custom object in there? It will work if you planned ahead with dunder add and radd. If not fix, implement, or roll back.
> You just did tell us what it does by looking at it, for the 90% case at least
The problem is that you can't know if the function is going to do what you want it to do without also looking at the context in which it is used. And what you pass as input could be dependent on external factors that you don't control. So I prefer the languages that let me know what happens in 100% of the cases.
> Protection from untrusted input is something that has to be considered in any language
Sure, but some languages make it easier than others. And that was just one example, another example could be having a branch where the input to your function depends on some condition. You could have one of the two branches passing the wrong types, but you will only notice when that branch gets executed.
It never happened to me, because I don't use Python ;)
On a more serious note, your comment actually hints at an issue: unit testing is less effective without static type checking. Let's assume I would like to sum x and y. I can extensively test the function and see that it indeed correctly sums two numbers. But then I need to call the function somewhere in my code, and whether it will work as intended or not depends on the context in which the function is used. Sometimes the input you pass to a function depends from some external source outside your control, an if that's the case you have to resort to manual type checking. Or use a properly typed language.
This isn't an actual problem people encounter in unit testing, partially because you test your outer interfaces first. Also, irl static types often get so big and polymorphic that the context matters just as much.
And if you specify that they are numbers then you lose the ability of the function to generalize to vectors.
Indeed assuming it adds two things is correct, and knowing that concatenation is how Python defines adding strings is important for using the language in the intended way.
what it has is "type hints" which is way to have richer integration with type checkers and your IDE, but will never offer more than that as is