When I looked at Python, initially, the syntax struck me as extremely clean and eminently likable. I actually like the ‘whitespace thing’. It’s the all-objects-are-really-dictionaries approach to OO that pisses me off. But, when I looked at Ruby, I didn’t like the syntax, and obviously still didn’t like the dictionary approach.
As such I always suggest Python if the alternative is Ruby, and have been known to say that the two are essentially dialects of the same common idea (similar to C# and Java being dialects of one idea).
However, today Slava Pestov pointed me to something python got thoroughly wrong. Behold the python version of java’s ‘use an array to have mutable variables in closures’ hack:
<
p> OMG WTF!
<
p>
What’s going on here is that the ‘=’ operator does two totally different things. In the normal case, it takes some sort of identifier (left-hand) and sets its value to whatever’s on the right of the = sign. Let’s call that assignment. However, sometimes, it creates whatever’s on the left hand, and then follows this with the aforementioned assignment operation. Let’s call this the declare operation. Different beasts.
Now, it’s normal for lexical scope to be allowed to override previous identifiers. In other words, if I have (in java) a class with a private int foobar; attribute in it, and later in a method I declare a new integer with public void someMethod() { int foobar;} this new ‘foobar’ is completely distinct from the instance attribute. You can still get at the instance attribute foobar using the ‘this.’ op. Witness the most often observed format of your random java setter method which shows this in action:
public void setThingie(int thingie) {
this.thingie = thingie;
}
In java, declaration is rather distinct from assignment. In python, however, this is not so.
So, what happends in this case:
x = 5
def something():
x = 10
whoops, that ‘=’ in the ‘something’ method is actually a declare-version of the ‘=’ operator!
Worse, if you screw this up, you don’t get an error at all. Instead, x will mysteriously not arrive back at whatever you’re closing over with the above ‘something’ closure.
The motivation for using that array hack is different from java’s, but I’ll take java’s ANYDAY. In java, if you fail to use the ‘array hack’, you get a compile-time error. I like compile-time errors. I see them as I write them, which is much sooner and more localized compared to a unit test. You can even make a quickfix in your favourite IDE to automate the ‘wrap-in-array’ hack if you like.
In python you get a mysterious bug that could potentially caused by a very large set of causes (including every cause that might result in the closure never running, or anything which overwrites the closure with some other closure that never sets the thing, or code in the closure causing the ‘assignment’ statement to be skipped).
Ruby does get this right.
As an aside, in java the assumption is: Rather error now, possibly frivolously, because the fix is totally trivial, instead of assuming you know what you’re doing, and in the unlikely scenario you didn’t, you win a long and unfun bug hunt. This code will produce a compile-time error in java because you can’t overwrite a previous identifier if both are declared inside the same method-level code block:
public void someMethod() {
int x = 5;
while ( something ) {
int x = 10;
}
while ( somethingElse ) {
int x = 15;
}
}
Though, if you remove the ‘int x = 5′, there’s no overwriting at all going on, and java won’t complain. Yet another example where arrogance in assuming you must never write bugs and it is thus beneficial to guess at intent is clearly inferior to error early. Fixes are simply mass-renaming one of two identifiers. I’m borderline unsure if allowing overriding of scope between a method and its class definition (as is the case in the setter example) was a wise plan. At least an IDE is smart enough to warn you in cases like this:
public void setSomething(int smothing) { //note: typo!
this.something = something; //legal, but does nothing
}
{ 6 } Comments
I’m the one who wrote the link you posted to with “OMG WTF”
It’s a hack–nothing more. I enjoy an occasional hack
Feel free to reply to me there.
It’s a nice hack. Well done. The NEED for the hack makes me go OMG WTF.
> Worse, if you screw this up, you don’t get an error at all. Instead, x will mysteriously not arrive back at whatever you’re closing over with the above ’something’ closure.
By the way, the determination of scope is determined at “compile time”. For instance, you can’t use the closed over variable and then later in the same function try to define the variable at local scope:
>>> def outer():
… x = 1
… def inner():
… print x
… x = 2
… return inner
…
>>> inner = outer()
>>> inner()
Traceback (most recent call last):
File “”, line 1, in ?
File “”, line 4, in inner
UnboundLocalError: local variable ‘x’ referenced before assignment
Hence, it’s probably not quite as bad as you might think. Whether or not you need to declare variables explicitly is something I can go either way with. However, as a programmer comfortable with all of the above languages, let me state that this Pythonism almost never causes me pain. There are just too many other things I love about Python to let little warts like this bother me. By the way, I also *love* the objects as dictionaries thing. Since I don’t find any error in your reasoning, I think many of the places we disagree are just a matter of taste and priorities.
You know, there’s a decent upshot to this whole limited scope business.
I think an IDE can flag these kinds of scenarios. -sort of-. It should be possible to detect at compile/write-time either creating a new variable in scope but not doing anything with it (a sure sign something’s wrong), or using a variable before declaring it (definitely wrong, that’s a run time error too).
Any pythonistas happen to know if the python editor exists that’s smart enough to do this?
PyChecker.
I just found out that in Python 3000, Guido is adding a “nonlocal” keyword. It will behave in the same way as the “global” keyword does for globals. This will allow you to rebind a variable that is already bound in an outer scope.