Можно ли упростить мой код?
Написал функцию-фильтр для текста. Если в тексте находится какое-то слово из запрещённого спам-списка - возвращаем False. Но есть еще 3 параметр функции если space_around == True слова считаются только если они стоят отдельно (перед словом пробел или это начало строки и после слова пробел или точка). Но мне не нравится мой код визуально, как можно упростить его или это нормальная реализация?
def is_spam_words(text, spam_words, space_around=False):
text_l = text.lower()
for word in spam_words:
if text_l.find(word) != -1:
if not space_around:
return True
elif space_around:
if text_l[text_l.find(word) - 1] == ' ' or text_l.startswith(word)\
and text_l[text_l.find(word) + len(word)] == ' ':
return True
else:
return False
else:
return False
text = 'Молох бог ужасен.'
spam_words = ['лох']
print(is_spam_words(text, spam_words, True))
Ответы (3 шт):
Использовал следующий алгоритм:
При
space_around == Falseпри помощиlist comprehenshionsмы поочередно проверяем каждое слово из черного списка на включение в текст и возвращаемTrue, если итоговый список содержит хотя бы один элемент.При
space_around == True, когда нам необходимо проверить включение только целых слов в тексте, мы предварительно разбиваем текст на отдельные слова и уже проверяем их наличие в списке спам-слов.
Получилось довольно компактно:
def is_spam_words(text, spam_words, space_around=False):
if space_around:
return any([word for word in text.split() if word.lower() in spam_words])
return any([word for word in spam_words if word in text.lower()])
text = 'Молох бог ужасен.'
spam_words = ['лох']
print(is_spam_words(text, spam_words, True)) # False
print(is_spam_words(text, spam_words, False)) # True
По-прежнему рекомендую вам изучить list comprehenshions, это сделает ваш текущий код гораздо более понятным.
Решение с regex:
import re
def is_spam_words(text, spam_words, space_around=False):
pattern = r'\b{}\b' if space_around else '{}'
prog = re.compile(pattern.format('|'.join(spam_words)), re.I)
return bool(prog.search(text))
Если стоит space_around, то регулярное выражение будет иметь \b по краям, что отвечает за word boundary и вполне выполняет условие:
слова считаются только если они стоят отдельно (перед словом пробел или это начало строки и после слова пробел или точка).
Word boundary означает, что будет найдено слово не в составе какого-либо другого слова, а отдельно. Вокруг могут быть пробелы, точки. Тут правда стоит оговориться, что слова написанные через дефис будут считаться отдельными словами, то есть при поиске \bнибудь\b в строке какой-нибудь нибудь найдётся как отдельное слово.
Все слова в spam_words объединяются через |, то есть логическое или.
Дополнение по комментарию:
Разберём строку re.compile(pattern.format('|'.join(spam_words)), re.I)
Ранее мы записали в pattern либо {} либо \b{}\b в зависимости от условия space_around. У строковых переменных есть метод .format(), который подставляет передаваемые в нём значения вместо {}.
В данном случае подставит значение из '|'.join(spam_words), то есть все элементы массива spam_words разделённые знаком |:
spam_words = ['лох']
print('|'.join(spam_words)) # лох
spam_words = ['лох', 'питух']
print('|'.join(spam_words)) # лох|питух
Эти значения подствляются вместо {}, соответственно получаем регулярные выражения для обоих случаев:
- space_around = True
re.compile('\\bлох\\b', re.IGNORECASE)re.compile('\\bлох|питух\\b', re.IGNORECASE)
- space_around = False
re.compile('лох', re.IGNORECASE)re.compile('лох|питух', re.IGNORECASE)
re.compile()
Обычно излишний метод, можно обойтись строковой переменной и передать её напрямую в re.search() первым аргументом, но компиляция, всё же, полезна.
Во-первых, при компиляции я указываю флаг re.I, который ранее упоминал в комментарии к вопросу, с ним будет искать вне зависимости от того заглавные буквы в тексте или строчные.
Во-вторых, скомпилированный паттерн можно переиспользовать как объект для поиска в другом pattern.search() методе, где как аргумент передавать только текст. В моём случае паттерн именуется prog, также как и в документации к регулярным выражениям.
Но можно обойтись и без компиляции, тогда код будет выглядеть так:
import re
def is_spam_words(text, spam_words, space_around=False):
pattern = r'\b{}\b' if space_around else '{}'
return bool(re.search(pattern.format('|'.join(spam_words)), text, re.I))
def is_spam_words(text, spam_words, space_around=False):
text_l = text.lower()
for word in spam_words:
if word not in text_l:
return False
if not space_around:
return True
index = text_l.find(word)
return any((
text_l[index - 1] == ' ',
text_l[index+ len(word)] == ' ',
text_l.startswith(word),
))
text = 'Молох бог ужасен.'
spam_words = ['лох']
print(is_spam_words(text, spam_words, True))
можно сделать так. Но ваш код (и то во что я его переделал) имеет несколько проблем:
- Если
space_aroundравенFalse, то результат всегда будетTrue - он не будет отрабатывать несколько слов, только первое