【问题标题】:Bulk replace with regular expressions in Python在 Python 中用正则表达式批量替换
【发布时间】:2013-06-20 20:57:12
【问题描述】:

对于 Django 应用程序,如果我的数据库中有与匹配项相关的资源,我需要将字符串中所有出现的模式转换为链接。

现在,流程如下: - 我使用 re.sub 处理很长的文本字符串 - 当 re.sub 找到模式匹配时,它会运行一个函数来查找该模式是否与数据库中的条目匹配 - 如果有匹配项,则将链接包装在匹配项周围。

问题是有时对数据库有数百次点击。我想做的是对数据库进行一次批量查询。

那么:你能在 Python 中使用正则表达式进行批量查找和替换吗?

作为参考,这里是代码(对于好奇,我正在查找的模式是为了合法引用):

def add_linked_citations(text):
    linked_text = re.sub(r'(?P<volume>[0-9]+[a-zA-Z]{0,3})\s+(?P<reporter>[A-Z][a-zA-Z0-9\.\s]{1,49}?)\s+(?P<page>[0-9]+[a-zA-Z]{0,3}))', create_citation_link, text)
    return linked_text

def create_citation_link(match_object):
    volume = None
    reporter = None
    page = None
    if match_object.group("volume") not in [None, '']:
        volume = match_object.group("volume")
    if match_object.group("reporter") not in [None, '']:
        reporter = match_object.group("reporter")
    if match_object.group("page") not in [None, '']:
        page = match_object.group("page")

    if volume and reporter and page: # These should all be here...
        # !!! Here's where I keep hitting the database
        citations = Citation.objects.filter(volume=volume, reporter=reporter, page=page)
        if citations.exists():
            citation = citations[0] 
            document = citation.document
            url = document.url()
            return '<a href="%s">%s %s %s</a>' % (url, volume, reporter, page)
        else:
            return '%s %s %s' % (volume, reporter, page)

【问题讨论】:

  • 你错过了一点。 reporter_code 来自哪里?
  • 非常长的文本字符串有多长? Citation.objects.count() 千、百万是什么?
  • 对不起,reporter_code -- 为了这个问题,我试图简化我的代码,但忘记删除它。
  • 关于你的第二个问题——目前大约是 300,000,尽管我预计它很快就会达到数百万。

标签: python regex django


【解决方案1】:

很抱歉,如果这是显而易见的错误(在 4 小时内没有人提出建议令人担忧!),但为什么不搜索所有匹配项,对所有内容进行批量查询(一旦获得所有匹配项就很容易),并且然后用结果字典调用 sub (所以函数从字典中提取数据)?

你必须运行两次正则表达式,但似乎数据库访问是昂贵的部分。

【讨论】:

  • 我原以为会有更优雅的解决方案——但也许没有!我会试一试,我会告诉你进展如何。
【解决方案2】:

您可以使用返回匹配对象的finditer 使用单个正则表达式传递来完成此操作。

匹配对象有:

  • 一种返回命名组字典的方法,groupdict()
  • 原文中匹配的开始和结束位置,span()
  • 原始匹配文本,group()

所以我建议你:

  • 使用finditer 列出文本中的所有匹配项
  • 列出匹配中的所有唯一卷、记者、页面三元组
  • 查找这些三元组
  • 将每个匹配对象与三元组查找的结果(如果找到)相关联
  • 处理原始文本,按匹配范围分割并插入查找结果。

我通过组合Q(volume=foo1,reporter=bar2,page=baz3)|Q(volume=foo1,reporter=bar2,page=baz3)... 的列表实现了数据库查找。也许有更有效的方法。

这是一个未经测试的实现:

from django.db.models import Q
from collections import namedtuple

Triplet = namedtuple('Triplet',['volume','reporter','page'])

def lookup_references(matches):
  match_to_triplet = {}
  triplet_to_url = {}
  for m in matches:
    group_dict = m.groupdict()
    if any(not(x) for x in group_dict.values()): # Filter out matches we don't want to lookup
      continue
    match_to_triplet[m] = Triplet(**group_dict)
  # Build query
  unique_triplets = set(match_to_triplet.values())
  # List of Q objects
  q_list = [Q(**trip._asdict()) for trip in unique_triplets]
  # Consolidated Q
  single_q = reduce(Q.__or__,q_list)
  for row in Citations.objects.filter(single_q).values('volume','reporter','page','url'):
    url = row.pop('url')
    triplet_to_url[Triplet(**row)] = url
  # Now pair original match objects with URL where found
  lookups = {}
  for match, triplet in match_to_triplet.items():
    if triplet in triplet_to_url:
      lookups[match] = triplet_to_url[triplet]
  return lookups

def interpolate_citation_matches(text,matches,lookups):
  result = []
  prev = m_start = 0
  last = m_end = len(text)
  for m in matches:
    m_start, m_end = m.span()
    if prev != m_start:
      result.append(text[prev:m_start])
    # Now check match
    if m in lookups:
      result.append('<a href="%s">%s</a>' % (lookups[m],m.group()))
    else:
      result.append(m.group())
  if m_end != last:
    result.append(text[m_end:last])
  return ''.join(result)

def process_citations(text):
  citation_regex = r'(?P<volume>[0-9]+[a-zA-Z]{0,3})\s+(?P<reporter>[A-Z][a-zA-Z0-9\.\s]{1,49}?)\s+(?P<page>[0-9]+[a-zA-Z]{0,3}))'
  matches = list(re.finditer(citation_regex,text))
  lookups = lookup_references(matches)
  new_text = interpolate_citation_matches(text,matches,lookups)
  return new_text

【讨论】:

  • 我花了一段时间才明白这一点,但我认为你的想法是你在比赛中使用开始/结束对在 python 中进行替换,对吧?所以从某种意义上说,它正在用 python 中的手动处理取代第二次 re 调用?看看哪个更快会很有趣 - 一方面你的第二遍不匹配,另一方面 re lib 在 c 中(尽管在这种情况下有回调)。
  • @andrewcooke:很清楚:知道的唯一方法是使用样本数据进行测试。您可以使用回调重新运行正则表达式,并使用批量获取的结果填充查找表。如果不使用“真实世界”样本数据进行测试,我无法告诉您哪个会更快。
  • 谢谢,马特!有机会我会尽快尝试一下这个和 Andrew 的建议,我会报告哪个更快。
猜你喜欢
  • 2019-04-06
  • 2017-12-20
  • 2011-04-29
  • 2012-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多