电子邮件解析器处理个人数据;在循环列表推导中使用时中断,然后也中断原始数据...然后使用地图

Email parser work on individual data; breaks when used in loops list comprehensions, then breaks on original data as well... then works with map

这里有一些奇怪的神秘行为。

EDIT 这真的很长很纠结,我已经编辑了10次了。 TL/DR 是在处理一些文本的过程中,我设法编写了一个函数:

有一个保存为 html 的 ipython 笔记本,它在这里显示了整个混乱:http://paul-gowder.com/wtf.html ---I've put a link at the top to jump past some irrelevant stuff. I've also made a[nother] gist that just has the problem code and some sample data, but since this problem seems to throw around a bunch of state somehow, I can't guarantee it'll be reproducible from it: https://gist.github.com/paultopia/402891d05dd8c05995d2

结束TL/DR,开始混乱

我正在那个旧的 enron 数据集上做一些玩具文本挖掘,我有以下一组函数来清理电子邮件,准备在加载 nltk 停用词等之后将它们变成文档术语矩阵。下面使用python中的email library 2.7

def parseEmail(document):
    # strip unnecessary headers, header text, etc.
    theMessage = email.message_from_string(document)
    tofield = theMessage['to']
    fromfield = theMessage['from']
    subjectfield = theMessage['subject']
    bodyfield = theMessage.get_payload()
    wholeMsgList = [tofield, fromfield, subjectfield, bodyfield]
    # get rid of any fields that don't exist in the email
    cleanMsgList = [x for x in wholeMsgList if x is not None]
    # now return a string with all that stuff run together
    return ' '.join(cleanMsgList)

def lettersOnly(document):
    return re.sub("[^a-zA-Z]", " ", document)

def wordBag(document):
    return lettersOnly(parseEmail(document)).lower().split()

def cleanDoc(document):
    dasbag = wordBag(document)
    # get rid of "enron" for obvious reasons, also the .com
    bagB = [word for word in dasbag if not word in ['enron','com']]
    unstemmed =[word for word in bagB if not word in stopwords.words("english")]
    return [stemmer.stem(word) for word in unstemmed]

print enronEmails[0][1]

print cleanDoc(enronEmails[0][1])

首先(T 减去半小时)运行在一封表示为 unicode 字符串的电子邮件上执行此操作产生了预期的结果:print cleanDoc(enronEmails[0][1]) 产生了一个词干列表。需要说明的是,enronEmails的底层数据是一个[label,message]列表的列表,其中label为整数0或1,message为unicode字符串。 (在 python 2.7 中。)

然后在 t-10,我添加了几行代码(因为删除和丢失,不幸的是......但见下文),其中有一些列表推导来从 enronEmails 中提取消息,运行 我对它们执行清理函数,然后将它们连接回字符串,以便通过 sklearn 方便地转换为文档术语矩阵。但是函数开始抛出错误。所以我戴上调试帽...

首先,我尝试重新运行原始定义和测试单元格。但是当我重新 运行 那个单元格时,我的电子邮件解析函数突然开始在 message_from_string 方法中抛出错误:

AttributeError: 'list' object has no attribute 'message_from_string'

所以这很奇怪。这是完全相同的函数,调用完全相同的数据:cleanDoc(enronEmails[0][1])。该函数在相同的数据上工作,我没有改变它。

所以检查以确保我没有改变数据。 enronEmails[0][1] 仍然是一个字符串。不是清单。我不知道为什么回溯认为我正在将列表传递给 cleanDoc()。我不是。

但是剧情变厚了

因此,为了发布这个 SO 问题,我开始着手创建一个完全可重现的示例。我从工作部分开始。要点:https://gist.github.com/paultopia/c8c3e066c39336e5f3c2

为了确保它能正常工作,首先我将它放在一个普通的 .py 文件中,然后 运行 它从命令行运行。有效。

然后我把它和所有其他东西一起放在 ipython 笔记本底部的一个单元格里。这也奏效了。

然后我在 enronEmails[0][1] 上尝试了 parseEmail 函数。 再次起作用。然后我一直回到不到五分钟前抛出错误的原始单元格并重新 运行 它(包括从 sklearn 导入,并包括所有函数的原始定义)。 它非常有效。

然后 然后我回去并再次尝试列表理解等。而这一次,我更仔细地跟踪发生了什么。添加以下单元格:

1.

def atLeastThreeString(cleandoc):
    return ' '.join([w for w in cleandoc if len(w)>2])
print atLeastThreeString(cleanDoc(enronEmails[0][1]))

这有效,并产生了预期的输出:一个包含超过 2 个字母的单词的字符串。但是之后: 2.

justEmails = [email[1] for email in enronEmails]
bigEmailsList = [atLeastThreeString(cleanDoc(email)) for email in justEmails]

突然间它开始抛出一个全新的错误,在回溯中的同一个地方:

AttributeError: 'unicode' object has no attribute 'message_from_string'

这非常有趣,因为我在一分钟前向它传递了 unicode 字符串并且它运行得很好。而且,只是为了加深情节,然后返回并重新 运行ning cleanDoc(enronEmails[0][1]) 抛出相同的错误

这让我发疯。创建一个新列表,然后尝试在该列表上 运行 函数 A,不仅会在新列表上抛出错误,而且还会导致函数 A 在数据上抛出错误,这怎么可能?工作?我 知道 我不会改变原始列表...

我已经在此处以 html 形式发布了整个笔记本,如果有人想查看完整代码和回溯:http://paul-gowder.com/wtf.html 相关部分从大约 2/3 的位置开始编号为 24-5 的单元格,它工作的地方,然后编号为 26 的单元格,它爆炸的地方。

帮助??

另一个编辑: 我在上面链接的 html 笔记本的底部添加了一些调试工作。如您所见,我已将问题追溯到循环行为,无论是以列表理解形式隐式完成还是显式完成。我的函数适用于仅电子邮件列表中的单个项目,但是当我尝试遍历该列表时,每个项目都失败,except 当我使用 map() 来做吧。 ???世界疯了吗?

我认为问题在于这些状态:

justEmails = [email[1] for email in enronEmails]
bigEmailsList = [atLeastThreeString(cleanDoc(email)) for email in justEmails] 

在 python 2 中,虚拟变量 email 泄漏到命名空间中,因此您正在覆盖 email 模块的名称,然后您试图在 python 字符串。 python2里面没有ntlk,所以没法测试,不过我想应该是这个吧