As a user rather (than designer) of Python there are three reasons I'm happy with this decisions:
Chiefly, it reads better. Of course I have to consciously decide to introduce a new variable when writing, but code is read far more often than it is written. A lot of code is simple in the sense that control flow and nesting has no influence on the meaning and existence of variables. When I read
it = Snark()
message = "A letter, not signed"
if random.random() > 0.5:
message += " (by the Knave)"
return [it, message]
then I usually don't really care which (if any) mention of a name first introduces it, I just care that it's there and what happens to it.
Note that a significant body of pseudo code uses the same convention (no declaration, implicit introduction via assignment), and Python is often described as executable pseudo code, only half-jokingly. Four extra characters for every local variable does, to my spoiled eyes, feel like significant clutter in the context of Python, especially when it doesn't feel like it adds any value (see below); this would be lessened by a
:= operator as in Go.
Second, I have yet to encounter a typo bug (due to this specific feature) that didn't also show itself in another clear manner. Maybe I'll change my mind in a few more years when I've encountered some bugs of this kind, but so far it's way, way down on my list of things that cause bugs. There's a fair number of questions about
UnboundLocalError and it's a bit tricky to explain to beginners, but the frequency and popularity of those questions, compared to other gotchas (mutable default parameters, reference semantics, class vs. instance variables) indicates it's a rare problem.
Finally, the intent thing... sorry but I can't really understand this one. When reading and writing Python, it's always perfectly clear: What you assign to is intended as a local, unless loudly and explicitly declared otherwise. No question about it. Moreover, since (at least in Python) a variable is either local for the entire function or not at all, you can't have tricky code that uses a local variable in one branch and a global one in another. You see a name being assigned to? You instantly know it was always local from the beginning. Well, unless the code is broken in a very special way (and will throw the dreaded
UnboundLocalError at run time, when entering those cases).