【问题标题】:Python re.sub() optimizationPython re.sub() 优化
【发布时间】:2018-01-05 21:08:23
【问题描述】:

我有一个 python 列表,每个字符串都是以下 4 个可能的选项之一(当然名称会不同):

Mr: Smith\n
Mr: Smith; John\n
Smith\n
Smith; John\n

我希望将这些更正为:

Mr,Smith,fname\n
Mr,Smith,John\n
title,Smith,fname\n
title,Smith,John\n

用 4 个 re.sub() 就够简单了:

with open ("path/to/file",'r') as fileset:
    dataset = fileset.readlines()
for item in dataset:
    dataset = [item.strip() for item in dataset]    #removes some misc. white noise
    item = re.sub((.*):\W(.*);\W,r'\g<1>'+','+r'\g<2>'+',',item)
    item = re.sub((.*);\W(.*),'title,'+r'\g<1>'+','+r'\g<2>',item)
    item = re.sub((.*):\W(.*),r'\g<1>'+','+r'\g<2>'+',fname',item)
    item = re.sub((*.),'title,'+r'\g<1>'+',fname',item)

虽然这对于我正在使用的数据集来说很好,但我希望提高效率。
有没有一个单一的操作可以简化这个过程?

如果我忘记了报价或类似内容,请见谅;我现在不在我的工作站,我知道我已经删除了换行符 (\n)。

谢谢你,

【问题讨论】:

  • 提前编译正则表达式会有所帮助,因为它可以缓存它生成的状态机。反向引用也可能很慢。
  • 问题:你想用你的数据集很好地做什么?你想写一个新的(更正的)文件吗?
  • for item in dataset: 的逻辑是什么?你有一个双重嵌套循环。
  • @Beefster:编译正则表达式的差异通常可以忽略不计,因为字符串正则表达式无论如何都会被编译并存储在缓存中。
  • 好吧,我将插入数据库,但这只是几个步骤。短期内我将使用这些信息从各种网页中获取数据。

标签: python regex optimization


【解决方案1】:

简介

您可以将其减少到仅一行,而不是运行两个循环。改编自 How to iterate over the file in Python(并使用我的 代码 部分中的代码):

f = open("path/to/file",'r')
while True:
    x = f.readline()
    if not x: break
    print re.sub(r, repl, x)

请参阅 Python - How to use regexp on file, line by line, in Python 了解其他替代方案。


代码

为了查看,我已将您的文件更改为数组。

See regex in use here

^(?:([^:\r\n]+):\W*)?([^;\r\n]+)(?:;\W*(.+))?

注意:在 python 中你不需要所有这些,我这样做是为了在 regex101 上显示它,所以你的正则表达式实际上只是 ^(?:([^:]+):\W*)?([^;]+)(?:;\W*(.+))?

用法

See code in use here

import re

a = [
    "Mr: Smith",
    "Mr: Smith; John",
    "Smith",
    "Smith; John"
]
r = r"^(?:([^:]+):\W*)?([^;]+)(?:;\W*(.+))?"

def repl(m):
    return (m.group(1) or "title" ) + "," + m.group(2) + "," + (m.group(3) or "fname")

for s in a:
    print re.sub(r, repl, s)

说明

  • ^ 在行首断言位置
  • (?:([^:]+):\W*)? 可选匹配以下
    • ([^:]+) 捕获除: 之外的任何字符一次或多次进入捕获组 1
    • : 从字面上匹配这个
    • \W*匹配任意数量的非单词字符(从OP的原始代码复制,我假设\s*可以代替)
  • ([^;]+) 将除; 之外的任何字符一次或多次分组到捕获组 2 中
  • (?:;\W*(.+))? 可选匹配以下
    • ; 逐字匹配
    • \W* 匹配任意数量的非单词字符(复制自 OP 的原始代码,我假设可以使用 \s* 代替)
    • (.+)将任意角色捕获一次或多次进入捕获组 3

鉴于上述正则表达式部分的解释。 re.sub(r, repl, s) 的工作原理如下:

  • repl 是对 repl 函数的回调,它返回:
    • group 1 如果它捕获了任何东西,title 否则
    • group 2(它应该总是设置 - 再次使用 OP 的逻辑)
    • group 3 如果它捕获了任何东西,fname 否则

【讨论】:

  • 是的,即使您的建议将四种模式减少到只有一种(这可能会加快代码速度),请注意他的代码中的主要问题不是模式,而是冗余和嵌套循环。
  • 我认为他有一个错字:在他打开文件的那一行应该有with,而不是while,下一行从文件中读取所有行readlines
  • 干得好,但不是 Python 良好实践方面的专家,我认为 Mike Pennington 设计(请参阅您的其他替代方案链接)不像您建议的那样老派。类似this
【解决方案2】:

恕我直言,RegEx 在这里太复杂了,您可以使用经典的字符串函数将字符串 item 分割成块。为此,您可以使用partition(或rpartition)。

首先,将您的 item 字符串拆分为“记录”,如下所示:

item = "Mr: Smith\n Mr: Smith; John\n Smith\n Smith; John\n"
records = item.splitlines()
# -> ['Mr,Smith,fname', 'Mr,Smith,John', 'title,Smith,fname', 'title,Smith,John']

然后,您可以创建一个简短的函数来规范每个“记录”。 这是一个例子:

def normalize_record(record):
    # type: (str) -> str
    name, _, fname = record.partition(';')
    title, _, name = name.rpartition(':')
    title = title.strip() or 'title'
    name = name.strip()
    fname = fname.strip() or 'fname'
    return "{0},{1},{2}".format(title, name, fname)

这个函数比RegEx的集合更容易理解。而且,在大多数情况下,它会更快。

为了更好的集成,你可以定义另一个函数来处理每个item

def normalize(row):
    records = row.splitlines()
    return "\n".join(normalize_record(record) for record in records) + "\n"

演示:

item = "Mr: Smith\n Mr: Smith; John\n Smith\n Smith; John\n"
item = normalize(item)

你得到:

'Mr,Smith,fname\nMr,Smith,John\ntitle,Smith,fname\ntitle,Smith,John\n'

【讨论】:

  • 而正则表达式模式会比这更复杂吗?
  • 显然,你看过接受的答案了吗?
  • 是的,虽然它的布局不是很好,而且比必要的要长。我了解正则表达式如何成为一种诅咒,但我花了 10 年时间编写 Python 和另外 8 年使用 Perl 及其内置的正则表达式引擎。我现在宁愿阅读正则表达式解决方案,也不愿阅读你那神秘的 Python。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多