6 ms·
No, the name is definitely mutable. Consider: x = 1 capture = lambda: x x = 'foo' print capture() If python were just shadowing, this would print 1. B
by dilap 7y ago
No, the name is definitely mutable. Consider:
x = 1
capture = lambda: x
x = 'foo'
print capture()
If python were just shadowing, this would print 1. But it prints foo.
- joshuamorton 7y agoThat's an issue of scoping, not capturing. The x in the lambda isn't scoped to the lambda, it's scoped to the surrounding environment. So the x closes not over the lambda but the outer scope. So it's as expected given shadowing. Edit: Since I'm getting throttled: No, I'm saying that scoping rules are different in python and rust. In Rust (and cpp) there's the concept of scopes/closures as a first class feature. This concept doesn't exist in python (python has namespaces, I guess, instead, there's no good terminology here). See https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture https://stackoverflow.com/questions/2295290/what-do-lambda-f.... This is due to weird scoping, not name mutability. Second edit: Well ok on second thought I see where you're going here. I was trying to avoid thinking about copy-on-scope-change behavior, but you actually do have to consider that and you're right.
- hope-striker 7y agoShadowing is when you have two separate variables with the same identifier. It is beyond obvious that all the x's refer to the same variable in dilap's example. Contrast that with an actual example of shadowing[0], in which it is clear that the same identifier is being used to refer to two different variables. [0]: https://en.wikipedia.org/wiki/Variable_shadowing#Python https://en.wikipedia.org/wiki/Variable_shadowing#Python
- joshuamorton 7y agoI'm using shadowing in the rust sense, not the python sense.
- hope-striker 7y agoThey are the exact same sense! The exact same idea of shadowing is also present in, for example, sentences in first order logic.
- dilap 7y agoSetting aside any terminology for a second, consider this rust program: fn main() { let x = 1; let capture = || x; let x = 2; println!("{}", capture()); println!("{}", x) } This will print 1 and then 2, whereas python would print 2 and 2. Hence, you can see that the formulation "let mut" is equivalent to python, not "let" followed by "let". Here's the rust program that prints 2 and 2: fn main() { let mut x = 1; let ptr = &x as *const i32; let capture = || unsafe{ *ptr }; x = 2; println!("{}", capture()); println!("{}", x); } (I had to use unsafe otherwise the borrow checker will complain will I modify x from underneath the closure; maybe a more elegant way to make the same point -- I don't really know rust...)
- joshuamorton 7y agoActually hmm, I may want to take back my earlier comment. There are multiple things at play. There's scoping (where rust will copy across scope boundaries for non-ref types, which allows closing over something as in your first example above). Then there's mutable refs and mutable variables, which as hope-striker mentioned I was confusing, possibly because I was using ints in my example. If instead we used a vec: fn main() { let x = vec![0,1,2] x.push(3) // fails since x isn't mutable } There's no clear direct related concept here by default. If we're allowed to use pytype, you get this: def main(): x: Sequence[int] = [1,2,3] # Sequences aren't mutable x.push(3) # fails since x isn't mutable Cool, so mutable and immutable values are possible in both langs. What about refs? Well we went through that one, if you pass a mutable ref to a function in rust, you can modify the ref in ways that just aren't possible in python: fn main() { println!("Hello, world!"); let mut x: i32 = 3; modifies(&mut x); println!("{}", x); } fn modifies(x: &mut i32) { *x = 5; } There's nothing analogous to this in python. Everything is always passed as a mutable "value"[1], nothing is passed as a ref. Cool so that's mutable variables and mutable references. That leaves this weird scoping issue. In rust (and in cpp) there's lots of scopes. Any set of braces creates a new scope, and so shadowing can happen across scopes. Lambda capture/closure happens over the scope. A given scope binds a name to a value, or a set of names to their values. Python's a bit different, only new names are created in the scope. If a name isn't accessible in the given scope, the name is pulled from parent scopes etc. So for the capturing behavior you want, there's weird nonlocal stuff that needs to be done, or you can explicitly make an additional scope, which removes the wonky behavior. If the name were really mutable, you'd be able to change what x referred to in the enclosing scope, which you can't. tl;dr: This isn't mutable names, its python's (admittedly abnormal) scoping rules. [1]: Unless you add in mypy or whatnot, where the typechecker will prevent you from modifying something that is non-mutable, but unlike in rust this isn't done with mutability as a first class citizen, its just that some interfaces expose mutating methods (`append`) and some don't. You can pass a list to a function that expects a list or a sequence, and the first case is mutable, while the second isn't.