编程风格和避免空值
Programming style & avoiding null values
所以我正在努力阅读 Wentworth 等人 How to Think Like a Computer Scientist 的 Python 3 指南,尝试自学更多编程知识。虽然这是一个很棒的资源,但它在 Python 3.
中的写作风格和 "best practice" 几乎没有什么可说的
我正在完成有关条件的章节中的一个练习题,要求我编写一个函数,当 int 或 float 'mark' 是 returns 一个字符串 'grade'输入。
我在这里的直接问题是关于函数中条件句的重复和函数 returns 的值。是否可以使用循环而不是以某种方式使其更简洁,而不是一遍又一遍地编写 elif
语句?此外,主 grade
函数返回空 None
值;我怎样才能使这个函数 "fruitful" 而不是在它被调用时打印 None
?
这是我写的:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
print("Your grade is",grds[0])
elif mark < 75.0 and mark >= 70.0:
print("Your grade is",grds[1])
elif mark < 70.0 and mark >= 60.0:
print("Your grade is",grds[2])
elif mark < 60.0 and mark >= 50.0:
print("Your grade is",grds[3])
elif mark < 50.0 and mark >= 45.0:
print("Your grade is",grds[4])
elif mark < 45.0 and mark >= 40.0:
print("Your grade is",grds[5])
elif mark < 40.0:
print("Your grade is",grds[6])
def finalmark():
mark = float(input("Enter your mark"))
fnlmark = grade(mark)
return fnlmark
print(finalmark())
两件非常简单的事情:
你从来没有return任何东西。默认情况下,Python 将变为 return None
。您可以通过添加 return 语句来解决此问题,除了打印语句之外或代替打印语句。
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
print("Your grade is",grds[0])
return grds[0]
elif 75.0 > mark >= 70.0:
print("Your grade is",grds[1])
return grds[1]
你可以简化你的表达。 Python 接受类似于数学范围的表达式范围(例如 0 <= x <= 100 是有效的 Python)。您可以在上面看到一个示例;我将清理它并使其更具可读性作为 reader.
的练习
首先:为什么你的函数 return None ?
因为您实际使用打印,它会将文本回显给用户。您想要使用的是 return,它将离开函数并基本上说 "that's what I have calculated".
由于没有值 returned,python 自动 returns None。如果你使用更严格的语言,你可能会出错。
因此,您应该这样做:
return "Your grade is " + grds[0]
第二:如何改进你的代码?
首先要看的是,如果第一个条件成立(mark >= 75.0
)那么,在所有的elif中,mark都不能大于(或等于)75,也就是说你可以,在这种情况下,去掉每个 elif 中的所有低于条件。
第三:如何改进你的代码(2)?
现在,正如我告诉过你的,return 离开了这个功能。所以你可以使用它来删除 elifs:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return ("Your grade is " + grds[0])
if mark >= 70.0:
return ("Your grade is " + grds[1])
if mark >= 60.0:
return ("Your grade is " + grds[2])
if mark >= 50.0:
return ("Your grade is " + grds[3])
if mark >= 45.0:
return ("Your grade is " + grds[4])
if mark >= 40.0:
return ("Your grade is " + grds[5])
return ("Your grade is " + grds[6])
现在的问题是相同的代码重复了很多次。这意味着您可以将其分解为一个循环。我建议使用这样的边界数组:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
bounds = [75.0, 70.0, 60.0, 50.0, 45.0, 40.0]
for i in range(len(bounds)):
if mark >= bounds[i]:
return ("Your grade is " + grds[i])
return ("Your grade is " + grds[-1])
希望我已经解释清楚了,如果您有任何问题,请在下面提问。
而不是在 grade()
函数中使用 print()
,return 您的结果并让调用者打印结果标记。 grade()
函数应该只用于return一个等级:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return grds[0]
# .. etc
def finalmark():
mark = float(input("Enter your mark"))
fnlmark = grade(mark)
print("Your grade is", fnlmark)
finalmark()
注意finalmark()
现在负责打印;这是它的最佳位置,因为同一功能还负责在屏幕上打印问题并接受用户输入。与您的版本一样,finalmark()
returns None
(因为这是默认设置),我从 finalmark()
调用周围删除了 print()
以避免打印 return值。打印它没有意义,finalmark()
永远不会 return 除了 None
.
您也可以删除一半的测试;只有 第一个匹配的 if
或 elif
分支被选择,其余的被跳过。因此,您可以删除对先前分支已经涵盖的内容的测试:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return grds[0]
elif mark >= 70.0:
return grds[1]
elif mark >= 60.0:
return grds[2]
elif mark >= 50.0:
return grds[3]
elif mark >= 45.0:
return grds[4]
elif mark >= 40.0:
return grds[5]
else:
return grds[6]
如果第一个 if mark >= 75.0:
测试不匹配,则不再需要测试 mark < 75.0
,因为 我们已经测试了反向 .考 mark >= 70.0
就够了。如果匹配不上,我们就知道这个标记肯定小于70,所以接下来的测试只需要测试它是否大于60.0
,等等
现在出现了一种模式,您可以在其上构建循环。您测试 下限 ,如果匹配,您知道 return 的索引。构建一个单独的列表来存储下限:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
bounds = [75.0, 70.0, 60.0, 50.0, 45.0, 40.0]
for grade, bound in zip(grds, bounds):
if mark >= bound:
return grade
# there is no lower bound for F3; if the loop didn't find a match,
# we end up here and can assume the lowest grade.
return grds[6]
我使用 zip()
function here to pair up the grade names and the bounds, pairwise. You could also have used the enumerate()
function 生成一个索引以及每个年级名称,或者一个 for index in range(len(grds)):
循环,但我发现 zip()
在这里工作更干净。
接下来,我们可以开始巧妙地运用算法了。以上还是考每个年级,从高到低,一个一个考。对于 N 个等级,这最多可能需要 N 个步骤。这是一个线性算法,它需要的步骤与输入的数量一样多。
但是成绩是排序的,所以我们可以在这里使用二分法;跳到中间,看看标记是低于还是高于当前边界。然后选择任何一半,再次测试,直到找到最佳匹配。二分法最多需要 Log(N) 步。 Python 有一个 very fast implementation included;它采用 递增 顺序的值,因此反转等级和边界:
import bisect
def grade(mark):
grds = ['F3', 'F2', 'F1 Supp.', 'Third', 'Second', 'Upper Second', 'First']
bounds = [40.0, 45.0, 50.0, 60.0, 70.0, 75.0]
return grds[bisect.bisect_right(bounds, mark)]
bisect.bisect_right()
平分 bounds
找到 mark
的 'insertion point',它将在 右边 列表中的相同值。因此 35.0
将插入到 0
,50.0
插入到 3
(因为它等于 或更高 ),74.0
在 5
和任何在 75.0
或更高的 6
。这些恰好是匹配成绩的确切索引。
下面是两个 pythonic 解决方案。作为一个学习问题,有一些有趣的部分需要理解:带有元组键的字典、迭代字典项、生成器表达式、class 继承。
这不是构建代码的唯一方式。另一种方法是设置一系列边界分数,如 。但是,这些是 可读 且性能合理的解决方案。
在这两种情况下,请注意 return
语句的重要性,您的代码中缺少该语句。默认情况下,Python returns None
.
功能正常
def grade(mark):
grds = {(75, 100.1): 'First',
(70, 75): 'Upper Second',
(60, 70): 'Second',
(50, 60): 'Third',
(45, 50): 'F1 Supp.',
(40, 45): 'F2',
(0, 40): 'F3'}
return next(v for k, v in grds.items() if k[0] <= mark < k[1])
面向对象
python 的美妙之处在于它在某种程度上结合了面向对象和函数式编程。考虑以下解决方案。效率上会和上面类似,但是引入了一个构造,subclass dict_range
继承自dict
,可以方便的在其他场景中复用
class dict_range(dict):
def __getitem__(self, value):
return next(self.get(k) for k in self.keys() if k[0] <= value < k[1])
def grade(mark):
grds = dict_range({(75, 100.1): 'First',
(70, 75): 'Upper Second',
(60, 70): 'Second',
(50, 60): 'Third',
(45, 50): 'F1 Supp.',
(40, 45): 'F2',
(0, 40): 'F3'})
return grds[mark]
Makoto 的处理方式略有不同。同样,只是为了一个片段。
if mark >= 75:
# limit the religion here by just saving an index
idx = 0
# you don't need to check its below 75, as that was confirmed by above failing
elif mark >= 70:
idx = 0
# now consolidate the repetitions here.
return grds[idx]
所以我正在努力阅读 Wentworth 等人 How to Think Like a Computer Scientist 的 Python 3 指南,尝试自学更多编程知识。虽然这是一个很棒的资源,但它在 Python 3.
中的写作风格和 "best practice" 几乎没有什么可说的我正在完成有关条件的章节中的一个练习题,要求我编写一个函数,当 int 或 float 'mark' 是 returns 一个字符串 'grade'输入。
我在这里的直接问题是关于函数中条件句的重复和函数 returns 的值。是否可以使用循环而不是以某种方式使其更简洁,而不是一遍又一遍地编写 elif
语句?此外,主 grade
函数返回空 None
值;我怎样才能使这个函数 "fruitful" 而不是在它被调用时打印 None
?
这是我写的:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
print("Your grade is",grds[0])
elif mark < 75.0 and mark >= 70.0:
print("Your grade is",grds[1])
elif mark < 70.0 and mark >= 60.0:
print("Your grade is",grds[2])
elif mark < 60.0 and mark >= 50.0:
print("Your grade is",grds[3])
elif mark < 50.0 and mark >= 45.0:
print("Your grade is",grds[4])
elif mark < 45.0 and mark >= 40.0:
print("Your grade is",grds[5])
elif mark < 40.0:
print("Your grade is",grds[6])
def finalmark():
mark = float(input("Enter your mark"))
fnlmark = grade(mark)
return fnlmark
print(finalmark())
两件非常简单的事情:
你从来没有return任何东西。默认情况下,Python 将变为 return
None
。您可以通过添加 return 语句来解决此问题,除了打印语句之外或代替打印语句。def grade(mark): grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3'] if mark >= 75.0: print("Your grade is",grds[0]) return grds[0] elif 75.0 > mark >= 70.0: print("Your grade is",grds[1]) return grds[1]
你可以简化你的表达。 Python 接受类似于数学范围的表达式范围(例如 0 <= x <= 100 是有效的 Python)。您可以在上面看到一个示例;我将清理它并使其更具可读性作为 reader.
的练习
首先:为什么你的函数 return None ?
因为您实际使用打印,它会将文本回显给用户。您想要使用的是 return,它将离开函数并基本上说 "that's what I have calculated".
由于没有值 returned,python 自动 returns None。如果你使用更严格的语言,你可能会出错。
因此,您应该这样做:
return "Your grade is " + grds[0]
第二:如何改进你的代码?
首先要看的是,如果第一个条件成立(mark >= 75.0
)那么,在所有的elif中,mark都不能大于(或等于)75,也就是说你可以,在这种情况下,去掉每个 elif 中的所有低于条件。
第三:如何改进你的代码(2)?
现在,正如我告诉过你的,return 离开了这个功能。所以你可以使用它来删除 elifs:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return ("Your grade is " + grds[0])
if mark >= 70.0:
return ("Your grade is " + grds[1])
if mark >= 60.0:
return ("Your grade is " + grds[2])
if mark >= 50.0:
return ("Your grade is " + grds[3])
if mark >= 45.0:
return ("Your grade is " + grds[4])
if mark >= 40.0:
return ("Your grade is " + grds[5])
return ("Your grade is " + grds[6])
现在的问题是相同的代码重复了很多次。这意味着您可以将其分解为一个循环。我建议使用这样的边界数组:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
bounds = [75.0, 70.0, 60.0, 50.0, 45.0, 40.0]
for i in range(len(bounds)):
if mark >= bounds[i]:
return ("Your grade is " + grds[i])
return ("Your grade is " + grds[-1])
希望我已经解释清楚了,如果您有任何问题,请在下面提问。
而不是在 grade()
函数中使用 print()
,return 您的结果并让调用者打印结果标记。 grade()
函数应该只用于return一个等级:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return grds[0]
# .. etc
def finalmark():
mark = float(input("Enter your mark"))
fnlmark = grade(mark)
print("Your grade is", fnlmark)
finalmark()
注意finalmark()
现在负责打印;这是它的最佳位置,因为同一功能还负责在屏幕上打印问题并接受用户输入。与您的版本一样,finalmark()
returns None
(因为这是默认设置),我从 finalmark()
调用周围删除了 print()
以避免打印 return值。打印它没有意义,finalmark()
永远不会 return 除了 None
.
您也可以删除一半的测试;只有 第一个匹配的 if
或 elif
分支被选择,其余的被跳过。因此,您可以删除对先前分支已经涵盖的内容的测试:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
if mark >= 75.0:
return grds[0]
elif mark >= 70.0:
return grds[1]
elif mark >= 60.0:
return grds[2]
elif mark >= 50.0:
return grds[3]
elif mark >= 45.0:
return grds[4]
elif mark >= 40.0:
return grds[5]
else:
return grds[6]
如果第一个 if mark >= 75.0:
测试不匹配,则不再需要测试 mark < 75.0
,因为 我们已经测试了反向 .考 mark >= 70.0
就够了。如果匹配不上,我们就知道这个标记肯定小于70,所以接下来的测试只需要测试它是否大于60.0
,等等
现在出现了一种模式,您可以在其上构建循环。您测试 下限 ,如果匹配,您知道 return 的索引。构建一个单独的列表来存储下限:
def grade(mark):
grds = ['First','Upper Second','Second','Third','F1 Supp.','F2','F3']
bounds = [75.0, 70.0, 60.0, 50.0, 45.0, 40.0]
for grade, bound in zip(grds, bounds):
if mark >= bound:
return grade
# there is no lower bound for F3; if the loop didn't find a match,
# we end up here and can assume the lowest grade.
return grds[6]
我使用 zip()
function here to pair up the grade names and the bounds, pairwise. You could also have used the enumerate()
function 生成一个索引以及每个年级名称,或者一个 for index in range(len(grds)):
循环,但我发现 zip()
在这里工作更干净。
接下来,我们可以开始巧妙地运用算法了。以上还是考每个年级,从高到低,一个一个考。对于 N 个等级,这最多可能需要 N 个步骤。这是一个线性算法,它需要的步骤与输入的数量一样多。
但是成绩是排序的,所以我们可以在这里使用二分法;跳到中间,看看标记是低于还是高于当前边界。然后选择任何一半,再次测试,直到找到最佳匹配。二分法最多需要 Log(N) 步。 Python 有一个 very fast implementation included;它采用 递增 顺序的值,因此反转等级和边界:
import bisect
def grade(mark):
grds = ['F3', 'F2', 'F1 Supp.', 'Third', 'Second', 'Upper Second', 'First']
bounds = [40.0, 45.0, 50.0, 60.0, 70.0, 75.0]
return grds[bisect.bisect_right(bounds, mark)]
bisect.bisect_right()
平分 bounds
找到 mark
的 'insertion point',它将在 右边 列表中的相同值。因此 35.0
将插入到 0
,50.0
插入到 3
(因为它等于 或更高 ),74.0
在 5
和任何在 75.0
或更高的 6
。这些恰好是匹配成绩的确切索引。
下面是两个 pythonic 解决方案。作为一个学习问题,有一些有趣的部分需要理解:带有元组键的字典、迭代字典项、生成器表达式、class 继承。
这不是构建代码的唯一方式。另一种方法是设置一系列边界分数,如
在这两种情况下,请注意 return
语句的重要性,您的代码中缺少该语句。默认情况下,Python returns None
.
功能正常
def grade(mark):
grds = {(75, 100.1): 'First',
(70, 75): 'Upper Second',
(60, 70): 'Second',
(50, 60): 'Third',
(45, 50): 'F1 Supp.',
(40, 45): 'F2',
(0, 40): 'F3'}
return next(v for k, v in grds.items() if k[0] <= mark < k[1])
面向对象
python 的美妙之处在于它在某种程度上结合了面向对象和函数式编程。考虑以下解决方案。效率上会和上面类似,但是引入了一个构造,subclass dict_range
继承自dict
,可以方便的在其他场景中复用
class dict_range(dict):
def __getitem__(self, value):
return next(self.get(k) for k in self.keys() if k[0] <= value < k[1])
def grade(mark):
grds = dict_range({(75, 100.1): 'First',
(70, 75): 'Upper Second',
(60, 70): 'Second',
(50, 60): 'Third',
(45, 50): 'F1 Supp.',
(40, 45): 'F2',
(0, 40): 'F3'})
return grds[mark]
Makoto 的处理方式略有不同。同样,只是为了一个片段。
if mark >= 75:
# limit the religion here by just saving an index
idx = 0
# you don't need to check its below 75, as that was confirmed by above failing
elif mark >= 70:
idx = 0
# now consolidate the repetitions here.
return grds[idx]