Some interesting responses here, thanks. Not surprisingly lots of people are saying "types would have saved you". I'm sure they would have helped, though I personally think they do introduce a high cost too, and I'm a lisp-head nowadays not a Haskeller.
The real culprits, thinking about it since my post, were side effects and mutability. Python lets you create incredibly difficult to trace chains of side effects and mutations, and has basically no decent ways to prevent this. In Scheme, my code is still super small and I can dynamically create all kinds of object like things, but if I want private, I can make it god-damned private. In Python, anything can be changed by anything ("we're all consenting adults") and using side effects in weird ways is actually part of the idiom. I don't know how many times in Python literature I've seen some variant of "you don't need those baroque patterns because we can use 'import' as a singleton, running class initialization as the constructor". And so all the frameworks have crazy thread-local magic happening from bloody import statements!!! Do that too much and you have no idea what's happening where and why, and something as trivial as changing the order of imports can kill your app. Where I was, this had gotten so bad that the app couldn't even be turned on and tested in the normal way, and none of my predecessors had been willing to go through the pain of figuring out what the chain of imports were doing to bugger it up. (Because that didn't look like doing anything productive, I'm sure you all know the drill...)
If I were doing it again, personally, I'd use Clojure and Spec, and worry more about mutability and side-effects than anything else. Just my two cents Canadian.
Thanks for making this point. People focus on types but it's the whole smorgasboard of modern features missing from Python - lack of proper (idiomatic) support for functional programming, None types, etc etc.
The real culprits, thinking about it since my post, were side effects and mutability. Python lets you create incredibly difficult to trace chains of side effects and mutations, and has basically no decent ways to prevent this. In Scheme, my code is still super small and I can dynamically create all kinds of object like things, but if I want private, I can make it god-damned private. In Python, anything can be changed by anything ("we're all consenting adults") and using side effects in weird ways is actually part of the idiom. I don't know how many times in Python literature I've seen some variant of "you don't need those baroque patterns because we can use 'import' as a singleton, running class initialization as the constructor". And so all the frameworks have crazy thread-local magic happening from bloody import statements!!! Do that too much and you have no idea what's happening where and why, and something as trivial as changing the order of imports can kill your app. Where I was, this had gotten so bad that the app couldn't even be turned on and tested in the normal way, and none of my predecessors had been willing to go through the pain of figuring out what the chain of imports were doing to bugger it up. (Because that didn't look like doing anything productive, I'm sure you all know the drill...)
If I were doing it again, personally, I'd use Clojure and Spec, and worry more about mutability and side-effects than anything else. Just my two cents Canadian.