【问题标题】:Programmatically generate if elif elif elif else以编程方式生成 if elif elif elif else
【发布时间】:2016-09-03 22:52:49
【问题描述】:

我想压缩一些看起来像这样的湿代码:

if slips[i] < 3000:
    ac.setBackgroundColor(wheel['slip'], 0, 0, 0)
    # setBackgroundOpacity deliberately omitted here
elif slips[i] < 3700:
    ac.setBackgroundColor(wheel['slip'], .2, .4, .2)
    ac.setBackgroundOpacity(wheel['slip'], 1)
elif slips[i] < 4100:
    ac.setBackgroundColor(wheel['slip'], 0, 1, 0)
    ac.setBackgroundOpacity(wheel['slip'], 1)
elif slips[i] < 4500:
    ac.setBackgroundColor(wheel['slip'], 0, 0, 1)
    ac.setBackgroundOpacity(wheel['slip'], 1)
else:
    ac.setBackgroundColor(wheel['slip'], 1, 0, 0)
    ac.setBackgroundOpacity(wheel['slip'], 1)

每次重复这段 sn-p 代码时,唯一改变的是背景画布(在本例中为 wheel['slip']),以及 if elif else 中的数字。

我首先想到的是做一些可以像这样使用的东西:

if_replacer(wheel['slip'], slips[i], 3000, 3700, 4100, 4500)

def if_replacer(canvas, value, *args):
    # idunno

我的问题是,我将如何以编程方式生成 if elif else's?我知道我可以像这样对其进行硬编码:

def if_replacer(canvas, value, c1, c2, c3, c4):
    if value < c1:
        ac.setBackgroundColor(canvas, 0, 0, 0)
        return
    elif value < c2:
        ac.setBackgroundColor(canvas, .2, .4, .2)
    elif value < c3:
        ac.setBackgroundColor(canvas, 0, 1, 0)
    elif value < c4:
        ac.setBackgroundColor(canvas, 0, 0, 1)
    else:
        ac.setBackgroundColor(canvas, 1, 0, 0)
    ac.setBackgroundOpacity(canvas, 1)

但我很感兴趣是否有一种简洁的 Pythonic 方法来完成此任务。

编辑:很多优秀的答案,但可惜我只能将其中一个标记为已接受(尽管所有有效的解决方案都被赞成)。我接受了我在代码中实现的答案,但对于任何偶然发现这个问题的人来说,请看看其他解决方案,它们都很出色。并且,感谢所有写答案的人。

【问题讨论】:

  • 你是不是故意省略了第一种情况下的ac.setBackgroundOpacity(wheel['slip'], 1)
  • @TrevorMerrifield 他们在他们的“硬编码”示例中似乎是特殊的外壳......所以我猜这不仅仅是一条缺失的线......但我们永远不知道......跨度>
  • @TrevorMerrifield 是的,这是故意的
  • 我会使用部分函数来减少重复,但否则我认为您现有的结构是最清晰的(即目前最可维护的)。

标签: python if-statement dry


【解决方案1】:

这是一种可能的解决方案。

def least_bound_index(value, bounds):
    """return the least index such that value < bounds[i], or else len(bounds)""" 
    for i, b in enumerate(bounds):
        if value < b:
            return i
    return i+1

bounds = [3000, 3700, 4100, 4500]
bgcolors = [(0, 0, 0), (.2, .4, .2), (0, 1, 0), (0, 0, 1), (1, 0, 0)]

i = least_bound_index(slips[i], bounds)
ac.setBackgroundColor(wheel['slip'], *bgcolors[i])
if i > 0:
    ac.setBackgroundOpacity(wheel['slip'], 1)

请注意,least_bound_index 也可以称为 index_between,因为您可以想象 [a, b, c, d] 在数轴上,它会告诉您这些选择中的值落在哪里:

  a   b   c   d
^   ^   ^   ^   ^
0   1   2   3   4

【讨论】:

  • 我花了相当长的时间来关注这个,我想问return i+1 做了什么,但我终于想通了以避免尴尬:如果value 不在任何范围内,那么它一定是最后一个颜色,所以这一行加了一个来选中它。这避免了询问value 是否在最后一个界限和无穷大之间的“无限黑客”。在我看来,这是一个非常聪明的解决方案。这是我最终使用的那个,谢谢!
  • 很高兴它对你有用!我还只是在答案中添加了另一种思考方式。
