is dynamically typed, which means that the type of any object is inferred at the moment when the actual line is interpreted.
In statically typed languages, type is verified more rigorously during compilation. This can give the developer some additional assurance, that certain runtime errors would never occur.
With Python this is not the case, you can clearly pass a string argument to a function that expects a number, and the program will happily fail with error when you actually run it.
To make large software systems more reliable, type annotations were introduced to Python. These not only ensure some level of soundness and sanity in the codebase, but also help the developer, or anyone who uses the code, to understand what sort of arguments are required and what type of result is produced.
In your example func2 expects two strings and returns a string.
You can annotate with more complex types, custom objects and containers, by using the typing library.