Регулярное выражение соответствует символу «_», только если его нет в имени пользователя

#python #python-3.x #regex #regex-lookarounds #re

#python #python-3.x #регулярное выражение #регулярное выражение -поиск #python-re

Вопрос:

Контекст и объяснение

Я создаю telegram-бота, и я хочу добавить символ "" исключения перед каждым "_" символом, которого нет в имени пользователя (слово, начинающееся с "@" ), например "@username_" , чтобы предотвратить некоторые ошибки уценки (на самом деле в telegram "_" символ используется для выделения строки курсивом).

Так, например, имея эту строку:

 "hello i like this char _ write me lol_ @myusername_"
 

я хочу, чтобы мне соответствовали только первые два "_" символа, но не третий


Вопрос

как правильно сделать это с помощью шаблона регулярных выражений?


Ожидаемые условия и соответствие

Условие Совпадение
"_" один: ( "_" ) ДА
"_" одним словом, без "@" : ( "lol_" ) ДА
"_" в слове, начинающемся с "@" : ( "@username_" ) НЕТ
"_" в слове, содержащем "@" после "@" : ( "lol@username_" ) НЕТ
"_" в слове, содержащем "@" перед "@" : ( "lol_@username" ) ДА
"_" в мире, подобном: ( "lol_@username_" ) первый: ДА, второй: НЕТ

Что я пробовал

до сих пор я пришел к этому, но это не работает должным образом:

 "(?=[^@] )(?:s[^s]*(_)[^s]*s)"
 

Редактировать

Я также хочу, чтобы в этой строке: "lol_@username_" был сопоставлен первый символ "_"

Комментарии:

1. (?:^|s)((?:[^@s]*?)_(?:[^@s]*?))(?:s|$)

2. @OlvinRoght спасибо, но здесь это не работает при удалении "@"

3. @OlvinRoght Достаточно только последнего бита: (?:^|s)((?:[^@s]*?)_(?:[^@s]*?))(?=s|$) ( например, так )

4. Вы извлекаете или заменяете? Итак, у вас есть "hello i like this char _ write me lol_ @myusername_" , каков ожидаемый результат?

5. @LeonardoScotti Это работает

Ответ №1:

Вы можете сопоставить все символы, не являющиеся пробелами, после сопоставления @ и записать _ их в группу, используя чередование. Если обратный вызов re.sub, проверьте, существует ли группа 1.

Если это так, верните экранированное подчеркивание или исключенное значение группы 1 (которое также является подчеркиванием), иначе верните совпадение, чтобы оставить его неизменным.

 @S |(_)
 

Демонстрация регулярных выражений

 import re

strings = [
    "_",
    "lol_",
    "@username_",
    "lol@username_",
    "lol_@username",
    "lol_@username_"
]

for s in strings:
    result = re.sub(
        r"@S |(_)",
        lambda x: x.group(1).replace("_", r"_") if x.group(1) else x.group(),
        s
    )
    print(result)
 

Вывод

 _
lol_
@username_
lol@username_
lol_@username
lol_@username_
 

Ответ №2:

Основываясь на комментарии @OlvinRoght, с небольшой правкой, это должно сработать:

Регулярное выражение

((?:^|s)(?:[^@s]*?))(_)((?:[^@s]*?))(?=@|s|$)

Пример кода

 import re

text = '_hi hello i like this char _ write me lol_ _word something_ @myusername_ something_@username_'

regex = r"((?:^|s)(?:[^@s]*?))(_)((?:[^@s]*?))(?=@|s|$)"

# Leave the first and last capturing group as-is and replace the underscore with '_'
subst = "\1\\_\3"

print( re.sub(regex, subst, text) )
 

Ожидаемый результат:

 _hi hello i like this char _ write me lol_ _word something_ @myusername_ something_@username_
 

ДЕМОНСТРАЦИЯ

Посмотрите на это вживую

Примечание:

Хотя это работает, ответ @TheFourthBird быстрее. (И, по-моему, более элегантно.)

Ответ №3:

Извлечение с помощью библиотеки регулярных выражений PyPI:

 import regex
