【问题标题】:A more efficient pythonic way to use if condition一种更有效的使用 if 条件的 Pythonic 方式
【发布时间】:2018-02-07 22:05:20
【问题描述】:

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

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

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

【问题讨论】:

  • 你可以用a call to any and a generator expression替换每个循环。
  • 我认为,如果您发现 难以理解,那么您就有问题了。我认为这已经是最简单的方法了。
  • @StefanPochmann 确实.. 我去喝杯咖啡/

标签: python if-statement search coding-efficiency


【解决方案1】:

这很难理解(在我看来),所以我想知道是否有人可以用更短的行建议一些更 Pythonic 的东西?

一般来说,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

我可以合并两个 for 循环吗?

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

如果我合并两个 for 循环,会不会消耗更多时间?

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

有点牵强,但也许您甚至可以使用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 答案中的正则表达式建议。但是,设置方法仅表示“小建议”-我怀疑它是否可以应用于您的情况。但你需要判断。

【讨论】:

  • 不错的额外功能!但我相信你对anyfor-loop 更快是错误的。为什么会更快?我对它们进行了测试,每次测试 3 次,在它们之间交替进行。 for-loop 稍微。它的时间是 12.46 到 12.51 秒,而any 的时间是 12.61 到 12.63 秒。我的测试:timeit(lambda: contains_at_least_one(data, words), number=1000)data = list(map(str, range(1000)))words = list(map(str, range(1000, 2000)))
  • @StefanPochmann Oups,我直接走进去。通常我总是在做出这样的陈述之前进行基准测试。我会纠正它。 :)
  • 哎呀,我只记得我的data 应该是一个字符串,而不是一个列表。但我认为这不会影响我的观点。
  • 是的,显式循环可以避免生成器开销。即使any 以 C 速度执行,该生成器开销仍占主导地位。对于很长的*_WORDS,显式执行循环几乎快 2 倍。
  • @StefanPochmann 它在某种程度上影响了这一点,因为inO(n) 这使得辅助函数O(n*m) 所以常量因素(如生成器开销)不太重要。
【解决方案2】:

由于anys,这更紧凑并且短路,就像您的显式循环一样。

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,最后两个ifs的顺序会影响结果。这不是好的做法。

【讨论】:

  • 为什么第二个点不是好习惯?我可以将这些条件想象为过滤器,其中负过滤器更强并且首先拒绝错误的序列,并且只有在此之后才能应用正过滤器。
  • 你不是在说if ...: return False吗? :-P 在这里你可以实际上重写它。致not any(...) and any(...)。或者至少将第二个if 替换为return any(...)
  • 您的第一个问题:当我为这个论坛简化代码时,我忘记了 .lower()。 @erhesto 回答了您的第二个问题,如果它包含负面词,我首先不想要它,所以我不会浪费时间检查正面词。进一步:很好的解决方案!
  • @erhesto 因为其他人看起来很可疑。如果其他人也可以看到此代码,我可能会在此处添加注释。
  • @Ev.Kounis 另外,检查 NEGATIVE_WORDS 发生了什么(在您最近的几个解决方案中)?也许你应该去喝更多咖啡:-)
【解决方案3】:

除了使用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 结构时。

【讨论】:

  • 不是真正可读的代码(但你已经提到过)但是很好的解释!只是一个额外的注意事项:当您转换为集合时,它不会匹配包含任何类型空格的单词以及单词片段。 :)
  • @MSeifert 当然,这完全取决于这些列表中的单词,它们是否可以包含空格或逗号,当然还有单词片段是否也应该匹配。
猜你喜欢
  • 2013-11-18
  • 1970-01-01
  • 2010-11-22
  • 1970-01-01
  • 2018-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多