Подтвердить что ты не робот

Разделенная строка Python без разделения экранированного символа

Есть ли способ разделить строку без разделения экранированного символа? Например, у меня есть строка и вы хотите разделить на ":", а не на "\:"

http\://www.example.url:ftp\://www.example.url

Результат должен быть следующим:

['http\://www.example.url' , 'ftp\://www.example.url']
4b9b3361

Ответ 1

Обратите внимание, что: не представляется символом, который нуждается в экранировании.

Самый простой способ, который я могу придумать для этого, - разделить на персонажа, а затем добавить его обратно, когда он экранирован.

Пример кода (В значительной степени необходимо некоторое улучшение):

def splitNoEscapes(string, char):
    sections = string.split(char)
    sections = [i + (char if i[-1] == "\\" else "") for i in sections]
    result = ["" for i in sections]
    j = 0
    for s in sections:
        result[j] += s
        j += (1 if s[-1] != char else 0)
    return [i for i in result if i != ""]

Ответ 2

Существует гораздо более простой способ использования регулярного выражения с отрицательным утверждением lookbehind:

re.split(r'(?<!\\):', str)

Ответ 3

Как говорит Игнасио, да, но не тривиально за один раз. Проблема в том, что вам нужно получить обратную связь, чтобы определить, есть ли у вас экранированный разделитель или нет, а базовый string.split не обеспечивает эту функциональность.

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

# Bear in mind this is not rigorously tested!
def escaped_split(s, delim):
    # split by escaped, then by not-escaped
    escaped_delim = '\\'+delim
    sections = [p.split(delim) for p in s.split(escaped_delim)] 
    ret = []
    prev = None
    for parts in sections: # for each list of "real" splits
        if prev is None:
            if len(parts) > 1:
                # Add first item, unless it also the last in its section
                ret.append(parts[0])
        else:
            # Add the previous last item joined to the first item
            ret.append(escaped_delim.join([prev, parts[0]]))
        for part in parts[1:-1]:
            # Add all the items in the middle
            ret.append(part)
        prev = parts[-1]
    return ret

s = r'http\://www.example.url:ftp\://www.example.url'
print (escaped_split(s, ':')) 
# >>> ['http\\://www.example.url', 'ftp\\://www.example.url']

В качестве альтернативы, проще всего следовать логике, если вы просто разделите строку вручную.

def escaped_split(s, delim):
    ret = []
    current = []
    itr = iter(s)
    for ch in itr:
        if ch == '\\':
            try:
                # skip the next character; it has been escaped!
                current.append('\\')
                current.append(next(itr))
            except StopIteration:
                pass
        elif ch == delim:
            # split! (add current to the list and reset it)
            ret.append(''.join(current))
            current = []
        else:
            current.append(ch)
    ret.append(''.join(current))
    return ret

Обратите внимание, что эта вторая версия ведет себя несколько иначе, когда она встречает двойные экраны, за которыми следует разделитель: эта функция позволяет экранировать escape-символы, так что escaped_split(r'a\\:b', ':') возвращает ['a\\\\', 'b'], потому что первый \ ускользает от второго, оставляя : интерпретированным как реальный разделитель. Так что кое-что, чтобы следить за.

Ответ 4

Отредактированная версия Генри отвечает с совместимостью Python3, тестирует и исправляет некоторые проблемы:

def split_unescape(s, delim, escape='\\', unescape=True):
    """
    >>> split_unescape('foo,bar', ',')
    ['foo', 'bar']
    >>> split_unescape('foo$,bar', ',', '$')
    ['foo,bar']
    >>> split_unescape('foo$$,bar', ',', '$', unescape=True)
    ['foo$', 'bar']
    >>> split_unescape('foo$$,bar', ',', '$', unescape=False)
    ['foo$$', 'bar']
    >>> split_unescape('foo$', ',', '$', unescape=True)
    ['foo$']
    """
    ret = []
    current = []
    itr = iter(s)
    for ch in itr:
        if ch == escape:
            try:
                # skip the next character; it has been escaped!
                if not unescape:
                    current.append(escape)
                current.append(next(itr))
            except StopIteration:
                if unescape:
                    current.append(escape)
        elif ch == delim:
            # split! (add current to the list and reset it)
            ret.append(''.join(current))
            current = []
        else:
            current.append(ch)
    ret.append(''.join(current))
    return ret

