From 2bc1cd61c11285b19e575dfcbda971b0b90a7477 Mon Sep 17 00:00:00 2001 From: Satwik Date: Sun, 17 Jan 2021 13:53:03 +0530 Subject: [PATCH] Update the sticy output example Closes https://github.com/satwikkansal/wtfpython/issues/245 --- CONTRIBUTORS.md | 1 + README.md | 65 +++++++++++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 659a382..28c3890 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,6 +23,7 @@ Following are the wonderful people (in no specific order) who have contributed t | jab | [jab](https://github.com/jab) | [#77](https://github.com/satwikkansal/wtfpython/issues/77) | | Jongy | [Jongy](https://github.com/Jongy) | [#208](https://github.com/satwikkansal/wtfpython/issues/208), [#210](https://github.com/satwikkansal/wtfpython/issues/210), [#233](https://github.com/satwikkansal/wtfpython/issues/233) | | Diptangsu Goswami | [diptangsu](https://github.com/diptangsu) | [#193](https://github.com/satwikkansal/wtfpython/issues/193) | +| Charles | [charles-l](https://github.com/charles-l) | [#245](https://github.com/satwikkansal/wtfpython/issues/245) --- diff --git a/README.md b/README.md index 7137d07..27ed3ab 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ So, here we go... + [▶ Evaluation time discrepancy](#-evaluation-time-discrepancy) + [▶ `is not ...` is not `is (not ...)`](#-is-not--is-not-is-not-) + [▶ A tic-tac-toe where X wins in the first attempt!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt) - + [▶ The sticky output function](#-the-sticky-output-function) + + [▶ Schrödinger's variable](#-schrödingers-variable-) + [▶ The chicken-egg problem *](#-the-chicken-egg-problem-) + [▶ Subclass relationships](#-subclass-relationships) + [▶ Methods equality and identity](#-methods-equality-and-identity) @@ -989,10 +989,9 @@ We can avoid this scenario here by not using `row` variable to generate `board`. --- -### ▶ The sticky output function +### ▶ Schrödinger's variable * -1\. ```py funcs = [] @@ -1006,17 +1005,17 @@ for x in range(7): funcs_results = [func() for func in funcs] ``` -**Output:** - +**Output (Python version):** ```py >>> results [0, 1, 2, 3, 4, 5, 6] >>> funcs_results [6, 6, 6, 6, 6, 6, 6] ``` -Even when the values of `x` were different in every iteration prior to appending `some_func` to `funcs`, all the functions return 6. -2\. +The values of `x` were different in every iteration prior to appending `some_func` to `funcs`, but all the functions return 6 when they're evaluated after the loop completes. + +2. ```py >>> powers_of_x = [lambda x: x**i for i in range(10)] @@ -1024,27 +1023,45 @@ Even when the values of `x` were different in every iteration prior to appending [512, 512, 512, 512, 512, 512, 512, 512, 512, 512] ``` -#### 💡 Explanation +#### 💡 Explanation: +* When defining a function inside a loop that uses the loop variable in its body, the loop function's closure is bound to the *variable*, not its *value*. The function looks up `x` in the surrounding context, rather than using the value of `x` at the time the function is created. So all of the functions use the latest value assigned to the variable for computation. We can see that it's using the `x` from the surrounding context (i.e. *not* a local variable) with: +```py +>>> import inspect +>>> inspect.getclosurevals(funcs[0]) +ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set()) +``` +Since `x` is a global value, we can change the value that the `funcs` will lookup and return by updating `x`: -- When defining a function inside a loop that uses the loop variable in its body, the loop function's closure is bound to the variable, not its value. So all of the functions use the latest value assigned to the variable for computation. +```py +>>> x = 42 +>>> [func() for func in funcs] +[42, 42, 42, 42, 42, 42, 42] +``` -- To get the desired behavior you can pass in the loop variable as a named variable to the function. **Why does this work?** Because this will define the variable -within the function's scope. +* To get the desired behavior you can pass in the loop variable as a named variable to the function. **Why does this work?** Because this will define the variable *inside* the function's scope. It will no longer go to the surrounding (global) scope to look up the variables value but will create a local variable that stores the value of `x` at that point in time. - ```py - funcs = [] - for x in range(7): - def some_func(x=x): - return x - funcs.append(some_func) - ``` +```py +funcs = [] +for x in range(7): + def some_func(x=x): + return x + funcs.append(some_func) +``` - **Output:** - ```py - >>> funcs_results = [func() for func in funcs] - >>> funcs_results - [0, 1, 2, 3, 4, 5, 6] - ``` +**Output:** + +```py +>>> funcs_results = [func() for func in funcs] +>>> funcs_results +[0, 1, 2, 3, 4, 5, 6] +``` + +It is not longer using the `x` in the global scope: + +```py +>>> inspect.getclosurevars(funcs[0]) +ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set()) +``` ---