【解决方案2】:

我对此的看法(未经测试的实际 ac... 调用):

from functools import partial

mapping = [
    (3000, (0, 0, 0), None),
    (3700, (.2, .4, .2), 1),
    (4100, (0, 1, 0), 1),
    (4500, (0, 0, 1), 1),
    (float('inf'), (1, 0, 0), 1)
]

def if_replacer(canvas, value, mapping):
    set_color = partial(ac.setBackgroundColor, canvas)
    set_opacity = partial(ac.setBackgroundOpacity, canvas)
    for limit, vals, opacity in lookup:
        if value < limit:
            set_color(*vals)
            if opacity is not None:
                set_opacity(opacity)
            break

然后,如果由于某种原因您必须选择新的范围,那么您可以执行以下操作:

from bisect import insort_left
insort_left(mapping, (4300, (1, 1, 1), 0))

这会将mapping 更新为:

[(3000, (0, 0, 0), None),
 (3700, (0.2, 0.4, 0.2), 1),
 (4100, (0, 1, 0), 1),
 (4300, (1, 1, 1), 0),
 (4500, (0, 0, 1), 1),
 (inf, (1, 0, 0), 1)]

【讨论】:

  • 这是一个简洁而专业的解决方案,但不幸的是我不能使用functools,因为它在 Python 的精简版本中运行 - 具体来说,它作为应用程序运行在神力科莎。我什至不能在这里使用dict.items()
  • @DavidTan yowsers - 我当然不羡慕你 :)
【解决方案3】:

如果 'setBackgroundOpacity' 被错误地省略,或者它是否包含在第一种情况中也没关系,这是您可能正在寻找的解决方案:

color_map = [ (3000, 0, 0, 0), 
              (3700, .2, .4, .2), 
              (4100, 0, 1, 0), 
              (4500, 0, 0, 1), 
              (10**10, 1, 0, 0) ]

for i, (c, r, g, b) in enumerate(color_map):
    if value < c:
        ac.setBackgroundColor(wheel['slip'], r, g, b)
        if i > 0:
            ac.setBackgroundOpacity(wheel['slip'], 1)
        break

编辑:看到关于 setBackgroundOpacity 函数的评论

Edit2:修正了错字并为 10**10 添加了替代解决方案

color_map = [ (3000, 0, 0, 0), 
              (3700, .2, .4, .2), 
              (4100, 0, 1, 0), 
              (4500, 0, 0, 1), 
              (float("inf"), 1, 0, 0) ]

for i, (c, r, g, b) in enumerate(color_map):
    if value < c:
        ac.setBackgroundColor(wheel['slip'], r, g, b)
        if i > 0:
            ac.setBackgroundOpacity(wheel['slip'], 1)
        break

【讨论】:

  • 这可能会产生意想不到的副作用。找到匹配项后,您应该跳出循环。
  • 是的,我一发布就注意到了这一点。已修复,谢谢
  • 一个小错字:setBackgroundCOlor 应该是 setBackgroundColor。这个解决方案直观且易于理解,我会说在控制流方面非常pythonic,但10**10 让我有点不舒服。
  • 确实,这不是最大数量的最佳选择。已修复,以及错字。
【解决方案4】:
c = [[0,0,0],[.2,.4,.2],[0,1,0],[0,0,1]]
threshold = [3000,3700,4100,4500]
if slips[i] >= 4500:
    ac.setBackgroundColor(wheel['slip'],1,0,0)
else:
    for x in range(4):
        if slips[i] < threshold[x]:
            ac.setBackgroundColor(wheel['slip'],c[x][0],c[x][1],c[x][2])
            break
if slips[i] >= 3000:
    ac.setBackgroundOpacity(wheel['slip'], 1)

这是一种选择,但我个人更喜欢@mpurg 的回答。

【讨论】:

    【解决方案5】:
    def problem1_5(age):
        """Prints according to ages"""
        if age(1<7):
            print("Have a glass of milk")
        elif age(7<21):
            print("Have a cake")
        elif age>21:
            print("Have a martini")
        else:
            print(end='')
    

    【讨论】: