LMc
LMc

Reputation: 18642

Why is my call to a variable that exists in the local scope not working in Python?

This code is part of a function. When I run the function line by line with the proper arguments, it runs fine, but when I call the function it doesn't seem to work. The following three lines of code and their output are from the function call:

print(locals().keys())
dict_keys(['allpost', 'allpre', 'pre', 'post'])

You can see that 'allpre' exists in my local scope. This line of code, also in the function, works as well:

print(locals()['allpre'])
[15.0, 12.0, 10.0, 6.0, 12.0, 8.0, 5.0, 3.0]

But for some reason this code doesn't work:

print([locals()[k] for k in ['allpre']])
Traceback (most recent call last):
File "prepost.py", line 85, in <module>      parentinfantstats=delete.bystats(mod='parentinfant',assessment=".*pii[1-3]plus",dta=sortFiltbyMod.copy())
File "/mnt/m/safecare/prepost/delete.py", line 37, in bystats
print([locals()[k] for k in ['allpre']])
File "/mnt/m/safecare/prepost/delete.py", line 37, in <listcomp>
print([locals()[k] for k in ['allpre']])
KeyError: 'allpre'

Does anyone have a suggestion for what the problem might be? I would post an example, but can't seem to duplicate the problem.

This is the whole function:

import re
from statistics import mean,median,stdev

def bystats(*,mod,assessment,dta):
    varz=dta[mod]               
    alab=[i for i in varz if re.match(assessment.lower(),i.lower())]
    alab.insert(0,'prepost')
    alab.insert(0,'cact_familycodenormalized')
    alst=[varz[i] for i in alab] # [ID,prepost,assessment]
    bymodprepost=[list(row) for row in zip(*alst) if row[1] in [1,2]] # [ID,prepost,assessment] if prepost 1 or 2
    bymodpost=[i for i in bymodprepost if i[1]==2] # [ID,prepost,assessment] if prepost was 2 (post)
    bymodpre=[i for i in bymodprepost if i[0] in [ids[0] for ids in bymodpost] and i[1]==1] # [ID,prepost,assessment] if ID had a post test

    allpre,allpost,allch,allpctch=[],[],[],[]
    for pre in bymodpre:
        post=[i for i in bymodpost if i[0].upper().strip()==pre[0].upper().strip()][0] # find matching post test
        if any([True for i in pre[2:]+post[2:] if float(i)<0]): continue # cannot have negative number
        sumpre=sum([float(i) for i in pre[2:]]) # sum of pre test assessments
        allpre.append(sumpre)
        sumpost=sum([float(i) for i in post[2:]]) # sum post test assessments
        allpost.append(sumpost)
        ch=sumpost-sumpre       # change from pre to post
        allch.append(ch)
        pctch=round(ch/sumpre*100,1) # percent change from pre to post
        allpctch.append(pctch)
    print(locals().keys())
    print(locals()['allpre'])
    print(locals()[k] for k in ['allpre'])

And this is the function call:

parentinfantstats=delete.bystats(mod='parentinfant',assessment=".*pii[1-3]plus",dta=sortFiltbyMod.copy())

Upvotes: 3

Views: 86

Answers (4)

Blckknght
Blckknght

Reputation: 104722

Calling locals from within a list comprehension won't work the way you expect it to (in Python 3). That's because the main body of the list comprehension gets run within its own local namespace, just like it was a function returning a list. You can see this in your traceback, where <listcomp> shows up as one of the stack levels.

In Python 2, list comprehensions didn't do this (but generator expressions did). A side effect of their old behavior was that the iteration variable (e.g. k) would "leak" out into the surrounding namespace, which was often very unexpected. List comprehensions can also break when they're run at the top level of a class, as the class variables previously defined won't be accessible to the comprehension function (since classes don't have scopes).

Upvotes: 1

user1238367
user1238367

Reputation: 525

List comprehensions have their local scope since Python 3.0. This was to prevent scope leak.

https://docs.python.org/3.0/whatsnew/3.0.html

[...] note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a list() constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.

Upvotes: 2

user2357112
user2357112

Reputation: 280973

You're on Python 3, where a list comprehension has its own scope with its own local variables. allpre is not one of the comprehension's local variables.

Upvotes: 0

Reinier Hern&#225;ndez
Reinier Hern&#225;ndez

Reputation: 428

Try whitout [] in your print

print(locals()[k] for k in ['allpre'])

Upvotes: 0

Related Questions