News for hackers. Stuff that matters.
Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @03:52AM (#21862309)
My biggest complaint about Python is that it somehow doesn’t get flak for having the same (if not worse) scoping as JS, which gets endless hate for its function-scoped variables. (So much so that block scoped variables are the new normal in JS, but not in Py!)

Take for instance:

>>> powers_of_x = [lambda x: x^i for i in range(10)]

>>> [f(2) for f in powers_of_x]

[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

To me this is more absurd than any of the JS “wat”s I’ve seen.

In JS:

powersOfX = Array.from({length: 10}).map((_,i) => x => x^i)

powersOfX.map(f => f(2))

Not a particular fan of the Array.from over list comprehensions in terms of syntax, but I much prefer consistency of semantics JS provides by not adding new syntax.

Here Python has special syntax for both list comprehensions and for anonymous single line functions, and they interact in a highly unexpected way.

Funny that Py doesn’t need semicolons, as I’m reminded of GJS telling me of adding special forms to languages: “beware the allure of syntactic sugar, lest you bring upon yourself the curse of the semicolon” (paraphrased of course, he said it much better). It seems Python has fallen into the trap of syntactic sugar, with the curse manifesting itself not in the form of a semicolon, but in confusing and unexpected interactions between its various special forms. Another example: the walrus operator and all its oddities listed in TFA.

Re:WTFPython – Understanding Python through surprising snippets by Kwantuum on Monday December 23, 2019 @05:20AM (#21862683)
You can achieve the same thing in python by using two lambdas (which is actually what you're doing in JS with map):

   >>> powers_of_x = [(lambda i: (lambda x: x**i))(i) for i in range(10)]
   >>> [f(2) for f in powers_of_x]
   [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Or you can use default parameter values to use a single lambda (though this means it can be overridden, it's not semantically equivalent to the js implementation)

   >>> powers_of_x = [lambda x, i=i: x**i for i in range(10)]
   >>> [f(2) for f in powers_of_x]
   [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
your python snippet is equivalent to the following JS:

   >>> function* comprehension(iterable){
   >>>   let i;
   >>>   for(let j of iterable){
   >>>     i = j;
   >>>     yield x => x**i;
   >>>   }
   >>> }
   >>> powersOfX = [...comprehension([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])];
   >>> powersOfX.map(f => f(2))
   [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
And Python's comprehensions are actually a shorthand for writing the above generator with this syntax: yieldExpression for i in iterable. The semantics are consistent with the regular for..in

   >>> def comprehension(iterable):
   >>>     for i in iterable:
   >>>         yield lambda x: x**i
   >>> [*comprehension(range(10))]
is equivalent to

   >>> [lambda x: x**i for i in range(10)]
This is all a consequence of python's late binding.
Re:WTFPython – Understanding Python through surprising snippets by Izkata on Monday December 23, 2019 @08:04AM (#21863340)
Amusingly, the "default parameter" version relies on a feature that also gives rise to another WTPython entry: https://github.com/satwikkansal/wtfpython/blob/master/README...
Re:WTFPython – Understanding Python through surprising snippets by sjagoe on Monday December 23, 2019 @08:36AM (#21863483)
That's a different issue around default arguments that are mutable, rather than default arguments generally.

The lambda case here is more like a default argument of a constant integer (immutable) than a default that is a list, which can be altered directly by the function it's a default of.

Re:WTFPython – Understanding Python through surprising snippets by Izkata on Monday December 23, 2019 @08:47AM (#21863532)
The underlying feature that both rely on is that default arguments are set on function declaration instead of function call. Mutable/immutable only affects whether or not you hit the bug.
Re:WTFPython – Understanding Python through surprising snippets by mplanchard on Monday December 23, 2019 @09:14AM (#21863684)
Parenthetical comprehensions (generator expressions) are equivalent to your expanded function, but list comprehensions are not. `(x for x in range(10))` creates a generator, but using square brackets converts it to a list, including allocating the necessary memory. If you’re trying to do yield and streaming, the parens will serve you better.
Re:WTFPython – Understanding Python through surprising snippets by Kwantuum on Monday December 23, 2019 @03:47PM (#21867219)
You will notice that I explicitly unpacked the generator object in the brackets below the generator function by prefixing it with an asterisk.
Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @04:06AM (#21862351)
I agree totally with that, it's an ugly trap in python, although mitigated by the fact the language doesn't encourage this kind of paradigm so you rarely encounter it.

But more absurd than creating a global variable if you forget "var/let/const" ? More absurd than "this" being schizophrenic ? More absurd than having no namespace for 20 years ?

That's pushing it.

Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @04:14AM (#21862384)
In my opinion, if standard tooling is able to identify the foot-gun, it’s not too terrible of a foot-gun. I don’t think standard python tooling would catch the above issues, but standard JS tooling would identify a missing variable declaration. As for the other things you mention (this, namespaces), I never said JS was perfect! Just that JS got a lot of shit for its scoping, fixed the problem, and now has sane scopes. Py has scoping with a lot of the same problems JS used to have, but for some reason didn’t fix them, and thus to this day is riddled with these foot-guns.
Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @04:27AM (#21862445)
> I don’t think standard python tooling would catch the above issues,

Of course it does.

    $ echo "
    > powers_of_x = [lambda x: x^i for i in range(10)]
    > [f(2) for f in powers_of_x]
    > " > test.py
    $ pylint test.py
    ...
    test.py:2:27: W0640: Cell variable i defined in loop (cell-var-from-loop)
    ...
Pylint is the most powerful linter for Python is well integrated in VSCode or PyCharm.

When concerns are raised about JS, people are quick to answer you are not supposed to use it without modern tooling (Webpack + babel + typescript + eslint + lowdash...), although many devs still do.

Now, I use and teach a lot of JS, and I enjoy many of its modern features. I especially like the object destructuring, it's even better than Python's unpacking.

But objectively, you can get a very decent experience in Python without any kind of tooling. And yet, there is a lot of it if you want to up your game: pylint, black, mypy, jedi, pytest, poetry, etc.

Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @04:32AM (#21862472)
That’s good to hear. As I said, I don’t use python much anymore.
Re:WTFPython – Understanding Python through surprising snippets by kortilla on Monday December 23, 2019 @04:36AM (#21862493)
Are they really foot-guns? The lambda variable reference thing has appeared more in forums comparing languages than I’ve seen it used at all in python and I’ve spent a lot of time working in large open source python projects with thousands of contributors.

Even if you got the binding right, your powers of 2 would be shot down for being unpythonic anyway due to lambdas being shunned when there are cleaner ways to write things.

powers_of_x makes no sense as a variable name for a list of functions that you have to call in order with the same number to actually get the powers of that number. I get that it’s just a contrived example, but most can be turned into someone more apparent to the reader.

    def powers_of_x(x, limit=10):
        for i in range(limit):
            yield x^i
Less memory, no anonymous functions, more flexibility, testable...
Re:WTFPython – Understanding Python through surprising snippets by Bahamut on Monday December 23, 2019 @04:14AM (#21862393)
Modules avoid the variable issue because ‘use strict’ is applied by default for them.

IMO JS as a language is in a much better place than Python currently. I end up frustrated a fair amount by arcane errors in Python due to less expressive constructs or some wtf issues like unexpected variable scoping within Python modules.

Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @04:31AM (#21862468)
This is most probably because you are trying to code in Python like you code in JS: this always leads to frustration.

I started to have fun coding in JS the day I accepted it was not Python and that I had to structure and style my code differently.

E.G: in Python you will use iteration a lot. A lot of a lot. But not so many callbacks. The reverse is true in JS.

Re:WTFPython – Understanding Python through surprising snippets by CallocRoast on Monday December 23, 2019 @04:43AM (#21862530)
Iterators and callbacks aren’t comparable. You don’t use them for the same thing.
Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @05:15AM (#21862659)
What do you think each(), map() and filter() do ?

But even without that, I'm not saying they are comparable. I'm saying the same API will use explicit anonymous callbacks in JS and something else in Python (decorators, subclassing, protocols, generators...). I'm saying that the same API will use __iter__ in Python and something else in JS (type conversion, proxy object, explicit method call...).

E.G, this is a Python pattern you'll find in contextlib or in pytest fixtures:

    @somekindofregistration
    def foo():
        print('code that runs before')
        try:
            yield
        except Stuff:
            print('Error handling')
        print('code that runs after')
This uses Python iteration mechanism to run code at 3 different times in a life cycle.

While in JS, you would pass 3 callbacks.

Re:WTFPython – Understanding Python through surprising snippets by WoodTree on Monday December 23, 2019 @06:28AM (#21862968)
Map, filter, and fold are higher order functions, functions which take functions as parameters.

You are misusing the word callback. A callback is a function passed to another thread that will maybe be invoked later as a response (like it calls you back).

Re:WTFPython – Understanding Python through surprising snippets by zbentley on Monday December 23, 2019 @07:54AM (#21863298)
That's not what a callback is. A callback is just a function passed to an outer function that the outer function may call.

Even MDN uses the term callback to describe e.g. the argument to 'map': https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

In contrast, Lodash goes out of its way not to use the term "callback", presumably to avoid confusion since it's a place where many functions-that-take-a-function-argument do not call the provided function, e.g. https://lodash.com/docs/#curry

Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @07:28AM (#21863204)
Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @04:11AM (#21862376)
After a long time using both languages, I’d argue that non-sane equality checks in js is the biggest source of bugs.
Re:WTFPython – Understanding Python through surprising snippets by tyri_kai_psomi on Monday December 23, 2019 @04:34AM (#21862483)
Unfortunately, this is not true. Although the equality operators are a favorite bikeshedding topic, the largest source of errors in JS code bases are type errors. Using a "non-sane" equality check, such as those with type coercion would actually mask or alleviate these source of bugs you mention.
Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @04:41AM (#21862521)
I was thinking more of the case where the types match but objects and arrays with the same contents are considered different. I’ve watched every member of my team get stung by it again and again - and then have to create workarounds to get past it.
Re:WTFPython – Understanding Python through surprising snippets by CallocRoast on Monday December 23, 2019 @04:48AM (#21862552)
I don’t agree and this behavior shouldn’t be surprising. The alternative would be to walk the container and compare the value of each element, which could be horrible.
Re:WTFPython – Understanding Python through surprising snippets by BiteCode_dev on Monday December 23, 2019 @05:19AM (#21862675)
Quite the contrary, it's very beautiful and useful:

    >>> (1, 2) == (1, 2)
    True
    >>> (2, 1) == (1, 2)
    False
If you need identity for perf:

    >>> (1, 2) is (1, 2)
    False
If you just need the type check:

   >>> isinstance((1, 2), type((1, 2)))
   True
It's very explicit, practical, and you can set the scale of practicality vs performances where you want. Plus: no implicit weird type conversion, only one equality comparison operator, and no hidden rules.

I think it's sane.

Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @05:23AM (#21862698)
I’m not sure if you’re for or against here. Walking the container is exactly what you have to do, and it is horrible. More importantly, if it’s not your library, you don’t get any choice on how the equality check is implemented.
Re:WTFPython – Understanding Python through surprising snippets by Alex3917 on Monday December 23, 2019 @10:43AM (#21864287)
> I agree totally with that, it's an ugly trap in python, although mitigated by the fact the language doesn't encourage this kind of paradigm so you rarely encounter it.

This. I've made this mistake exactly once in the last 7 years, but that one time almost caused a security issue.

Re:WTFPython – Understanding Python through surprising snippets by faizshah on Monday December 23, 2019 @10:53AM (#21864380)
This situation is mentioned in the docs:

https://docs.python.org/3.8/faq/programming.html#why-do-lamb...

"This happens because i is not local to the lambdas, but is defined in the outer scope, and it is accessed when the lambda is called — not when it is defined. At the end of the loop, the value of i is 4, so all the functions now return 42, i.e. 16."

These sort of scoping gotchas are pretty common across all programming languages and are a great argument for unit testing. As usual the answer is be more explicit what you're asking the language to do:

>>> powers_of_x = [lambda x, i=i: x*i for i in range(10)]

>>> [f(2) for f in powers_of_x]

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Note the positional argument with default "i=i."

More discussion:

https://stackoverflow.com/questions/452610/how-do-i-create-a...

Re:WTFPython – Understanding Python through surprising snippets by wvenable on Monday December 23, 2019 @04:46PM (#21867726)
Any feature that is a "great argument for unit testing" is a poor feature.
Re:WTFPython – Understanding Python through surprising snippets by pc86 on Monday December 23, 2019 @03:39PM (#21867156)
Isn't there something wrong if you have to explicitly state i=i?
Re:WTFPython – Understanding Python through surprising snippets by varelaz on Monday December 23, 2019 @04:00AM (#21862332)
There is nothing bad in general with list comprehension there. Problem is lambda which uses variable from outer scope. In Python, comparing to JS, using variables from outer scope is not so common. I prefer more obvious constructions and in case of problems with scope I can even create class to protect value that I need to manage.
Re:WTFPython – Understanding Python through surprising snippets by intrepidhero on Monday December 23, 2019 @12:12PM (#21865092)
In a decade of python use I can probably count on one hand the number of times lambda has been a good solution to a problem I had. Many times I end up re-writing them as regular functions for clarity sake.
Re:WTFPython – Understanding Python through surprising snippets by xscott on Monday December 23, 2019 @05:54PM (#21868220)
What's the symptom and what's the cause? Python's lambdas aren't very good, so they're frequently not a very good solution...
Re:WTFPython – Understanding Python through surprising snippets by edflsafoiewq on Monday December 23, 2019 @04:23AM (#21862427)
I don't find this at all unexpected. In an imperative language I expect a loop to be implemented by mutating a common variable.
Re:WTFPython – Understanding Python through surprising snippets by owl57 on Monday December 23, 2019 @04:57AM (#21862590)
Imperative loop, maybe. But list comprehensions are a sublanguage imported from Haskell and looking like Haskell, so it's somewhat natural to be surprised by mutable variables here.
Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @04:30AM (#21862459)
Do you perhaps think that because you come from a background in low-level languages?
Re:WTFPython – Understanding Python through surprising snippets by fanf2 on Monday December 23, 2019 @08:50PM (#21869201)
Lua and Perl use fresh variables for each iteration, instead of mutating a shared copy.
Re:WTFPython – Understanding Python through surprising snippets by b0rsuk on Monday December 23, 2019 @12:01PM (#21864994)
>My biggest complaint about Python is that it somehow doesn’t get flak for having the same (if not worse) scoping as JS, which gets endless hate for its function-scoped variables. (So much so that block scoped variables are the new normal in JS, but not in Py!)

It's less of a problem in Python because it has built-in module support. A global variable is only global in the module, unless you import it with "from foo import *", which lazy programmers do but is discouraged by style guides.

Re:WTFPython – Understanding Python through surprising snippets by fxj on Monday December 23, 2019 @06:14AM (#21862919)
You forgot the i=i in your lambda definition. Then there exists a local variable i and everything works as you wanted.
Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @04:09AM (#21862361)
Maybe I’m misunderstanding, but I’d argue that arrow syntax in js is equally special syntax as lambda function. They’re both native ways of making anonymous functions. Python’s version is more clunky, but no more special
Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @04:25AM (#21862435)
Interesting, I don’t use python day-to-day, so I had assumed that the delayed capturing of the iteration variable was a lambda thing. Looks like this is still broken:

for i in range(10):

   def mul(x):

     return i^x
   powers_of_x.append(mul)
That to me coming from a JS background is totally wild
Re:WTFPython – Understanding Python through surprising snippets by masklinn on Monday December 23, 2019 @04:39AM (#21862509)
Why would it be "totally wild" coming from a JS background when the corresponding JS code:

    for(var i=0; i<10; ++i) {
        powers_of_x.push((x) => i^x);
    }
behaves the exact same way for the same reason?

By definition, a closures closes over its definition context, it doesn't capture the values at definition time but instead keeps referring to said definition context. If the definition context is mutable and modified, the closure reflects that change when it's finally invoked.

Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @04:30AM (#21862460)
It’s actually due to python late binding. The variables are looked up at call time, rather than function define time. One could argue that your example shouldn’t work anymore in python3 since variables in list comprehensions go out of scope when they finish now. I haven’t tried it myself. I fill it under, “things I never need to do” :-)
Re:WTFPython – Understanding Python through surprising snippets by masklinn on Monday December 23, 2019 @08:18AM (#21863397)
It's not "python late binding", closures behave this way in any language with mutable bindings[0]. The entire point of a closure is to close over its lexical context, if the lexical context is mutable and mutated before the invocation of the closure, the closure is going to reflect that change.

    for(var i=0; i<10; ++i) {
        powersOfX.push(x => x^i);
    }
will behave the same way because it does the same thing, so will e.g.

    for i := 0; i<10; i++ {
        powers_of_x = append(powers_of_x, func(x int) int { return x ^ i })
    }
in Go.

One of the confounding factors is that many languages have block-scoped for loops especially when using iterators instead of low-level C-style loops; that's also the case in Javascript when using `let` bindings (and is in fact one of the major reason to use `let` instead of `var` if that's possible).

The underlying concern is still there[1], but this very common failure case is avoided: rather than update a single binding, each iteration creates a brand new binding for the closure to close over.

Alternatively, a common mitigation technique is to emulate that using e.g. immediately invoked function expressions.

In Python you can also use the "default parameter" trick to shadow the closed-over binding (though most of the time this is used for performance reasons) without the overhead (both syntactic and runtime) of lambdas in lambdas in lambdas:

    for i in range(10):
        powers_of_x.append(lambda x, i=i: x^i)
[0] excluding the special case of low-level languages with capture clauses, as well as languages like Java where closing over mutable bindings is specifically forbidden (a lambda or anonymous class can only close over `final` bindings)

[1] and remains a regular issue in async code fighting over closed-over context

Re:WTFPython – Understanding Python through surprising snippets by aidos on Monday December 23, 2019 @09:12AM (#21863674)
You're right. I remembered the reason after reading your sibling comment and almost deleted my comment to avoid the confusion. Thanks for going into the details.
Re:WTFPython – Understanding Python through surprising snippets by gpderetta on Monday December 23, 2019 @08:59AM (#21863604)
C++:

   std::vector<std::function<int(int)>> powers_of_x;
   for (int i = 0; i < 10; ++1)
       power_of_x.push_back([=](int x) { return std::pow(x, i); });
works as one would intuitively expect (a different i is captured for each iteration). The issue is with those languages that conflate values with references.
Re:WTFPython – Understanding Python through surprising snippets by masklinn on Monday December 23, 2019 @11:05AM (#21864525)
…

> excluding the special case of low-level languages with capture clauses

Of course it works, you're specifically capturing `i` by value. It's not exactly surprising that doing things completely differently yields a different result.

Re:WTFPython – Understanding Python through surprising snippets by gpderetta on Monday December 23, 2019 @11:52AM (#21864908)
To be fair I missed the note at the end of your commend on my first read. Still, in the specific C++ example, no other capture clause is valid.

Also, python could have chosen a slightly different closure semantics and preserved sanity: instead of closing over the binding itself, it could close over each object reference separately (exactly in the same way the default parameter hack works).

Re:WTFPython – Understanding Python through surprising snippets by loopz on Monday December 23, 2019 @09:49AM (#21863897)
Works intuitively in golang:

https://play.golang.org/p/pn2jBTaTNS8

  package main
  
  import (
   "fmt"
  )
  
  // return a^n
  func Power(a, n int) int {
   var i, result int
   result = 1
   for i = 0; i < n; i++ {
    result *= a
   }
   return result
  }
  
  func main() {
   fmt.Println("Hello, playground")
   x := 2
   var powers_of_x []int
   for i := 0; i < 10; i++ {
    powers_of_x = append(powers_of_x, func(x int) int { return Power(x, i) }(x))
   }
   fmt.Println(powers_of_x)
  }

  //Output:
  //Hello, playground
  //[1 2 4 8 16 32 64 128 256 512]
Re:WTFPython – Understanding Python through surprising snippets by gpderetta on Monday December 23, 2019 @10:38AM (#21864252)
I know nothing about go, but it seems to me in your example powers_of_x is a list of n integers, while in the example being discussed is an list of n functions (which can be used to compute the n'th power).
Re:WTFPython – Understanding Python through surprising snippets by masklinn on Monday December 23, 2019 @11:04AM (#21864505)
I provided a Go example demonstrating that it does not. Your version

1. uses a slice of integers which completely misses the point

2. uses an IIFE which completely misses the point

The point is to look at what happens when you close over the iteration variable.

Re:WTFPython – Understanding Python through surprising snippets by verttii on Monday December 23, 2019 @04:21AM (#21862417)
Aren't Python's lambdas expressions while JS's arrow functions are not?
Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @04:22AM (#21862423)
Arrow functions are expressions in JS.
Re:WTFPython – Understanding Python through surprising snippets by SPBS on Monday December 23, 2019 @06:56AM (#21863083)
Surely they have to considered expressions to be assignable to a variable?
Re:WTFPython – Understanding Python through surprising snippets by watersb on Monday December 23, 2019 @07:47PM (#21868940)
"Excess syntactic sugar causes cancer of the semicolon."

GJS: Gerry Sussman attributes this quote to Alan Perlis.

_Structure and Interpretation of Computer Programs_: https://mitpress.mit.edu/sites/default/files/sicp/full-text/...

Re:WTFPython – Understanding Python through surprising snippets by beagle3 on Monday December 23, 2019 @05:10AM (#21862636)
The real WTF here is that you are getting a power output from thee ^ operator, when it actually does xor. Indeed, I've never seen JavaScript mix up operators :)

/s

Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @05:11AM (#21862642)
HN isn’t able to render two adjacent asterisk, so I had to make do.
Re:WTFPython – Understanding Python through surprising snippets by klyrs on Monday December 23, 2019 @12:23PM (#21865174)
If they're surrounded by spaces it goes ok...

    x ** y
Re:WTFPython – Understanding Python through surprising snippets by xscott on Monday December 23, 2019 @05:12AM (#21862644)
Even more fundamental is the nonlocal/global stuff required in order to avoid declaring your variables. Many people are surprised by what this snippet does:

    a = 0
    def f():
        print(a)
        a = 1
    f()
    f()
Re:WTFPython – Understanding Python through surprising snippets by cutler on Monday December 23, 2019 @07:21AM (#21863178)
All it does is produce an error: "UnboundLocalError: local variable 'a' referenced before assignment". The second f() call makes no difference.
Re:WTFPython – Understanding Python through surprising snippets by xscott on Monday December 23, 2019 @01:31PM (#21865830)
Is that what you expected from such a simple piece of code?
Re:WTFPython – Understanding Python through surprising snippets by Scarblac on Monday December 23, 2019 @02:14PM (#21866311)
Well it's f that has the problem, rather obviously, and the other three lines are unrelated. So I don't see how this is some especially simple way of triggering that error.
Re:WTFPython – Understanding Python through surprising snippets by xscott on Monday December 23, 2019 @02:59PM (#21866771)
The other three lines are not unrelated. If you just take the first three lies (and the last one), it's fine:

    a = 0
    def f():
        print(a)
    f()
Maybe this is all obvious to you, but I'll bet you can't name any other language which behaves this way. Compare the original Python to JavaScript (or Lua, Perl, Tcl, Ruby, C, Scheme, Rust, Clojure, or ...):

    a = 0
    function f() {
        console.log(a)
        a = 1
    }
    f()
    f()
This one behaves how I think most people would initially expect the Python snippet to behave. First it prints 0, then it prints 1. (Of course JavaScript has it's flaws too...) If you don't believe me, ask your coworkers and friends what they think it does before running it.

My only real point is that Python conflates variable declaration and variable assignment in a way which initially seems like a friendly time-saver, but which ends up being pretty subtle and confusing until you've learned its quirks. All of that just to avoid declaring your variables (in some fictional version of Python):

    var a = 0
    def f():
        print(a)
        a = 1
    f()
    f()
Here it would be clear which are declarations + initializations, and which are only assignments. And for just the cost of typing the word "var", the compiler could tell you when you've made typos in your variable names. As an added bonus, you could get rid of the "global" and "nonlocal" keywords.
Re:WTFPython – Understanding Python through surprising snippets by Scarblac on Monday December 23, 2019 @06:06PM (#21868293)
Of course I'm not a good judge, I have been programming Python since 1.4 and my colleagues also all have Python experience.

But at least I like the idea that it gives an error message when confronted with ambiguity. That way it doesn't do something you didn't expect silently. Sadly it only gives it at runtime, not at compile time.

Re:WTFPython – Understanding Python through surprising snippets by xscott on Tuesday December 24, 2019 @12:46AM (#21870139)
I'm not sure what your point is about Python 1.4. Maybe it's that you're past the point where these things trip you up.

However, there are plenty of cases where Python won't give an error message too. Make a typo somewhere in the middle of your function, and it'll quietly introduce a new variable instead of letting you know. A rarely used branch of an if-statement could hide this indefinitely. This could also be avoided (or at least mitigated) by requiring variables to be declared explicitly.

Re:WTFPython – Understanding Python through surprising snippets by h8hawk on Monday December 23, 2019 @03:43PM (#21867189)
js also doesn't behave as you want:

  var a = 9;   
  function foo() {
      console.log(a);
      var a = 12;
  }
  foo();

  $node foo.js
  undefined
The same goes with ruby.
Re:WTFPython – Understanding Python through surprising snippets by jakear on Monday December 23, 2019 @05:23PM (#21868024)
You’re sidestepping the problem. The problem is that Python uses the same syntax for variable declaration and variable assignment, and this can lead to unexpected behavior. That’s far from normal in the programming world, making it more unexpected.
Re:WTFPython – Understanding Python through surprising snippets by xscott on Monday December 23, 2019 @04:09PM (#21867399)
JavaScript is far from perfect, but at least it's clear you declared two variables.
Re:WTFPython – Understanding Python through surprising snippets by mschuetz on Monday December 23, 2019 @04:10AM (#21862364)
Yes! So much that! I can't believe that it's okay in python to declare/initialize a variable within a loop or if statement, and that it goes to live on afterwards. JS fixed that issue years ago with let and const.