【问题标题】:Are python list comprehensions always a good programming practice? [closed]python列表推导总是一个好的编程习惯吗? [关闭]
【发布时间】:2011-11-18 00:07:42
【问题描述】:

为了让问题更清楚,我将使用一个具体的例子。

我有一个大学课程列表,每门课程都有几个字段(都是字符串)。用户给了我一串搜索词,我返回一个与所有搜索词匹配的课程列表。这可以在单个列表理解或几个嵌套的 for 循环中完成。

这里是实现。一、Course类:

class Course:
    def __init__(self, date, title, instructor, ID, description, instructorDescription, *args):
        self.date = date
        self.title = title
        self.instructor = instructor
        self.ID = ID
        self.description = description
        self.instructorDescription = instructorDescription
        self.misc = args

每个字段都是一个字符串,除了misc,它是一个字符串列表。

这是作为单个列表理解的搜索。 courses 是课程列表,query 是搜索词串,例如“历史项目”。

def searchCourses(courses, query):
    terms = query.lower().strip().split()
    return tuple(course for course in courses if all(
            term in course.date.lower() or
            term in course.title.lower() or
            term in course.instructor.lower() or
            term in course.ID.lower() or
            term in course.description.lower() or
            term in course.instructorDescription.lower() or
            any(term in item.lower() for item in course.misc)
        for term in terms))

您会注意到复杂的列表理解难以阅读。

我实现了与嵌套 for 循环相同的逻辑,并创建了这个替代方案:

def searchCourses2(courses, query):
    terms = query.lower().strip().split()
    results = []
    for course in courses:
        for term in terms:
            if (term in course.date.lower() or
                term in course.title.lower() or
                term in course.instructor.lower() or
                term in course.ID.lower() or
                term in course.description.lower() or
                term in course.instructorDescription.lower()):
                break
            for item in course.misc:
                if term in item.lower():
                    break
            else:
                continue
            break
        else:
            continue
        results.append(course)
    return tuple(results)

这种逻辑也很难遵循。我已经验证了这两种方法都返回了正确的结果。

这两种方法的速度几乎相同,但在某些情况下除外。我用timeit做了一些测试,发现前者在用户搜索多个不常用词时快三倍,而后者在用户搜索多个常用词时快三倍。不过,这还不足以让我担心。

所以我的问题是:哪个更好?列表推导总是可行的方法,还是应该使用嵌套的 for 循环来处理复杂的语句?还是有更好的解决方案?

【问题讨论】:

  • 在您显示的代码中没有一个列表理解——可能您的意思是 generator expressions 代替。
  • 作为一般规则:如果它难以阅读,那就是个坏主意。
  • False not in <iterable> 在 Python 中被称为 all()
  • 只是旁观者的小评论:lower() before comparisons doesn't always work.
  • @julio 我知道这是讨厌的代码;它不可读。这就是我来这里寻求帮助的原因。欢迎提出建议。

标签: python coding-style list-comprehension


【解决方案1】:

恕我直言,当它们比替代方案更清晰(或至少,同样清晰)和简洁时,它们是很好的编程实践。在这种情况下,这两种选择都不是那么清楚。就个人而言,我会将搜索逻辑保留在 Course 类中。这对我来说更有意义,因为逻辑与类相关联。

class Course:
    def __init__(self, date, title, instructor, ID, description, instructorDescription, *args):
        self.date = date
        self.title = title
        self.instructor = instructor
        self.ID = ID
        self.description = description
        self.instructorDescription = instructorDescription
        self.misc = args

    def matches_term(self, term):
        if term in self.date.lower():
            return True
        # etc
        return False

然后您可以使用更简单的生成器(或列表)表达式进行搜索:

def searchCourses(courses, query):
    terms = query.lower().strip().split()
    return tuple(course for course in courses
                 if all(course.matches_term(term)
                        for term in terms)
                )

一个简单的测试:

courses = (
    Course("today", "", "", "", "", ""),
    Course("wednesday", "", "", "", "", ""),
    Course("today", "", "", "", "", ""),
    Course("sunday", "", "", "", "", ""),
)

results = searchCourses(courses, "on today or wednesday")
for course in results:
    print course.date

哪个输出:

today
wednesday
today

【讨论】:

  • 很好的解决方案,但是有一个错误。课程必须匹配所有条款,而不仅仅是一个。这就像谷歌搜索。
  • @dln385 我编辑了答案,要求课程匹配所有条款。您可以看到通过这样的设计更改操作规范细节是多么容易,这与您的嵌套循环与中断和继续版本不同。
  • @dln385 - 我的错误,不知何故忽略了这一点。
【解决方案2】:

列表推导式(或生成器表达式)与您尝试执行的操作更匹配,即从其他一些值集合中组合生成值的集合。

嵌套的 for 循环允许您以组合方式执行某些操作。您显然可以使用这些操作来构建一个集合并执行与列表理解相同的操作(就像您所做的那样),但是您必须添加样板来创建列表、附加到它并返回它。在我看来,这种复杂的中断结构和多个循环级别的继续结构比等效的列表理解更难直观理解。我仍然无法弄清楚它是如何工作的(尽管我怀疑它出现在 SO 上存在缩进错误)。根据我的经验,带有 break 和 continue 的嵌套循环也是难以发现的错误的丰富来源。

不过,在我看来,这两个版本的不可读性的真正来源是测试课程是否与查询词匹配的逻辑。该操作应该被分解为Course 上的方法(如布莱尔的回答所示),或者如果您不能或不想更改课程,您仍然可以定义一个单独的函数来检查课程是否匹配一个查询词。完成后,无论是 for 循环还是列表推导都变得几乎是微不足道的。

【讨论】:

    【解决方案3】:

    我会说列表推导并不总是可取的。有时,旧的 for 循环效果最好(我发现在进行语法处理时通常是这样)。

    还有第三种方法,就是使用内置的filter函数或者itertools.ifilter迭代器。像这样:

    result = ifilter(test1f, ifilter(test2f, ifilter(test3f, someiterator)))
    try:
      result.next()
      return True
    except StopIteration:
      return False
    

    在这种特殊情况下,您需要的测试功能的数量取决于提交的不同搜索词的数量,所以我不建议在这里使用ifilter,除非您想实现一种方法将多个测试合并为一个功能(这是我为自己完成的,但你所拥有的已经足够了,何必费心)。

    在其他更简单的情况下filter 做得很好,因为它是用 C 实现的,所以效率很高。使用基于迭代器的方法,您通常可以通过一种或两种方式修改数据流来解决看似复杂的问题(使用过滤器函数或其他东西),然后将生成的流传递给公共库函数。

    例如,这里有一个素数筛子,它大大减少了识别素数所需的因式分解量(第一次浪费的努力是在 49 处,在它连续吐出 12 个实际素数之后):

    sieve = itertools.ifilter(lambda x: x % 5 != 0,
      itertools.ifilter(lambda x: x % 3 != 0,
      itertools.ifilter(lambda x: x % 2 != 0, itertools.count(7))))
    

    【讨论】:

    • 顺便说一句,因为这不是你的问题:为了做这样的事情,你将把所有的对象都存储在内存中。将 SQLAlchemy 作为一种管理对象存储并使用索引搜索它们的方法。即使您没有正式的数据库服务器,您也可以将 SQLite 与 SQLAlchemy 一起使用,它将使用单个文件作为数据库。
    猜你喜欢
    • 2020-08-25
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 2020-09-16
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多