У меня есть этот кусок кода, который проверяет условия:

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = get_additional_data(data_row)
    for word in NEGATIVE_WORDS:
        if word in add_data.lower():
            return False
    for word in POSITIVE_WORDS:
        if word in add_data.lower():
            return True
    return False

За этим довольно трудно следовать (по моему мнению), поэтому мне было интересно, кто-нибудь может предложить что-то более питоническое с более короткими линиями? Могу ли я, например, объединить два цикла for? Если я объединю два цикла, будет ли это занимать больше времени?

1
CodeNoob 30 Авг 2017 в 12:04

3 ответа

Лучший ответ

Это более компактно и короткое замыкание благодаря any так же, как это сделали ваши явные циклы.

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = get_additional_data(data_row)
    if any(word in add_data.lower() for word in NEGATIVE_WORDS):  # negative check takes precedence.
        return False
    if any(word in add_data.lower() for word in POSITIVE_WORDS):
        return True
    return False

Пара вещей по этому поводу:

  • почему вы вызываете .lower() при поиске NEGATIVE_WORDS, а не POSITIVE_WORDS?
  • если add_data содержит оба NEGATIVE_WORDS и POSITIVE_WORDS, порядок последних двух if будет влиять на результат. Это не хорошая практика.
2
Ev. Kounis 30 Авг 2017 в 09:59

За этим довольно трудно следовать (по моему мнению), поэтому мне было интересно, кто-нибудь может предложить что-то более питоническое с более короткими линиями?

Обычно pythonic не означает более короткие строки. Питонический код может быть легко читаемым и понятным (по крайней мере, с небольшим фоном). Так что, если вам трудно читать, вы можете включить его в другую функцию:

# I'm not sure if the function name is a good fit, it's just a suggestion.
def contains_at_least_one(data, words):  
    for word in words:
        if word in data:
            return True
    return False

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = get_additional_data(data_row).lower()
    if contains_at_least_one(add_data, NEGATIVE_WORDS):
        return False
    if contains_at_least_one(add_data, POSITIVE_WORDS):
        return True
    return False

Могу ли я, например, объединить два цикла for?

На самом деле, нет. Потому что цикл NEGATIVE_WORDS должен иметь приоритет (по крайней мере, в вашем коде) над циклом POSITIVE_WORDS. За исключением того, что вы имели в виду факторинг в функции. Тогда посмотри сначала.

Если я объединю два цикла, будет ли это занимать больше времени?

Я не уверен, что вы имели в виду под «объединением» циклов, но если вы хотите, чтобы оно было короче, вы можете использовать any в подходе выше. Это эквивалентно for - циклу и короче - но, согласно тестам my и StefanPochmans, медленнее:

def contains_at_least_one(data, words):
    return any(word in data for word in words)

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = get_additional_data(data_row).lower()
    if contains_at_least_one(add_data, NEGATIVE_WORDS):
        return False
    if contains_at_least_one(add_data, POSITIVE_WORDS):
        return True
    return False

Вы можете даже уменьшить количество строк, используя and для return. Я бы не советовал, потому что такие конструкции не улучшают читабельность, но это ваше решение, и это один из способов «сократить» код:

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = get_additional_data(data_row).lower()
    return (not contains_at_least_one(add_data, NEGATIVE_WORDS) and
            contains_at_least_one(add_data, POSITIVE_WORDS))

Немного неправдоподобно, но, возможно, вы могли бы даже использовать set для ускорения этого. Это потребует, чтобы вы искали только совпадения всего слова (не частичные совпадения, не совпадения из нескольких слов):

def contains_at_least_one(data, words):
    return data.intersection(words)

def is_important(data_row):
    if data_row.get('important', None):
        return True
    add_data = set(get_additional_data(data_row).lower().split())  # set and split!
    return not contains_at_least_one(add_data, NEGATIVE_WORDS) and contains_at_least_one(add_data, POSITIVE_WORDS)

См. Также предложения регулярных выражений в ответе tobias_k, если вы не хотите, чтобы пунктуация нарушала ваше соответствие. Однако данный подход подразумевается только как «небольшое предложение» - я сомневаюсь, что он может быть применен в вашем случае. Но вы должны судить об этом.

2
MSeifert 30 Авг 2017 в 11:04

Помимо использования any, вы также можете объединить различные условия в один оператор return, хотя вопрос о том, яснее ли это, может зависеть от мнения.

def is_important(data_row):
    add_data = get_additional_data(data_row)
    return (data_row.get('important', None)
        or (not any(word in add_data.lower() for word in NEGATIVE_WORDS)
            and any(word in add_data         for word in POSITIVE_WORDS)))

Хотя, если get_additional_data стоит дорого, вы можете оставить это первое if отдельным.

Кроме того, вы, вероятно, можете ускорить проверку, преобразовав add_data в set из (строчных) слов в первую очередь, но это слегка меняет логику, как, например, это произойдет. не совпадают слова-фрагменты.

def is_important(data_row):
    add_data = set((word.lower() for word in get_additional_data(data_row).split()))
    return (data_row.get('important', None)
        or (not any(word in add_data for word in NEGATIVE_WORDS)
            and any(word in add_data for word in POSITIVE_WORDS)))

Или вместо .split() используйте, например, re.findall(r"\w+") чтобы найти слова без знаков препинания.

В зависимости от размера положительных и отрицательных списков, это может также окупиться, чтобы инвертировать чек, например, any(word in POSITIVE_WORDS for word in add_data.split()), особенно если это уже set структуры с быстрым поиском.

1
tobias_k 30 Авг 2017 в 09:36