pythonpython-3.xvisual-studio-codevscode-debugger

Inconsistent behavior between Python function and VS Code debugger for scope lookup


For the following code (revised):

# infile.py

FILE1 = 'file1'
FILE2 = 'file2'

def getfile():
    # Note:  I am doing the equivalent of this with VS Code debugger:
    # breakpoint()
    file = FILE1 if 'FILE1' in locals() else FILE2
    print(f'{file=}')

if __name__ == '__main__':
    getfile()

If I run this on Windows (using Python 3.12.7 or Python 3.13.0), here's what I see:

PS> .\infile.py
file='file2'

If I run this in my VS Code debugger (v1.95.3, Python v2024.20.0, Pylance v2024.12.1, Python Debugger v2024.12.0), here's what I see:

# Uncomment breakpoint at first line in above def getfile function
# Start VS Code in debugging mode (F5)
# VS Code stops at breakpoint
# From VS Code Debug Console, execute following:
file = FILE1 if 'FILE1' in locals() else FILE2

print(f'{file=})

# Output:
file='file1'

My expectation was the result shown in the VS Code debugger above. It sounds like I'm not quite understanding something with Python scoping. What am I missing?

I'm also curious why I see a difference between what Python does and what the VS Code Python debugger shows. Am I doing something wrong, or is this a bug?

Revised: If I do this with pdb, I see this:

PS> .\infile
> ...\infile.py(9)getfile()
-> breakpoint()
(Pdb) l
  4     FILE1 = 'file1'
  5     FILE2 = 'file2'
  6
  7
  8     def getfile():
  9  ->     breakpoint()
 10         file = FILE1 if 'FILE1' in locals() else FILE2
 11         # file = FILE1 if 'FILE1' in globals() else FILE2
 12         print(f'{file=}')
 13
 14
(Pdb) 'FILE1' in locals()
False
(Pdb) 'FILE1' in globals()
True
(Pdb)

So, I think what I really want is to use globals() - is that right?


Solution

  • Based on what @Grismar said, it sounds like the answer is that the locals() built-in function only shows things defined in the local scope. In other words, if we define Python scoping as LEGB, locals() only displays the "L" part. For what I was trying to do, I need to use the globals() built-in.

    As for VS Code, it appears that when using the Python debugger in VS Code that locals() displays more than just the "L" scope. However, I believe pdb is the definitive debugger and that only shows things in the "L" scope.

    Finally - is what I'm trying to do a good idea? Maybe/maybe not. In a nutshell - I'm doing Cloud hosted code challenges. The cloud environment defines their own variables (globals) that make sense for them (a Linux hosted environment). I choose to solve the challenges locally on a Windows environment. My environment is quite a bit different, so I define my own variables that make sense for my environment. I want to do something generic that works in either environment, so I check if my variable is defined. If yes use it, if no fallback to the cloud definition. There's probably a better way to do this and I'm open to suggestions.

    In terms of what I'm trying to do - here is an example code challenge skeleton that I hope is illustrative:

    import os
    from pathlib import Path
    
    # Cloud Environment - temporary (throw away):
    TEMPFILE = os.getenv('TMP', '/tmp')
    FNAME = 'data.csv'
    LOCALFILE = os.path.join(TMP, FNAME)
    
    # My Environment - in a local repo I wish to keep:
    CWD = Path(__file__).parent
    DATADIR = CWD/'data'
    DATA = CWD/DATADIR/FNAME
    
    # Why this approach instead of just setting a default
    # parameter or specifying DATA as a parameter which invoking
    # the function?
    # Because I want to copy this function as is back to the
    # cloud hosted challenge - this only changes things if
    # my variables are defined.  In addition, the cloud environment
    # comes with tests that need to run unchanged to validate the
    # solution.  The prevents me from changing the function
    # invocation parameter.
    def code_challenge(targetfile):
        targetfile = DATA if 'DATA' in globals() else LOCALFILE
        ...
    

    If this is the wrong approach, please share example code of the right way to do it.

    Thanks again for all the feedback.