Discover why the `nonlocal` keyword in Python doesn't pass outer-scoped variables to the calling module and learn effective solutions for better testing.
---
This video is based on the question https://stackoverflow.com/q/76577388/ asked by the user 'AmagicalFishy' ( https://stackoverflow.com/u/1715544/ ) and on the answer https://stackoverflow.com/a/76577758/ provided by the user 'larsks' ( https://stackoverflow.com/u/147356/ ) at 'Stack Overflow' website. Thanks to these great users and Stackexchange community for their contributions.
Visit these links for original content and any more details, such as alternate solutions, latest updates/developments on topic, comments, revision history etc. For example, the original title of the Question was: Why doesn't the `nonlocal` keyword propogate the outer-scoped variable to the calling module?
Also, Content (except music) licensed under CC BY-SA https://meta.stackexchange.com/help/l...
The original Question post is licensed under the 'CC BY-SA 4.0' ( https://creativecommons.org/licenses/... ) license, and the original Answer post is licensed under the 'CC BY-SA 4.0' ( https://creativecommons.org/licenses/... ) license.
If anything seems off to you, please feel free to write me at vlogize [AT] gmail [DOT] com.
---
Understanding the nonlocal Keyword in Python: Why It Doesn't Propagate Outer-Scoped Variables
In the world of Python programming, the nonlocal keyword is a feature that allows developers to work with variables from an outer scope within nested functions. However, a common point of confusion arises around its behavior, especially when dealing with variables across different modules or test cases. This guide explores a specific scenario and its solution regarding why the nonlocal keyword doesn’t propagate outer-scoped variables as one might expect.
The Problem at Hand
Let’s consider a simple example with two Python files, A.py and B.py, attempted to be tested through test_A.py. The function make_call in A.py calls a function call from B.py, passing a phone number argument. During testing, you aim to capture the phone number passed to call and validate it.
Here’s a simplified outline of the relevant code:
[[See Video to Reveal this Text or Code Snippet]]
In your test (test_A.py), you're using a fixture with pytest and monkeypatch to replace call and capture the number being passed.
[[See Video to Reveal this Text or Code Snippet]]
Upon running the test, the output shows that the number remains "NOT ENTERED" while number_list captures the phone number "867-5309". This leads to confusion about why nonlocal seems ineffective in this situation.
Understanding the Behavior of nonlocal in This Context
The Timing of Fixtures
The main reason for this behavior lies in the execution order of fixtures in pytest. In your case, the patch_A_call fixture is executed before the test_make_call function runs. Therefore, when the fixture returns the values of number and number_list, it does so before the make_call function has triggered the patched call.
The nonlocal statement is working as intended in the call definition, meaning that it correctly references the outer scoped number.
However, the return statement occurs before make_call is executed, leading to stale values being returned at that point in time.
The Role of Mutable Containers
Interestingly, while the nonlocal variable did not show the updated value, the list (number_list) reflects the desired behavior. This is because lists in Python are mutable, and you are appending the value of the phone number to it, which is still valid even when referenced from a nested or outer scope.
Solution: Using Mocking Instead of Nonlocal
Instead of trying to manipulate outer-scoped variables with nonlocal, consider using mocking techniques provided by the unittest.mock module. Here’s how you can rewrite the test more effectively:
[[See Video to Reveal this Text or Code Snippet]]
Benefits of Using Mocking
Simplicity: The above method requires significantly less code and is more straightforward than setting up a manual fixture.
Clarity: Mocking allows you to directly check the arguments passed without the need to manipulate or carry over variable states from functions.
Robustness: Mocking is designed for testing purposes, which allows for more robust tests that can be easily adapted or extended.
Conclusion
The confusion surrounding the nonlocal keyword and its behavior in Python, especially concerning scopes and function calls across modules, boils down to timing and how Python manages variable states. By using mocking, you can simplify your tests while achieving the desired outcomes without fighting against the scoping rules of Python.
This understanding not only clarifies the use of nonlocal but also highlights best practices for writing effective tests in Python.
コメント