【问题标题】:Programming style & avoiding null values [duplicate]编程风格和避免空值
【发布时间】:2018-07-17 02:56:32
【问题描述】:

所以我正在阅读 Wentworth 等人 How to Think Like a Computer Scientist 的 Python 3 指南,以尝试自学更多关于编程的知识。虽然它是一个很棒的资源,但它对于使用 Python 3 编写的风格和“最佳实践”几乎没有什么可说的。

我正在处理关于条件的章节中的一个练习题,它要求我编写一个函数,当输入一个 int 或 float 'mark' 时返回一个字符串 'grade'。

我的直接问题是关于函数中条件句的重复以及函数返回的值。是否可以以某种方式使用循环来使其更简洁,而不是一遍又一遍地编写elif 语句?此外,主要的grade 函数返回一个空的None 值;我怎样才能使这个函数“富有成效”而不是在被调用时打印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())    

【问题讨论】:

  • ....你得到None是因为你的grade函数总是返回None

标签: python python-3.x function for-loop conditional


【解决方案1】:

而不是在grade() 函数中使用print()返回您的结果并让调用者打印结果标记。 grade() 函数只能用于返回成绩:

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() 返回 None(因为这是默认值),我从 finalmark() 调用周围删除了 print() 以避免打印该返回值。打印它没有意义,finalmark() 永远不会返回除None 之外的任何内容。

您还可以删除一半的测试;只有 第一个匹配 ifelif 分支被选中,其余的被跳过。因此,您可以删除先前分支已经涵盖的测试:

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 &gt;= 75.0: 测试不匹配,则无需再测试mark &lt; 75.0,因为我们已经测试了逆向。测试mark &gt;= 70.0 就足够下一个年级了。如果不匹配,我们就知道这个标记肯定小于70,所以接下来的测试只需要测试它是否大于60.0等。

现在出现了一种模式,您可以在此基础上构建循环。您测试一个下限,如果匹配,您就知道要返回哪个索引。建立一个单独的列表来存储下限:

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 来配对等级名称和界限,成对的。您也可以使用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 的“插入点”,该插入点位于列表中相同值的右侧。所以35.0 将插入050.03(因为它等于或更高),74.05 和任何75.0 或更高在6。而这些恰好是匹配等级的确切指标。

【讨论】:

  • 轻微修正 - bisect_right 应该在这里使用,因为如果一个分数正好等于一个等级的下限,那足以仍然获得两个候选等​​级中较高的奖励,例如grade(75.0) 应该返回 'First'
  • @plamut:啊,确实,我的逻辑基于之前的测试倒退了,没有正确应用反转。
  • 二分查找似乎过大了。我什至不相信它在这里更快,因为它更复杂而且列表很小。而且它甚至没有提供更好的复杂性类,因为您的函数每次调用时都会构建整个列表,因此您的函数无论如何都需要 O(N) 。也许更好地使用元组,以便它们得到预编译。不过,这绝对是不错的代码,所以我想说这才是真正的优势。
  • ... 或者像its example of exactly this 中的文档那样使用默认参数。
【解决方案2】:

两件非常简单的事情:

  • 从不返回任何东西。默认情况下,Python 将返回None。您可以通过在 print 语句之外或代替 print 语句添加 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

【讨论】:

    【解决方案3】:

    以下是两个 pythonic 解决方案。作为一个学习问题,有一些有趣的地方需要理解:带有元组键的字典、迭代字典项、生成器表达式、类继承。

    这不是构造代码的唯一方式。另一种方法是设置边界分数序列,如@MartijnPeter's answer。但是,这些是可读且性能合理的解决方案。

    在这两种情况下,请注意您的代码中缺少的 return 语句的重要性。默认情况下,Python 返回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 的美妙之处在于它在某种程度上结合了面向对象和函数式编程。考虑以下解决方案。效率会和上面差不多,但是它引入了一个构造,子类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]
    

    【讨论】:

      【解决方案4】:

      首先:为什么你的函数返回 None ?

      因为您实际使用的是 print,它会将文本回显给用户。你想要使用的是 return,它将离开函数并基本上说“这就是我计算的结果”。

      由于没有返回值,python 自动返回 None。如果您使用更严格的语言,您可能会遇到错误。

      因此,你应该这样做:

      return "Your grade is " + grds[0]
      

      第二:如何改进你的代码?

      首先要看到的是,如果第一个条件是有效的(mark &gt;= 75.0),那么在所有的 elif 中,mark 不能大于(或等于)75,这意味着你可以,在这种情况下, 去掉每个 elif 中的每个低于条件。

      第三:如何改进你的代码(二)?

      现在,正如我告诉你的,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])
      

      希望我已经解释清楚了,如果您有任何问题,请在下面提问。

      【讨论】:

        【解决方案5】:

        对 Makoto 的处理方式略有不同。同样,只是为了一个 sn-p。

        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]
        

        【讨论】:

          猜你喜欢
          • 2015-07-22
          • 1970-01-01
          • 2019-05-05
          • 2019-03-16
          • 1970-01-01
          • 1970-01-01
          • 2010-09-27
          • 1970-01-01
          • 2014-11-29
          相关资源
          最近更新 更多