Python 替换单引号(撇号除外)

Python Replace Single Quotes Except Apostrophes

我正在对单词列表执行以下操作。我从 Project Gutenberg 文本文件中读取行,用空格分隔每一行,执行一般标点符号替换,然后将每个单词和标点符号标记打印在自己的行上,以便稍后进一步处理。我不确定如何用标签替换每个单引号或排除所有撇号。我目前的方法是使用编译后的正则表达式:

apo = re.compile("[A-Za-z]'[A-Za-z]")

并执行以下操作:

if "'" in word and !apo.search(word):
    word = word.replace("'","\n<singlequote>")

但这忽略了在带有撇号的单词周围使用单引号的情况。它也没有向我指示单引号是否邻接单词的开头或结尾。

示例输入:

don't
'George
ma'am
end.'
didn't.'
'Won't

示例输出(处理并打印到文件后):

don't
<opensingle>
George
ma'am
end
<period>
<closesingle>
didn't
<period>
<closesingle>
<opensingle>
Won't

关于这个任务我还有一个问题:既然区分 <opensingle><closesingle> 似乎相当困难,那么执行像

这样的替换会更明智吗?
word = word.replace('.','\n<period>')
word = word.replace(',','\n<comma>')

执行替换操作后?

我建议在这里聪明地工作:改用 nltk 或其他 NLP 工具包。

Tokenize words 像这样:

import nltk
sentence = """At eight o'clock on Thursday morning
Arthur didn't feel very good."""
tokens = nltk.word_tokenize(sentence)

您可能不喜欢像 don't 这样的缩写是分开的。实际上,这是预期的行为。参见 Issue 401

但是,TweetTokenizer 可以提供帮助:

from nltk.tokenize import tknzr = TweetTokenizer()
tknzr.tokenize("The code didn't work!")

如果涉及更多,RegexpTokenizer 可能会有所帮助:

from nltk.tokenize import RegexpTokenizer
s = "Good muffins cost .88\nin New York.  Please don't buy me\njust one of them."
tokenizer = RegexpTokenizer('\w+|$[\d\.]+|\S+')
tokenizer.tokenize(s)

那么正确标注分词词应该就容易多了。

进一步参考:

正确替换开始和结束真正需要的东西'regex。 要匹配它们,您应该使用:

  • ^' 用于启动 ' (opensingle),
  • '$ 用于结束 ' (closesingle).

很遗憾,replace 方法不支持正则表达式, 所以你应该改用re.sub

下面有一个示例程序,打印您想要的输出 (在 Python 3):

import re
str = "don't 'George ma'am end.' didn't.' 'Won't"
words = str.split(" ")
for word in words:
    word = re.sub(r"^'", '<opensingle>\n', word)
    word = re.sub(r"'$", '\n<closesingle>', word)
    word = word.replace('.', '\n<period>')
    word = word.replace(',', '\n<comma>')
    print(word)

我认为这可以从前瞻或后视引用中获益。 python 引用是 https://docs.python.org/3/library/re.html, and one generic regex site I often reference is https://www.regular-expressions.info/lookaround.html.

您的数据:

words = ["don't",
         "'George",
         "ma'am",
         "end.'",
         "didn't.'",
         "'Won't",]

现在我将定义一个包含正则表达式及其替换的元组。

In [230]: apo = (
    (re.compile("(?<=[A-Za-z])'(?=[A-Za-z])"), "<apostrophe>",),
    (re.compile("(?<![A-Za-z])'(?=[A-Za-z])"), "<opensingle>",),
    (re.compile("(?<=[.A-Za-z])'(?![A-Za-z])"), "<closesingle>", ),
    (re.compile("(?<=[A-Za-z])\.(?![A-Za-z])"), "<period>",),
)
     ...:      ...:      ...:      ...:      ...:      ...: 
In [231]: words = ["don't",
         "'George",
         "ma'am",
         "end.'",
         "didn't.'",
         "'Won't",]
     ...:      ...:      ...:      ...:      ...:      ...: 
In [232]: reduce(lambda w2,x: [ x[0].sub(x[1], w) for w in w2], apo, words)
Out[232]: 
['don<apostrophe>t',
 '<opensingle>George',
 'ma<apostrophe>am',
 'end<period><closesingle>',
 'didn<apostrophe>t<period><closesingle>',
 '<opensingle>Won<apostrophe>t']

以下是正则表达式的情况:

  1. (?<=[A-Za-z]) 是一个 lookbehind,意思是仅匹配(但 不消耗 )如果前面的字符是字母.
  2. (?=[A-Za-z])lookahead(仍然没有消耗)如果后面的字符是字母。
  3. (?<![A-Za-z]) 是一个负向回顾,意思是如果它前面有一个字母,那么它就不会匹配。
  4. (?![A-Za-z]) 是一个 负前瞻 .

请注意,我在 <closesingle> 中添加了一个 . 检查,并且 apo 中的顺序很重要,因为您可能会将 . 替换为 <period> ...

这是对单个词的操作,但也适用于句子。

In [233]: onelong = """
don't
'George
ma'am
end.'
didn't.'
'Won't
"""
     ...:      ...:      ...:      ...:      ...:      ...:      ...: 
In [235]: print(
    reduce(lambda sentence,x: x[0].sub(x[1], sentence), apo, onelong)
)

     ...:      ...: 
don<apostrophe>t
<opensingle>George
ma<apostrophe>am
end<period><closesingle>
didn<apostrophe>t<period><closesingle>
<opensingle>Won<apostrophe>t

(使用 reduce 是为了方便在 words/strings 上应用正则表达式的 .sub,然后为下一个正则表达式的 .sub 等保留该输出。