使用Stanza处理文本时出现List index out of range报错解决
问题描述
我尝试从大型.txt文件中移除所有空行,但无论使用哪种方法,始终返回如下报错:
Traceback (most recent call last): File "C:\Users\svp12\PycharmProjects\practiques\main.py", line 53, in <module> doc = nlp(texts[line]) IndexError: list index out of range
如果不移除这些空行,后续2个for循环会触发IndexError,目前我用如下try/except逻辑规避报错:
try: for word in doc.sentences[0].words: noun.append(word.text) lemma.append(word.lemma) pos.append(word.pos) xpos.append(word.xpos) deprel.append(word.deprel) except IndexError: errors += 1 pass
我希望能够直接移除所有空行,无需通过捕获异常的方式规避IndexError,以下是完整项目代码:
import io import stanza import os def linecount(filename): ffile = open(filename, 'rb') lines = 0 buf_size = 1024 * 1024 read_f = ffile.read buf = read_f(buf_size) while buf: lines += buf.count(b'\n') buf = read_f(buf_size) return lines errors = 0 with io.open('@_Calvia_2018-01-01_2022-04-01.txt', 'r+', encoding='utf-8') as f: text = f.read() # replacing eos with \n, numbers and symbols texts = text.replace('eos', '.\n') texts = texts.replace('0', ' ').replace('1', ' ').replace('2', ' ').replace('3', ' ').replace('4', ' ')\ .replace('5', ' ').replace('6', ' ').replace('7', ' ').replace('8', ' ').replace('9', ' ').replace(',', ' ')\ .replace('"', ' ').replace('·', ' ').replace('?', ' ').replace('¿', ' ').replace(':', ' ').replace(';', ' ')\ .replace('-', ' ').replace('!', ' ').replace('¡', ' ').replace('.', ' ').splitlines() os.system("sed -i '/^$/d' @_Calvia_2018-01-01_2022-04-01.txt") # removing empty lines to avoid IndexError nlp = stanza.Pipeline(lang='ca') nouns = [] lemmas = [] poses = [] xposes = [] heads = [] deprels = [] total_lines = linecount('@_Calvia_2018-01-01_2022-04-01.txt') - 1 for line in range(50): # range should be total_lines which is 6682 noun = [] lemma = [] pos = [] xpos = [] head = [] deprel = [] # print('analyzing: '+str(line+1)+' / '+str(len(texts)), end='\r') doc = nlp(texts[line]) try: for word in doc.sentences[0].words: noun.append(word.text) lemma.append(word.lemma) pos.append(word.pos) xpos.append(word.xpos) deprel.append(word.deprel) except IndexError: errors += 1 pass try: for word in doc.sentences[0].words: head.extend([lemma[word.head-1] if word.head > 0 else "root"]) except IndexError: errors += 1 pass nouns.append(noun) lemmas.append(lemma) poses.append(pos) xposes.append(xpos) heads.append(head) deprels.append(deprel) print(nouns) print(lemmas) print(poses) print(xposes) print(heads) print(deprels) print("errors: " + str(errors)) # wierd, seems to be range/2-1
附带疑问
是否值得仅为执行移除空行的sed命令,就单独导入os模块?相关代码如下:
os.system("sed -i '/^$/d' @_Calvia_2018-01-01_2022-04-01.txt")
解决方案
问题根因
你的代码逻辑顺序存在明显问题:
- 先把磁盘文件的全部内容读入内存,做字符替换、
splitlines()得到texts列表,这时候空行已经存在于texts里了 - 之后才调用
sed命令修改磁盘上的原文件,这个操作完全不会改动已经加载到内存的texts列表 - 你用
linecount读取被sed修改后的磁盘文件行数,作为循环的范围依据,和内存里texts的实际长度不匹配,自然会触发索引越界
另外你当前运行环境是Windows,系统默认没有sed命令,那行os调用本身就是无效代码,根本没起到删除空行的作用。
修复步骤
- 删掉无效的
os.system(sed)调用和自定义的linecount函数,完全不需要为了删空行导入os模块,直接在Python内存处理阶段过滤空行即可,跨平台且可控。
修改文本处理部分的代码,在splitlines()之后直接过滤空行,同时剔除只有空白字符的无效行:with io.open('@_Calvia_2018-01-01_2022-04-01.txt', 'r', encoding='utf-8') as f: text = f.read() # replacing eos with \n, numbers and symbols texts = text.replace('eos', '.\n') texts = texts.replace('0', ' ').replace('1', ' ').replace('2', ' ').replace('3', ' ').replace('4', ' ')\ .replace('5', ' ').replace('6', ' ').replace('7', ' ').replace('8', ' ').replace('9', ' ').replace(',', ' ')\ .replace('"', ' ').replace('·', ' ').replace('?', ' ').replace('¿', ' ').replace(':', ' ').replace(';', ' ')\ .replace('-', ' ').replace('!', ' ').replace('¡', ' ').replace('.', ' ').splitlines() # 过滤空行和纯空白行 texts = [line.strip() for line in texts if line.strip()] - 改掉用索引遍历的写法,直接遍历过滤后的
texts列表,从根源避免索引和列表长度不匹配的问题。同时把异常捕获换成前置判断,逻辑更清晰、执行效率更高:nlp = stanza.Pipeline(lang='ca') nouns = [] lemmas = [] poses = [] xposes = [] heads = [] deprels = [] errors = 0 # 直接遍历过滤后的所有行,不需要手动计算行数 for line_content in texts: noun = [] lemma = [] pos = [] xpos = [] head = [] deprel = [] doc = nlp(line_content) # 提前判断是否识别到有效句子,替代异常捕获 if not doc.sentences: errors += 1 continue for word in doc.sentences[0].words: noun.append(word.text) lemma.append(word.lemma) pos.append(word.pos) xpos.append(word.xpos) deprel.append(word.deprel) # 处理依存关系头节点,增加边界判断避免索引越界 for word in doc.sentences[0].words: if word.head > 0 and word.head -1 < len(lemma): head.append(lemma[word.head-1]) else: head.append("root") nouns.append(noun) lemmas.append(lemma) poses.append(pos) xposes.append(xpos) heads.append(head) deprels.append(deprel)
关于os模块导入的结论
完全不值得。
一方面sed是类Unix系统的自带命令,Windows环境默认不支持,你写的那行调用在你当前的开发环境下根本无法正常执行,属于无效代码;另一方面过滤空行是非常简单的原生字符串操作,几行Python代码就能实现,逻辑可控还没有跨平台兼容问题,完全没必要调用外部命令,自然不需要为了这个功能导入os模块。
内容的提问来源于stack exchange,提问作者Questioneer
相关产品推荐
相关产品推荐

