Live data from Hacker News

WTFPython – Understanding Python through surprising snippets

github.com

121–130 of 199 posts

Re: WTFPython – Understanding Python through surprising snippets

#121
post #8

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 m…

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…

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

#122
post #44
post #38

Earlier quoted context omitted.

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

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” :-)

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 x^i);
    }
will behave the same way because it does the same thing, so will e.g.

    for i := 0; 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

#123

It's a good repo, but remember a lot of those are "A Good Thing™". The first snippet is a very good example: a := "wtf_walrus" doesn't work while: (a := "wtf_walrus") works. It's a fantastic design decision. Python took a long time before getting this operator, because it's a language that favors being readable, easy to use, and above all, to learn. But in many other languages, the very same operator is often misused…

Only problem with the walrus operator is we still can't use it until linters support it. Now we're at 3.8.1 and still no linter support.

Re: WTFPython – Understanding Python through surprising snippets

#124
post #93

Earlier quoted context omitted.

I understand why people are willing to defend it or make apologies for it, but there really are some lessons to be learned from the mistakes it made, and dismissing legitimate criticism just ignores the opportunity.

I agree, there is a long list of things I would change in Python. And believe me when I say people involved in the community listen to this very carefully. This is why we had Python 3 in the first place. Because text handling was such a cause of pain. This is why we have type hints in the first place. Because big projects using Python felt let down. And Python 3 and type hints are also a huge source of criticism (I w…

I have a mantra, which this thread engraves so well: People always complain, and developers are the worse

If only people cared for fellow human beings, or earth, as much as they care for esoteric Wulrus operations in this or that language.

Re: WTFPython – Understanding Python through surprising snippets

#125

If only it had curly braces instead of just indention. God, that kills me. And used unicode instead of ascii as the default. And there wasn't python 2.7 vs 3. Someone help me stop this list.

That would have literally made me never look into it in the first place. I would have went with Ruby instead.

Re: WTFPython – Understanding Python through surprising snippets

#126
post #121

Earlier quoted context omitted.

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…

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...

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

#127
post #126
post #121

Earlier quoted context omitted.

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...

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.

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

#128
post #43

Earlier quoted context omitted.

I don't find this at all unexpected. In an imperative language I expect a loop to be implemented by mutating a common variable.

Do you perhaps think that because you come from a background in low-level languages?

[deleted]

Re: WTFPython – Understanding Python through surprising snippets

#130
post #44

Earlier quoted context omitted.

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” :-)

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 x^i); } will behave the same way because it does the same thing, so will e.g. for i := 0; i in Go. One of the…

C++:

   std::vector> powers_of_x;
   for (int i = 0; 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.
Post reply on HN