正如评论中提到的,这里有一个比遍历 args 更好的设计。将函数应用于单个元素,这是评估成绩所需的最小粒度单位。将循环烘焙到您可能想应用于多个元素的每个函数中,充其量是浪费打字,而在最坏的情况下确实会对调用者造成可读性和维护损害。
根据经验,当一个函数在一个单元上运行时,将循环移动到调用者(例如int
)。仅当需要对可迭代对象(例如sort
, min
)进行操作时才在函数内部循环sum
。
另外,避免长if
链else
。如果你有 30 个桶而不是 5 个呢?编写 30 if
-else
语句不可扩展。说它为时过早,但当我的if
-else
链长于 3 左右时,我往往会感到不安。
这是我的建议,它利用了大多数桶可以被 10 整除的事实,我们只需要处理 <60 是一个巨大的桶而 100 需要一个额外的桶的事实。现在我们完全消除分支。
>>> def grade(x): return "FFFFFFDCBAA"[x//10]
...
>>> grade(99)
'A'
>>> grade(100)
'A'
>>> grade(80)
'B'
>>> grade(89)
'B'
>>> grade(60)
'D'
>>> grade(59)
'F'
如果您想变得健壮,请在另一行中折腾并检查边界,IndexError
如果参数超出范围,并且ValueError
如果参数不是正确的类型,则可能会引发一个。
有了我们的新grade
函数,我们可以根据调用者的选择将其应用于单个成绩或可迭代的成绩:
>>> grades = [87, 92, 100, 54, 72, 84, 81, 74]
>>> [grade(x) for x in grades]
['B', 'A', 'A', 'F', 'C', 'B', 'B', 'C']
>>> list(map(grade, grades)) # alternately
['B', 'A', 'A', 'F', 'C', 'B', 'B', 'C']
在实际的应用程序中,grades
列表不仅仅是凭空出现的一堆文字或变量——它必须作为某种数据结构处于状态。如果你坚持你的函数设计,你要么有一个额外的循环,要么你被困在打包和解包数据结构中,或者两者兼而有之。
还有一个语义/可读性问题:grade(100, 20, 64)
目前还不清楚应该发生什么操作。是否grade
要返回所有分数的平均值并产生最终成绩?归约是像这样的可变参数函数的典型模式(例如sum
,min
等)。当调用者使用[grade(x) for x in grades]
时,更清楚的是我们使用的是映射操作,将函数grade
单独应用于每个元素,而不是将集合作为一个单元。按照这个逻辑,一个更好的函数名称可能是score_to_letter_grade
或类似的,它清楚地描述了正在执行的 1:1 操作。
此外,如果您只想对一件事进行评分,那么您就会被一个列表作为输出,无论这对于保持调用者的清洁来说几乎是一个即时的交易破坏者。如果内置函数int
像这样工作,那将是一个非常大的痛苦:x = int(x)[0]
只是从整数解析x
为字符串。
这里的情况并没有那么可怕,因为代码量很少,但是可以在这样的微观尺度上解释大的设计问题,而当应用于更大的代码库时,这种责任分配不当会产生设计债务。