So I'm doing a lot of new stuff in Python, well, it is new to me.
I had a nightmare last night where I was writing code to deal with stuff and couldn't tell if the code was correct or not.
We have had discussions about to what extent it is true that you can be writing totally invalid Python code that couldn't possibly work and not knowing until you executed the exact right lines with exact right data.
Right at the beginning of loading your whole source (a bit weird talking about Python source when normally there are no compiled or linked artifacts on disk, except for .pyc cached byte-code files) it does indeed check for a bunch of gross syntactical violations in modern CPython 3.9, all sorts of stuff like unmatched parenthesis or [ ] or { } or " " or ' '. So what some people were saying about earlier versions of Python that you could have absolute line noise or a sneeze and not know until runtime wasn't quite so true anymore.
However, a disturbingly large category of errors that wouldn't even compile in traditional statically-typed compiled languages can make it all the way thru to
testing, and a missing test case can mean that they make it right on in to production.
Static typing and compiled languages don't reduce this to zero. There are many types of errors that can easily be made in C code, for instance, unwittingly returning a pointer to an about-to-vanish stack variable, attempting to free a piece of static memory, freeing a pointer that you weren't supposed to free there because someone else was still using it to free later, etc. that can cause disaster far away in a large program, with attendant debugging nightmares, despite 'strict error checking at compile time'. Java explicitly set out to make as many of such things literally impossible as problems anymore, mostly but not entirely by garbage collecting everything and having no explicit freeing of memory anywhere. It succeeded admirably for many classes of errors, but there are still places where type erasure with generics or failure to properly override .equals() or use of mutable data types in situations where immutability is required for correctness can bite you, and most of these are covered on the OCJP. Without proper knowledge and attention to these things, it is still possible to make Java code that happily compiles and runs but has invalid stuff in it that isn't just business logic (using the wrong formula to calculate interest or the area of a shape) but actually is using the language in an unsafe way that is destined to cause runtime exceptions, or worse yet, wrong results that look plausible and will only be caught by sufficient testing. This is much more noticeable in multi-threaded code, where the dangers of writing something that compiles but will have, perhaps sporadic deadlocks or even flat-out wrong results is much higher. They have attempted to address this by giving us newer models for multi-threading access, and encouraging more leveraging of immutability and functional styles to eliminate hazards of shared mutable objects being accessed by multiple threads.
Static analyzers for C, C++, C# and Java, which are facilitated by their static nature, can catch incredibly subtle bugs in all of these languages. They are often expensive commercial products for commercial code, but worth it, and are generally free for open source code use -- that works two ways, they generate good will in the open source community and they can develop and improve their analysis code with lots and lots of free real-world test cases. I am a huge fan of employing these for complex code that needs to work correctly.
Back to my nightmare. While you won't often have code running that is grossly syntactically malformed, large classes of logic errors can creep in that might not be seen until runtime with just the right combinations of data to trigger them. There are many things that could happen in Python code, where a user decides to directly mess with something that isn't tightly encapsulated that will cause crashes or huge bugs in behavior of even the best-tested classes. You can say "don't do that" and with some work can basically emulate strong encapsulation using Python language conventions involving names with _ or __ prefixes.
Also, while Python has remained dynamic, it has added opt-in static declarations where anything looking at them can tell whether they contain internal inconsistencies or other code calling them is right or not. It is harder, I believe, to engineer reliable 'linters' (an old term from the earlier days of static analysis that we'd all but stopped using in terms of native code because we'd gone so far beyond that, but whatever) but Python is popular and used for many important things and people are working hard at them. They will often give us the familiar, comforting squiggles in our
IDE's on code that would have 'compiled' and executed just fine until it didn't, allowing us to never check those bugs in in the first place.
Anyway, in my nightmare, I was on some tight deadline for a project, trying to get some tricky new code just right, and had the sick feeling that I didn't know "whether Python even understood" what I was typing. I was going to find out when I did careful and detailed testing, which due to time pressure I questioned whether I was going to even be able to complete properly. The levels of mistakes I was suspecting I was making would have definitely been failed compiles in static languages I have worked successfully in, and the lack of PyLint or PyLance squiggles in my IDE gave me no additional confidence that my code would be correctly interpreted as I wrote it. This was a nightmare, after all, and I was dreaming, but the concern wasn't imaginary. The obvious solution is to have plenty of tests, and to ensure that having all the proper and appropriate tests is part of the definition of done and scheduled in time estimates for completion of any new code or updates or re-factorings. Those are all things that too commonly get shortchanged in too many environments.
Anyway, this is not one way. There are myriad disasters one can easily cause with inattentive, rushed or sloppy native code in C or C++ that are banished forever in both Java and Python. Java can catch countless usage errors that even modern linters would probably miss on Python, and Python won't run your code if you try to place mutable objects in a Set or use them as keys, to give an example of where Python is 'safer' than Java in the hands of the less expert, or more tired or rushed programmer.
Most of the things that seem insane that Python allows you to do can be easily avoided with some discipline. If you don't like the types of variables changing as you move thru your code, well, don't write:
If you think it is horrible that ten different instances of your same class can all have different attributes from each other, rather than just different values for the same attributes of the same type, well, never do THAT.
I remember when I was very first learning C as a kid, the grad students who worked as TA's would say "Ugh--these students think their code is good because it compiles!!" and the first few times I heard it while trying to fix compile errors I was like "better than mine which still doesn't??" but of course they were referring to all the many, many kinds of usage errors that would only be found during proper testing, many of which are no longer possible in Java and Python, others of which still are.
Also talking about various languages people would say "Just because a language has some feature doesn't mean you should use it!" -- which sounded weird to someone who was used to C or Pascal but now makes eminent sense. Whichever language you are using, if there are things that make you less comfortable about the correctness, readability, or reliability of your code, just say "No, thanks."
I was playing with tkinter, noticing that the anchor value seemed not to be a str type but an Enum, as it wasn't in quotes, and wondered how it got that modernized because tkinter long predated Enums in Python.
Of course, it wasn't any Enum type, but a simple constant giving a name for a
string literal. I immediately tried writing NE, SW = SW, NE changing the values of the compass constants and re-running my code. The alignment of everything changed! Well, don't do that!
That happened to be some of the last code I'd written before I went to bed...
I guess nightmares can be avoided in any language with sufficient understanding, judgement and discipline, and encountered without.