Repository: microsoft/debugpy

Issue:
Some variables not assigned when using debug console
Issue Type: <b>Bug</b>

Tested on VSCode 1.66.x. Does not seem to occur with 1.65.x

1. Run some code
2. stop on a break point.
3. some variables assigned by code prior to beakpoint (e.g. A = 10, B = 30)
4. execute a function that returns multiple variable and put into existing variables in local context (e.g. A, B = some_fun())
5. A does not get assigned with a new value.

Here is some code that reproduces the problem (with line numbers). Set a breakpoint on line 

```
import numpy as np

def some_fun():
    return np.random.randn(), np.random.randn()

def some_fun_2():
    return np.random.randn()

def main():
    A = 10
    B = 34
    C = 11

    A, B = some_fun() # this assignment works when stepping
    print(A) # break on this line
```

Once the code has stopped on breakpoint, execute A, B = some_fun() in the Debug Console to experiment the bug. 

These have the same erroneous behaviour:
`A, B = np.random.randn(), np.random.randn() `
`B, A = np.random.randn(), np.random.randn() `
`B, A = some_fun() `

However, `A = some_fun_2()` works fine.

python 3.8.10
Extension version: 2022.4.1
VS Code version: Code 1.66.2 (dfd34e8260c270da74b5c2d86d61aee4b6d56977, 2022-04-11T07:49:20.994Z)
OS version: Darwin x64 20.6.0
Restricted Mode: No

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz (12 x 2600)|
|GPU Status|2d_canvas: enabled<br>canvas_oop_rasterization: disabled_off<br>direct_rendering_display_compositor: disabled_off_ok<br>gpu_compositing: enabled<br>metal: disabled_off<br>multiple_raster_threads: enabled_on<br>oop_rasterization: enabled<br>opengl: enabled_on<br>rasterization: enabled<br>raw_draw: disabled_off_ok<br>skia_renderer: enabled_on<br>video_decode: enabled<br>video_encode: enabled<br>webgl: enabled<br>webgl2: enabled|
|Load (avg)|3, 3, 3|
|Memory (System)|32.00GB (0.06GB free)|
|Process Argv|--crash-reporter-id 2a273040-cc5d-49bc-b505-46f4293dc6a3 --crash-reporter-id 2a273040-cc5d-49bc-b505-46f4293dc6a3|
|Screen Reader|no|
|VM|0%|
</details><details>
<summary>A/B Experiments</summary>

```
vsliv368cf:30146710
vsreu685:30147344
python383cf:30185419
vspor879:30202332
vspor708:30202333
vspor363:30204092
pythonvspyl392:30443607
pythontb:30283811
pythonptprofiler:30281270
vshan820:30294714
vstes263:30335439
vscoreces:30445986
pythondataviewer:30285071
vscod805cf:30301675
pythonvspyt200:30340761
binariesv615:30325510
bridge0708:30335490
bridge0723:30353136
vsaa593cf:30376535
vsc1dst:30438360
pythonvs932:30410667
wslgetstarted:30449410
pythonvsnew555:30457759
vscscmwlcmt:30465135
vscaat:30438848

```

</details>

<!-- generated by issue reporter -->

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=microsoft_debugpy&pullRequest=918)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=918&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=918&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=microsoft_debugpy&pullRequest=918)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=918&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=918&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=918&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=918&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
index f214fa7c..1ed89636 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
@@ -377,13 +377,17 @@ def _update_globals_and_locals(updated_globals, initial_globals, frame):
     # Still, the approach to have a single namespace was chosen because it was the only
     # one that enabled creating and using variables during the same evaluation.
     assert updated_globals is not None
-    changed = False
+    f_locals = None
     for key, val in updated_globals.items():
         if initial_globals.get(key) is not val:
-            changed = True
-            frame.f_locals[key] = val
+            if f_locals is None:
+                # Note: we call f_locals only once because each time
+                # we call it the values may be reset.
+                f_locals = frame.f_locals
 
-    if changed:
+            f_locals[key] = val
+
+    if f_locals is not None:
         pydevd_save_locals.save_locals(frame)
 
 


Test patch:
diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
index 881f55a5..1adcce34 100644
--- a/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
+++ b/src/debugpy/_vendored/pydevd/tests_python/test_evaluate_expression.py
@@ -9,6 +9,8 @@ global_frame = sys._getframe()
 
 
 def obtain_frame():
+    A = 1
+    B = 2
     yield sys._getframe()
 
 
@@ -116,3 +118,15 @@ def test_evaluate_expression_4(disable_critical_log):
     assert 'email' in sys._getframe().f_globals
     del sys._getframe().f_globals['email']
     assert 'email' not in sys._getframe().f_globals
+
+
+def test_evaluate_expression_5(disable_critical_log):
+    from _pydevd_bundle.pydevd_vars import evaluate_expression
+
+    def check(frame):
+        eval_txt = 'A, B = 5, 6'
+        evaluate_expression(None, frame, eval_txt, is_exec=True)
+        assert frame.f_locals['A'] == 5
+        assert frame.f_locals['B'] == 6
+
+    check(next(iter(obtain_frame())))