文件中的.readlines()列表未索引值

0 投票
3 回答
3802 浏览
提问于 2025-04-17 18:03

我有一个文本文件,里面的内容是一些列表,像这样:

[1,2,3,4]
[5,6,7,8]

我用以下代码把这些列表放进了一个大列表里:

t = open('filename.txt', 'r+w')
contents = t.readlines()

alist = []

for i in contents:
    alist.append(i)

当我运行

alist[0]

时,我得到了

[1,2,3,4]

但是当我运行

for a in alist:
    print a[0]

时,我得到了

[

而不是列表中的第一个值。

3 个回答

0

你现在手里有一个字符字符串的列表。里面有括号和逗号的字符串,并不意味着它就是一个列表,它只是一个包含括号和逗号的字符串而已。

alist就是这个列表。在你的循环中,a是这个列表中的一个项目:首先是alist[0],然后是alist[1],依此类推。所以,a[0]其实是在请求alist[0][0]alist[1][0]等等:也就是每一行的第一个字符。这就是你得到的结果。

如果你想把它转换成真正的Python列表,可以使用ast.literal_eval()

3

如果你刚刚继承或下载了这些文件,无法改变它们的格式,并且你知道它们应该被当作Python的 list 来处理,那么使用 ast.literal_eval 是最好的选择,正如steveha所解释的那样:

t = open('filename.txt', 'r')
alist = []    
for i in contents:
    alist.append(ast.literal_eval(i))

如果你继承或下载了这些文件,并且只是猜测它们的格式,可能它们实际上是应该被当作JSON格式来读取,因为它们确实是有效的JSON,同时也是有效的Python字面量。在这种情况下:

t = open('filename.txt', 'r')
alist = []    
for i in contents:
    alist.append(json.loads(i))

但是如果这些文件是你自己创建的,那么你应该以一种适合序列化的方式来创建它们。

比如,不要这样做:

t = open('filename.txt', 'w')
for i in alist:
    print >>t, i

而是应该这样做:

t = open('filename.txt', 'w')
json.dump(alist, t)

这样你就可以像这样写读取代码:

t = open('filename.txt', 'r')
alist = json.load(t)

像JSON、YAML或Pickle这样的序列化格式的主要目的是让你可以写入一个值,然后再读取回那个值。

printstr 等函数并不是为了这个目的设计的;它们是为了让你以最漂亮的方式展示一个值,即使这样做可能会让你在之后很难或根本无法再读取这个值。

repr 函数则介于两者之间。它是为了让人类在交互式提示符下使用而设计的,所以如果可能的话,它会给你一个字符串,你可以在提示符中输入这个字符串来得到相同的值。这意味着在某些情况下, ast.literal_evalrepr 的反向操作,就像 json.loadjson.dump 的反向操作一样。但即使在适用的情况下,也不应该完全依赖这个特性。


关于你的代码,有几点附加说明:

t = open('filename.txt', 'r+w')

如果你只是想读取文件,不要尝试以写入模式打开它。而且,如果你确实想同时进行读取和写入,正确的模式字符串是 r+,而不是 r+w。(你这样做在技术上是错误的,但大多数Python版本会忽略 w,所以你可以逃过一劫。)

如果模式是 r,你根本不需要指定它,因为这是默认值。

同时,你从来没有 close 文件。最简单的做法是使用 with 语句。

contents = t.readlines()

几乎没有好的理由去调用 readlines()。这个方法会给你一系列的行,但文件本身已经是一个行的序列。你只是在多做一份副本而已。

alist = []

for i in contents:
    alist.append(i)

这种模式——创建一个空列表然后在循环中添加元素——是非常常见的,Python为此提供了一个快捷方式,叫做列表推导式。推导式比显式循环更简洁、更易读、更不容易出错,而且速度更快,所以大多数情况下值得使用。

最后,给你的变量起有意义的名字是更好的做法。特别是如果你希望其他人(或者六个月后的你自己)能够调试你的代码。如果代码运行得很好,我们可以通过变量的作用来理解它们的含义,但如果不行,我们就无法修复它,除非我们能猜到它们“应该”代表什么,而名字是传达这一点的最佳方式。

所以,综合来看,你的原始代码可以写成:

with open('filename.txt') as textfile:
    alist = [line for line in textfile]

而各种修正版本是:

with open('filename.txt') as textfile:
    alist = [ast.literal_eval(line) for line in textfile]

with open('filename.txt') as textfile:
    alist = [json.loads(line) for line in textfile]

with open('filename.txt') as textfile:
    alist = json.load(textfile)
4

.readlines() 是用来读取文件中的每一行,并把它们当作字符串来处理。这个字符串的第一个字符是一个 [ 符号。

如果你想读取文本文件并把它转换成数据结构,最简单的方法就是使用 Python 内置的 eval() 函数。不过,更安全的做法是使用 ast.literal_eval()

http://docs.python.org/2/library/ast.html?highlight=literal#ast.literal_eval

推荐的代码:

import ast

with open("filename.txt") as f:
    alist = [ast.literal_eval(line) for line in f]

print(type(alist[0]))  # prints: <type 'list'>
print(alist[0]) # prints: [1,2,3,4]

我们几乎不想使用 .readlines(),因为它会一次性把文件中的所有行都读进来,如果文件很大,就会让你的程序占用很多内存。打开的文件句柄对象(在我的例子中是 f)可以用作迭代器,每次迭代时它会返回文件中的一行。因此,使用 for 循环或者列表推导式可以一次从文件中提取一行。这样,这个示例程序就不会把整个文件都放在内存中,而是每次只保留一行,同时构建列表。如果这个程序调用了 .readlines(),它会把所有行都保存在内存中,还会保存列表,这样内存的峰值使用量就会更高。(当然,对于像这个例子这样的小文件来说,这并没有太大关系。但为了节省内存,尽量采用高效的方式,为什么不呢?)

使用 with 来打开文件总是个好习惯。这样你就可以确保在完成操作后,文件会被正确关闭。

我们使用列表推导式来构建 ast.literal_eval() 的结果列表,对于给定的输入文件,每一行都会返回一个列表,所以 alist 将会是一个列表的列表。

撰写回答