一种更有效的使用 if 条件的 pythonic 方式

A more efficient pythonic way to use if condition

我有这段检查条件的代码:

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

这很难理解(在我看来),所以我想知道是否有人可以建议一些更 pythonic 和更短的行?例如,我可以合并两个 for 循环吗?如果我合并两个 for 循环,它会消耗更多时间吗?

由于 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

有几件事:

  • 为什么搜索 NEGATIVE_WORDS 而不是 POSITIVE_WORDS 时调用 .lower()
  • 如果add_data同时包含NEGATIVE_WORDSPOSITIVE_WORDS,最后两个if的顺序将影响结果。这不是好的做法。

This is quite hard to follow (in my opinion), so I was wondering if anyone can suggest something more pythonic with shorter lines?

通常 pythonic 并不意味着更短的行。 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

Could I for example merge the two for loops?

不是真的。因为 NEGATIVE_WORDS 循环应该优先于 POSITIVE_WORDS 循环(至少在您的代码中)。除了你的意思是把它分解成一个函数。那就先看看吧

If I merge two for loops, would it consumes more time?

我不确定你所说的 "merging" 循环是什么意思,但如果你想要它更短,你可以在上面的方法中使用 any。它相当于 for 循环并且更短 - 但是,根据我和 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

您甚至可以通过对 return 使用 and 来减少行数。我不会推荐它,因为这样的结构不会提高可读性,但这是你的决定,这是 "shorten" 代码的一种方式:

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))

有点牵强,但也许您甚至可以使用 sets 来加快速度。这将要求您只查找整个单词匹配(而不是部分匹配,而不是多词匹配):

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 答案中的正则表达式建议。但是 set 方法仅表示 "small suggestion" - 我怀疑它是否适用于您的情况。但是你需要判断。

除了使用 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 具有快速查找的结构。