Edit: Why are people downvoting this post? Are Python developers really this inept? It's a legitimate question, not one that's been answered in other places. I searched for a solution. I'm not an idiot. One parameter has a value and the other one is undefined, but if you actually read the post, you will see that both of them appear to be equally scoped.
First of all, I assure you that this question is unlike other questions involving the error message:
UnboundLocalError: local variable referenced before assignment closure method
As I'm looking at this code, it appears that the parameter, uuidString
, of the top-level method, getStockDataSaverFactory
, should actually be in-scope when the method returns its inner method, saveData
, as a first-class function object... because to my amazement, the parameter tickerName
IS in-scope and does have the value 'GOOG' when the saveData()
method is called (e.g. by the test method testDataProcessing_getSaverMethodFactory
), so we can actually see that it has an actual value when the method, getDataMethodFactory(..)
is called, unlike uuidString
.
To make the matter more obvious, I added the lines:
localUuidString = uuidString
and
experimentUuidString = localUuidString
to show that the parameter uuidString
has an available value when the method is inspected by a breakpoint.
def getStockDataSaverFactory(self, tickerName, uuidString, methodToGetData, columnList):
# This method expects that methodToGetData returns a pandas dataframe, such as the method returned by: self.getDataFactory(..)
localUuidString = uuidString
def saveData():
(data, meta_data) = methodToGetData()
experimentUuidString = localUuidString
methodToNameFile = self.getDataMethodFactory(tickerName, uuidString)
(full_filepathname, full_filename, uuidString) = methodToNameFile()
methodToSaveData = self.getDataFrameSaverFactory(methodToGetData, columnList, full_filepathname)
# We might want try/catch here:
methodToSaveData()
# A parameterless method that has immutable state (from a closure) is often easier to design around than one that expects parameters when we want to pass it with a list of similar methods
return (full_filepathname, full_filename, uuidString)
return saveData
def testDataProcessing_getSaverMethodFactory(self):
dataProcessing = DataProcessing()
getSymbols = dataProcessing.getSymbolFactory(
dataProcessing.getNasdaqSymbols(dataProcessing.getListOfNASDAQStockTickers))
tickers = getSymbols()
uuidString = 'FAKEUUID'
columnList = ['low', 'high']
tickerSubset = tickers[0:2]
methodsToPullData = map(lambda ticker: dataProcessing.getStockDataSaverFactory(ticker,
uuidString,
dataProcessing.getDataFactory(
ticker),
columnList), tickerSubset)
savedPathTuples = [f() for f in methodsToPullData]
savedFileNames = [pathTuple[0] for pathTuple in savedPathTuples]
for fileName in savedFileNames:
self.assertTrue(os.path.isfile(fileName))
os.remove(fileName)
Just to make it clear that uuidString
has no value but ticker
does have a value, I'm including this screenshot:
Notice that in the variable watch window, uuidString
is undefined, but ticker
has the string value of "A".
Is there something unique about Python (or Python 3) that is resulting in this behavior?
The problem is that you reference uuidString in the call to self.getMethodThatProvidesFullFilePathNameForPricesCsvFromUUIDAndTickerName
before you assign to it. The assignment makes it local to the scope of the innermost function and therefore, it is unassigned when you reference it.
A full description of the scoping rules is provided by: https://stackoverflow.com/a/292502/7517724
This simpler example reproduces your error to make the problem more clear:
class aclass():
def outer(self, uuidString):
def inner():
print(uuidString)
uuidString = 'new value'
return uuidString
return inner
a = aclass()
func = a.outer('a uuid')
val = func()
print(val)
The assignment in inner()
causes the uuidString
to be local to inner()
and therefore it is unassigned when the print(uuidString)
is call, which causes Python to raise the UnboundLocalError
.
You can fix the error by passing the variable in to your function with a default argument. Changing the definition of saveData
to pass uuidString
as a default argument, as:
def saveData(uuidString=uuidString):
will make it work as you expect.