【问题标题】:Python 3 parameter closed upon by inner/nested method falls out of scope and triggers UnboundLocalError内部/嵌套方法关闭的 Python 3 参数超出范围并触发 UnboundLocalError
【发布时间】:2018-06-10 21:43:51
【问题描述】:

编辑:为什么人们不赞成这篇文章? Python 开发者真的这么无能吗?这是一个合理的问题,而不是在其他地方得到回答的问题。我寻找解决方案。我不是白痴。一个参数有一个值,另一个是未定义的,但如果你真的阅读这篇文章,你会发现它们的范围似乎是相同的。

首先,我向你保证,这个问题不同于其他涉及错误信息的问题:

UnboundLocalError: local variable referenced before assignment closure method

当我查看这段代码时,似乎顶级方法getStockDataSaverFactory 的参数uuidString 在方法返回其内部方法saveData 时实际上应该在范围内,作为一流的函数对象...因为令我惊讶的是,参数tickerName 在范围内并且在调用saveData() 方法时确实具有值'GOOG'(例如通过测试方法testDataProcessing_getSaverMethodFactory ),所以我们实际上可以看到它在调用 getDataMethodFactory(..) 方法时有一个实际值,这与 uuidString 不同。

为了让事情更明显,我添加了以下几行:

localUuidString = uuidString

experimentUuidString = localUuidString

在断点检查方法时显示参数uuidString具有可用值。

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)

为了清楚说明 uuidString 没有价值,但 ticker 确实有价值,我将这个屏幕截图包括在内:

请注意,在变量监视窗口中,uuidString 未定义,但ticker 的字符串值为“A”。

Python(或 Python 3)是否有什么独特之处导致了这种行为?

【问题讨论】:

  • 你能发布完整的堆栈跟踪吗?这里没有加起来。在实际应用中,您所展示的内容没有任何错误。请参阅此repl.it - 定义并返回的函数维护对其定义的范围的访问 - 除非发生其他未显示的事情。
  • 这是一种有趣的创建对象dataProcessing = DataProcessing.DataProcessing()的方法?为什么它不仅仅是dataProcessing = DataProcessing(),有什么特别的原因吗?
  • 看在上帝的份上,请缩短函数名称....!为什么函数会返回其他函数?您调用这些函数,然后立即调用它们返回的函数。这似乎真的过于复杂了。
  • @NedBatchelder 你有没有在函数式​​编程中使用过柯里化,或者在 Javascript/node.js 中使用过 promise?还是 C# 中的通用 lambda?实际上,我在这里为 S.O. 保持简单。例子。在实践中,我会获取一个字符串列表,然后我可以将一组方法生成器函数流水线化,这些函数的功能类似于装饰器(作为方法组合的一种形式)。想想 string -> methodThatGetsData -> methodThatSavesRetrievedData -> methodThatTransformsSavedData -> methodThatSortsTransformedData -> 等。在实施时,您只需要提供方法生产者列表即可设置管道。
  • 好吧,你做到了:)

标签: python python-3.x lambda closures first-class-functions


【解决方案1】:

问题是您在分配给它之前在对self.getMethodThatProvidesFullFilePathNameForPricesCsvFromUUIDAndTickerName 的调用中引用了 uuidString。赋值使其在最内层函数的范围内是本地的,因此,当您引用它时,它是未赋值的。

范围规则的完整描述由以下人员提供:https://stackoverflow.com/a/292502/7517724

这个更简单的例子重现了你的错误,使问题更清楚:

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)

inner() 中的赋值导致 uuidStringinner() 是本地的,因此在调用 print(uuidString) 时它未赋值,这导致 Python 引发 UnboundLocalError

您可以通过使用默认参数将变量传递给您的函数来修复错误。更改saveData 的定义以将uuidString 作为默认参数传递,如下:

def saveData(uuidString=uuidString):

将使其按您的预期工作。

【讨论】:

  • 我发现第二个答案也很有帮助。它显示了作为问题的确切场景。 stackoverflow.com/a/34094235/891373 第 7 点和第 8 点。
  • 那么为什么这不是tickerName的问题?
  • @devinbost - 这不是问题,因为您没有在嵌套函数中为 tickerName 赋值。正如我在回答中解释的那样,赋值使其成为内部函数范围的局部变量。如果你不给它赋值,它会在外部范围内搜索变量。
  • @devinbost - 也许您假设 Python 一次解释您的代码一行,因此它不应该知道分配。当您运行脚本时,Python 会读取整个脚本并将其编译为字节码,然后执行。在编译阶段,看到uuidString的赋值,判断变量是本地的。
猜你喜欢
  • 2023-04-05
  • 1970-01-01
  • 2018-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-22
  • 2018-05-23
  • 2013-11-01
相关资源
最近更新 更多