string = "hello i like this char _ write me lol_ @myusername_"
print(regex.findall(r'(?<!S)@w (*SKIP)(*F)|_', string))
# ['_', '_']
 

Смотрите Доказательство Python.

Объяснение

 --------------------------------------------------------------------------------
  (?<!                     look behind to see if there is not:
--------------------------------------------------------------------------------
    S                       non-whitespace (all but n, r, t, f,
                             and " ")
--------------------------------------------------------------------------------
  )                        end of look-behind
--------------------------------------------------------------------------------
  @                        '@'
--------------------------------------------------------------------------------
  w                       word characters (a-z, A-Z, 0-9, _) (1 or
                           more times (matching the most amount  possible))
--------------------------------------------------------------------------------
  (*SKIP)(*F)              skip the match, search from the failure location
--------------------------------------------------------------------------------
  |                        or
--------------------------------------------------------------------------------
  _                        a '_' char
 

Удалить с помощью re :

 import re
string = "hello i like this char _ write me lol_ @myusername_"
print(re.sub(r'(?<!S)(@w )|_', r'1', string))
# hello i like this char  write me lol @myusername_
 

Смотрите Доказательство Python.

Заменить на re :

 import re
string = "hello i like this char _ write me lol_ @myusername_"
print(re.sub(r'(?<!S)(@w )|_', lambda x: x.group(1) or "-", string))
# hello i like this char - write me lol- @myusername_
 

Смотрите другое доказательство Python.

Комментарии:

1. если вы измените строку замены с "-" на r"_" , то третий ответ будет выполнять то, что запросил OP … предполагая, что имена пользователей telegram содержат только символы word w

2. @LeonardoScotti Почему вы тестируете шаблон регулярных выражений PyPI в pythex? Вы import regex (после установки pip install regex )? Вы должны протестировать его с включенной опцией PCRE, см. Эту демонстрацию регулярных выражений.

3. извините, я заметил и удалил комментарий

4. третье работает хорошо, но я хочу, чтобы, например, в "lol_@username_" первом "_" было сопоставлено, а второе — нет

5. @LeonardoScotti Изменение вопроса сейчас в целом не является хорошей идеей, это делает недействительными текущие ответы. Вы можете удалить (?<!S) и использовать @w (*SKIP)(*F)|_ , см. Эту демонстрацию регулярных выражений.

Ответ №4:

Я предполагаю, что вы заботитесь только о @ том, чтобы быть в начале слова. Вы можете использовать re.sub вместе с replace и (?:s|^)[^@]S b для сопоставления слов, которые соответствуют вашей спецификации:

 import re

s = "hello i like this char _ write me lol_ @myusername_ asd@_a @_asdf"
s = re.sub(r"(?:s|^)[^@]S*b", lambda x: x.group().replace("_", r"_"), s)
print(s) # => hello i like this char _ write me lol_ @myusername_ asd@_a @_asdf
 

Если вы хотите @ появиться где-нибудь в word, попробуйте (?:s|^)[^@s] b :

 s = "he_llo i like this char _ write me lol_ @myusername_ asd@_a @_asdf"
s = re.sub(r"(?:s|^)[^@s] b", lambda x: x.group().replace("_", r"_"), s)
print(s) # => he_llo i like this char _ write me lol_ @myusername_ asd@_a @_asdf
 

Согласно комментарию OP, похоже, что последняя спецификация заключается в экранировании _ , которые находятся где угодно, кроме как после @ в слове:

 >>> s = "he_llo i lol_@username_ _ write me lol_ @myusername_ asd@_a @_asdf"
>>> re.sub(r"(?:s|^)[^@] @", lambda x: x.group().replace("_", r"_"), s)
'he\_llo i lol\_@username_ \_ write me lol\_ @myusername_ asd@_a @_asdf'
 

Комментарии:

1. это работает хорошо, но я хочу, чтобы, например, в "lol_@username_" первом "_" было сопоставлено, а во втором — нет

2. Обновлено, хотя у вас уже есть другие ответы. Похоже, это не очень хорошо соответствует вашему названию, хотя «(слово, начинающееся с @)». Рекомендуется заранее добавить все ваши требования, чтобы избежать догадок.