>>> 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.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.
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.
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.
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...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.
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.
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.
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).
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
>>> (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.
This. I've made this mistake exactly once in the last 7 years, but that one time almost caused a security issue.
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...
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.
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 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.
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
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.> 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.
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).
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]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.
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/...
/s
x ** y a = 0
def f():
print(a)
a = 1
f()
f() 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.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.
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.
var a = 9;
function foo() {
console.log(a);
var a = 12;
}
foo();
$node foo.js
undefined
The same goes with ruby.
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.