Можно ли упростить мой код?

Написал функцию-фильтр для текста. Если в тексте находится какое-то слово из запрещённого спам-списка - возвращаем 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 шт):

Автор решения: Павел

Использовал следующий алгоритм:

  1. При space_around == False при помощи list comprehenshions мы поочередно проверяем каждое слово из черного списка на включение в текст и возвращаем True, если итоговый список содержит хотя бы один элемент.

  2. При 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, это сделает ваш текущий код гораздо более понятным.

→ Ссылка
Автор решения: DiMithras

Решение с :

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))
→ Ссылка
Автор решения: Danis
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
  • он не будет отрабатывать несколько слов, только первое
→ Ссылка