(I'd like to note that I wrote a lot of code in Java and Python, and continue to use each language in its respective strong areas. This isn't meant as a drive-by attack of "Java r00lz, Python sux"; this is an experienced take.)
My real problem with the evolution of Python is that initially, the language and the community was positioned as anti-Java, anti-big-OOP-like-C++, and then it changed into the thing that it was against, but in a roundabout and suboptimal way. To me, the initial vibe of Python was, "write a 100-line script, don't worry about explicitly documenting types, don't worry about grand architecture, don't worry about creating custom classes, don't worry about encapsulation and public/private". I've been with Python since year 2007 in the 2.x days, and Java since 2002.
Initial examples: Why go through the ceremony of `public static void main(String[] args)` when Python just executes the script line by line at the top level? Oh wait, now you have things like `import` actually executing code instead of simply being a compile-time namespace convenience, and you need weird techniques like `if __name__ == "__main__"`. Why `System.out.println()` when `print()` is so much more concise? But now you're polluting the global namespace, and `print(file=sys.stderr)` isn't that elegant either.
Static typing in Python is the biggest hypocrisy ever. As I understood it, Python scripts were meant to be lightweight and free of the tyranny of enterprise OOP which was epitomized by Java. But people found out that keeping track of types in your head is laborious and error-prone, and getting a compiler to check {that the shape of your objects and function calls match} is a huge productivity boost. And so Python 3 enabled static type hints... which, like I said before, Java had from day zero. To make matters worse, static type hint features were introduced progressively over the years, leading to things getting deprecated from the `typing` module and moved to things like `T|None` and `list[T]` and `collections.abc`.
IIRC the old practice in Python was that you specified some kind of interface in prose or in code (e.g. `class IoStream: def read(); def close()`), but you didn't need to explicitly use that interface as a superclass; you can just duck-type your way around things. But this completely goes against static typing, so I'm pretty sure the new preferred way is to explicitly use abstract superclasses... just like Java did all along (and is mandatory).
I really don't think having top-level (module) variables and functions in Python is a good thing, especially because then they are duplicated as fields and methods in classes. In Java, fields and methods (whether static or instance) can only be placed in classes, and I think this particular straitjacket is a good thing.
> because Python wants these things to be optional
We can both agree that Python gives multiple ways to do things (e.g. no static type hints vs. static type hints). This flies in the face of:
> Readability counts.
> The Zen of Python / There should be one-- and preferably only one --obvious way to do it. -- https://peps.python.org/pep-0020/
Probably the most tragic example is the ways to build up strings in Python: `+` and str(), `%` operator, `str.format()`, f-string.
(To be fair, I have a laundry list of complaints about Java too, such as: .class files and the JVM being an intermediate layer that needs to be understood which is actually different from the Java source language, lack of in-place structs so `new Point[]` is very painful on the memory system, awkward string interpolation/formatting compared to Python's f-strings, very awkward JDBC compared to for example Python sqlite3 API, kinda clunky for web server programming, very awkward JSON handling, enterprisey libraries and APIs that are perfectly documented but are impossible to actually understand.)
> so I'm pretty sure the new preferred way is to explicitly use abstract superclasses... just like Java did all along (and is mandatory).
typing.Protocol is a good fit for this use case
Are you ever supposed to inherit from the protocol though(unless you’re defining another protocol)? One of the great things about protocols is that your class doesn’t even have to know about the protocol explicitly. What this code looks closer to doing (style wise) is an abstract base class
> Why go through the ceremony of `public static void main(String[] args)` when Python just executes the script line by line at the top level? Oh wait, now you have things like `import` actually executing code instead of simply being a compile-time namespace convenience, and you need weird techniques like `if __name__ == "__main__"`.
I don't think this is a fair criticism. Python is a scripting language, it makes sense that the code is executed line by line at the top level. This is also how other programming languages from its time like Perl or Bash does it. Even newer scripting languages like Ruby does something similar.
> Why `System.out.println()` when `print()` is so much more concise? But now you're polluting the global namespace, and `print(file=sys.stderr)` isn't that elegant either.
Another criticism that I don't think it is fair. Lots of other languages "polutes" the global namespace. I actually can't think another language other than Java that doesn't. Python at least still allow you to manually `import builtins`, but Go for example AFAIK has no mechanism for you to reference builtins if you end up shadowing them.
Also I find `print(file=sys.stderr)` pretty much elegant, it works exactly how I would expect, it also means I can open a file and write to it using `print`.
> And so Python 3 enabled static type hints... which, like I said before, Java had from day zero.
Again, I don't think this is fair. I find Python 3 type-hints much more powerful than whatever type system Java has, especially because Python has Option types that actually make the type system useful (Java is infamous for its NullPointerException for a reason).
> Static typing in Python is the biggest hypocrisy ever
Yes, agreed. I used to work on a large python codebase and tried to add type hints where I could. The issue is that python was not the right tool for the job - except that switching to the right tool was a non-starter. So type hints were the best I could do.
It is indeed a significant undertaking. But... it is doable. I've worked on a code base that converted several functionalities to golang. It did take a lot of effort and quite a lot of planning.
Let it out co-internet programmer. It’s ok. The pain is real. We both love and hate our languages. It’s natural and healthy.