Python: эффективная замена подстроки - PullRequest
3 голосов
/ 12 августа 2011

У меня есть такой код:

def escape_query(query):
    special_chars = ['\\','+','-','&&','||','!','(',')','{','}','[',']',
                     '^','"','~','*','?',':']
    for character in special_chars:
        query = query.replace(character, '\\%s' % character)
    return query

Эта функция должна экранировать все вхождения каждой подстроки (Примечание && и ||) в special_characters с обратной косой чертой.

Я думаю, что мой подход довольно уродлив, и я не мог перестать задаваться вопросом, нет ли лучших способов сделать это.Ответы должны быть ограничены стандартной библиотекой.

Ответы [ 4 ]

2 голосов
/ 12 августа 2011

Следующий код имеет точно такой же принцип, как и у стевехи.
Но я думаю, что он отвечает вашим требованиям ясности и удобства обслуживания, поскольку специальные символы по-прежнему перечислены в том же списке, что и ваш.

С этим кодом:
при выполнении определения функции создается объект со значением (регулярное выражение re.compile(special_chars_pattern)), полученным в качестве аргумента по умолчанию, и этому назначается имя reg объект и определен как параметр для функции.
Это происходит только один раз, в момент выполнения определения функции, которое выполняется только один раз во время компиляции.
Это означает, что во время выполненияскомпилированный код, который выполняется после компиляции, каждый раз, когда будет выполнен вызов функции, это создание и присвоение больше не будут выполняться: объект regex уже существует и постоянно зарегистрирован и доступен в кортеже func_defaults , который является определяющим атрибутом функции.
Интересно, если во время выполнения выполняется несколько вызовов функции, потому что Python не должен искать регулярное выражение снаружи, если оно было определено снаружи, или переназначать его параметру reg , если он был передан как простойаргумент.

2 голосов
/ 12 августа 2011

Использование reduce:

def escape_query(query):
  special_chars =  ['\\','+','-','&&','||','!','(',')','{','}','[',']',
                     '^','"','~','*','?',':']
  return reduce(lambda q, c: q.replace(c, '\\%s' % c), special_chars, query)
1 голос
/ 12 августа 2011

Если я правильно понимаю ваши требования, некоторые из специальных "символов" являются двухсимвольными строками (в частности: "&&" и "||"). Лучший способ создать такую ​​странную коллекцию - использовать регулярное выражение. Вы можете использовать класс символов, чтобы сопоставить что-нибудь, длиной в один символ, а затем использовать вертикальные черты для разделения некоторых альтернативных шаблонов, и они могут быть многосимвольными. Самая хитрая часть - это экранирование символов от обратной косой черты; например, чтобы соответствовать "||" вам нужно поставить г '\ | \ |' потому что вертикальная черта является особой в регулярном выражении. В классе символов обратная косая черта является особенной, как и '-' и ']'. Код:

import re
_s_pat = r'([\\+\-!(){}[\]^"~*?:]|&&|\|\|)'
_pat = re.compile(_s_pat)

def escape_query(query):
    return re.sub(_pat, r'\\\1', query)

Я подозреваю, что вышеизложенное - самое быстрое решение вашей проблемы в Python, поскольку оно сводит работу к механизму регулярных выражений, который написан на C.

Если вам не нравится регулярное выражение, вы можете упростить его просмотр, используя подробный формат, и скомпилировать, используя флаг re.VERBOSE. Затем вы можете растягивать регулярные выражения на несколько строк и размещать комментарии после любых частей, которые вы считаете запутанными.

Или вы можете создать свой список специальных символов, как вы уже сделали, и запустить его с помощью этой функции, которая автоматически скомпилирует шаблон регулярного выражения, который соответствует любой альтернативе в списке. Я удостоверился, что это ничего не будет соответствовать, если список пуст.

import re
def make_pattern(lst_alternatives):
    if lst_alternatives:
        temp = '|'.join(re.escape(s) for s in lst_alternatives)
        s_pat = '(' + temp + ')'
    else:
        s_pat = '$^' # a pattern that will never match anything
    return re.compile(s_pat)

Кстати, я рекомендую вам поместить строку и предварительно скомпилированный шаблон вне функции, как я показал выше. В вашем коде Python будет запускать код при каждом вызове функции, чтобы построить список и связать его с именем special_chars.

Если вы не хотите помещать в пространство имен ничего, кроме функции, вот способ сделать это без каких-либо накладных расходов во время выполнения:

import re
def escape_query(query):
    return re.sub(escape_query.pat, r'\\\1', query)

escape_query.pat = re.compile(r'([\\+\-!(){}[\]^"~*?:]|&&|\|\|)')

Вышеприведенное имя использует имя функции для поиска атрибута, который не будет работать, если вы перепривязаете имя функции позже. Здесь обсуждается это и хорошее решение: как функция python может получить доступ к своим собственным атрибутам?

(Примечание: вышеприведенный абзац заменяет некоторые вещи, включая вопрос, который обсуждался в комментариях к обсуждению ниже.)

На самом деле, после дальнейших размышлений, я думаю, что это чище и более Pythonic:

import re

_pat = re.compile(r'([\\+\-!(){}[\]^"~*?:]|&&|\|\|)')

def escape_query(query, pat=_pat):
    return re.sub(pat, r'\\\1', query)

del(_pat) # not required but you can do it

Во время компиляции escape_query() объект, связанный с именем _pat, будет связан с именем внутри пространства имен функции (это имя pat). Затем вы можете позвонить del(), чтобы отменить привязку имени _pat, если хотите. Это хорошо инкапсулирует шаблон внутри функции, совершенно не зависит от имени функции и позволяет передавать альтернативный шаблон, если вы хотите.

P.S. Если бы ваши специальные символы всегда были длиной в один символ, я использовал бы следующий код:

_special = set(['[', ']', '\\', '+']) # add other characters as desired, but only single chars

def escape_query(query):
    return ''.join('\\' + ch if (ch in _special) else ch  for ch in query)
0 голосов
/ 12 августа 2011

Не уверен, что это лучше, но работает и, вероятно, быстрее.

def escape_query(query):
    special_chars = ['\\','+','-','&&','||','!','(',')','{','}','[',']', '^','"','~','*','?',':']
    query = "".join(map(lambda x: "\\%s" % x if x in special_chars else x, query))
    for sc in filter(lambda x: len(x) > 1, special_chars):
        query = query.replace(sc, "\%s" % sc)
    return query
...