Reputation: 18642
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
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
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
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
Reputation: 428
Try whitout [] in your print
print(locals()[k] for k in ['allpre'])
Upvotes: 0