Ответ 5

Вот эффективное решение, которое правильно обрабатывает двойные экраны, т.е. любой последующий разделитель не экранируется. Он игнорирует неправильный одиночный escape-код в качестве последнего символа строки.

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

def split_esc(string, delimiter):
    if len(delimiter) != 1:
        raise ValueError('Invalid delimiter: ' + delimiter)
    ln = len(string)
    i = 0
    j = 0
    while j < ln:
        if string[j] == '\\':
            if j + 1 >= ln:
                yield string[i:j]
                return
            j += 1
        elif string[j] == delimiter:
            yield string[i:j]
            i = j + 1
        j += 1
    yield string[i:j]

Чтобы ограничители были длиннее одного символа, просто переместите я и j по длине разделителя в случае "elif". Это предполагает, что один escape-символ пропускает весь разделитель, а не один символ.

Протестировано с помощью Python 3.5.1.

Ответ 6

Я думаю, что простой синтаксический анализ C был бы намного более простым и надежным.

def escaped_split(str, ch):
    if len(ch) > 1:
        raise ValueError('Expected split character. Found string!')
    out = []
    part = ''
    escape = False
    for i in range(len(str)):
        if not escape and str[i] == ch:
            out.append(part)
            part = ''
        else:
            part += str[i]
            escape = not escape and str[i] == '\\'
    if len(part):
        out.append(part)
    return out

Ответ 7

Для этого нет встроенной функции. Здесь эффективная, общая и проверенная функция, которая даже поддерживает разделители любой длины:

def escape_split(s, delim):
    i, res, buf = 0, [], ''
    while True:
        j, e = s.find(delim, i), 0
        if j < 0:  # end reached
            return res + [buf + s[i:]]  # add remainder
        while j - e and s[j - e - 1] == '\\':
            e += 1  # number of escapes
        d = e // 2  # number of double escapes
        if e != d * 2:  # odd number of escapes
            buf += s[i:j - d - 1] + s[j]  # add the escaped char
            i = j + 1  # and skip it
            continue  # add more to buf
        res.append(buf + s[i:j - d])
        i, buf = j + len(delim), ''  # start after delim

Ответ 8

Я создал этот метод, который вдохновлен ответом Генри Кейтера, но имеет следующие преимущества:

  • Переменная escape-символ и разделитель
  • Не удаляйте escape-символ, если он на самом деле не ускользает.

Это код:

def _split_string(self, string: str, delimiter: str, escape: str) -> [str]:
    result = []
    current_element = []
    iterator = iter(string)
    for character in iterator:
        if character == self.release_indicator:
            try:
                next_character = next(iterator)
                if next_character != delimiter and next_character != escape:
                    # Do not copy the escape character if it is inteded to escape either the delimiter or the
                    # escape character itself. Copy the escape character if it is not in use to escape one of these
                    # characters.
                    current_element.append(escape)
                current_element.append(next_character)
            except StopIteration:
                current_element.append(escape)
        elif character == delimiter:
            # split! (add current to the list and reset it)
            result.append(''.join(current_element))
            current_element = []
        else:
            current_element.append(character)
    result.append(''.join(current_element))
    return result

Это тестовый код, указывающий на поведение:

def test_split_string(self):
    # Verify normal behavior
    self.assertListEqual(['A', 'B'], list(self.sut._split_string('A+B', '+', '?')))

    # Verify that escape character escapes the delimiter
    self.assertListEqual(['A+B'], list(self.sut._split_string('A?+B', '+', '?')))

    # Verify that the escape character escapes the escape character
    self.assertListEqual(['A?', 'B'], list(self.sut._split_string('A??+B', '+', '?')))

    # Verify that the escape character is just copied if it doesn't escape the delimiter or escape character
    self.assertListEqual(['A?+B'], list(self.sut._split_string('A?+B', '\'', '?')))

Ответ 9

построение на @user629923 предложения, но гораздо проще, чем другие ответы:

import re
DBL_ESC = "!double escape!"

s = r"Hello:World\:Goodbye\\:Cruel\\\:World"

map(lambda x: x.replace(DBL_ESC, r'\\'), re.split(r'(?<!\\):', s.replace(r'\\', DBL_ESC)))