嵌套函数声明的范围

Scope of nested function declarations

我有以下代码:

def function_reader(path):
    line_no = 0 
    with open(path, "r") as myfile:
        def readline():
            line_no +=1
            return myfile.readline()

Python 不断返回:

UnboundLocalError: local variable 'line_no' referenced before assignment

执行时line_no +=1

我知道问题是嵌套函数声明在 python 中有奇怪的作用域(尽管我不明白为什么要这样编程)。我主要想知道是否有一种简单的方法可以帮助 python 解析引用,因为我真的很喜欢它提供的功能。

不幸的是,在 Python 2.x 中没有办法做到这一点。嵌套函数只能读取封闭函数中的名称,不能重新分配它们。

一个变通方法是制作 line_no 列表,然后更改其单个项目:

def function_reader(path):
    line_no = [0]
    with open(path, "r") as myfile:
        def readline():
            line_no[0] += 1
            return myfile.readline()

然后您可以通过 line_no[0] 访问行号。下面是演示:

>>> def outer():
...     data = [0]
...     def inner():
...        data[0] += 1
...     inner()
...     return data[0]
...
>>> outer()
1
>>>

这个解决方案有效,因为我们没有重新分配名称 line_no,只是改变它引用的对象。


请注意,在 Python 3.x 中,使用 nonlocal statement:

可以轻松解决此问题
def function_reader(path):
    line_no = 0
    with open(path, "r") as myfile:
        def readline():
            nonlocal line_no
            line_no += 1
            return myfile.readline()

获取下一行并跟踪行号的更 Pythonic 方法是使用 enumerate 内置函数:

with open(path, "r") as my file:
    for no, line in enumerate(myfile, start=1):
        # process line

这将适用于所有当前 Python 版本。

很难说您在这里试图通过使用闭包实现什么。但是问题是,使用这种方法,当您从外部函数 return readline 时,您将以 ValueError: I/O operation on closed file 结束,或者如果您 return readline() 来自外部函数。

如果您只想重复调用 readline() 或遍历文件并记住当前行号,那么最好使用 class:

class FileReader(object):
    def __init__(self, path):
        self.line_no = 0
        self.file = open(path)

    def __enter__(self):
        return self

    def __iter__(self):
       return self

    def next(self):
        line = next(self.file)
        self.line_no += 1
        return line

    def readline(self):
        return next(self)

    def __exit__(self, *args):
        self.file.close()

用法:

with FileReader('file.txt') as f:
    print next(f)
    print next(f)
    print f.readline()
    print f.line_no # prints 3
    for _ in xrange(3):
        print f.readline() 
    print f.line_no # prints 6
    for line in f:
        print line
        break
    print f.line_no # prints 7