Pythonic相当于这个功能?

时间:2012-05-30 16:08:32

标签: python

我有一个从另一种语言移植的功能,请你帮我把它变成“pythonic”?

这里的函数以“非pythonic”的方式移植(这是一个人为的例子 - 每个任务都与一个项目或“无”相关联,我们需要一个不同项目的列表,不同意义没有重复.identifier属性,从任务列表开始):

@staticmethod
def get_projects_of_tasks(task_list):

    projects = []
    project_identifiers_seen = {}

    for task in task_list:

        project = task.project

        if project is None:
            continue

        project_identifier = project.identifier

        if project_identifiers_seen.has_key(project_identifier):
            continue

        project_identifiers_seen[project_identifier] = True
        projects.append(project)

    return projects

我特别没有开始让它“pythonic”不要从错误的脚开始(例如列表理解“如果project.identifier不是None,filter()基于查找基于字典的谓词标识符注册表,使用set()去除重复项等。)

编辑:

根据反馈,我有这个:

@staticmethod
def get_projects_of_tasks(task_list):

    projects = []
    project_identifiers_seen = set()

    for task in task_list:

        project = task.project

        if project is None:
            continue

        project_identifier = project.identifier

        if project_identifier in project_identifiers_seen:
            continue

        project_identifiers_seen.add(project_identifier)
        projects.append(project)

    return projects

5 个答案:

答案 0 :(得分:7)

关于这段代码没有任何大规模的非语言。一些可能的改进:

  • project_identifiers_seen可以是一个集合,而不是字典。
  • foo.has_key(bar)拼写更好bar in foo
  • 我怀疑这是一个班级staticmethod。除非你实际上在进行数据封装,否则通常不需要Python中的类。如果这只是一个普通函数,请将其设置为模块级函数。

答案 1 :(得分:3)

def get_projects_of_tasks(task_list):
    seen = set()
    return [seen.add(task.project.identifier) or task.project #add is always None
            for task in task_list if 
            task.project is not None and task.project.identifier not in seen]

这是因为(a)add返回None(并且or返回最后一个表达式的值)和(b)映射子句(第一个子句)只是如果if子句是True,则执行。

没有理由它必须在列表理解中 - 你也可以将其设置为循环,事实上你可能更喜欢。这种方式的优点是很明显,您只是在构建一个列表,以及它应该包含在哪个列表中。

我没有使用staticmethod,因为很少需要它。将此作为模块级函数或classmethod


另一种选择是生成器(感谢@delnan指出这一点):

def get_projects_of_tasks(task_list):
    seen = set()
    for task in task_list:
        if task.project is not None and task.project.identifier not in seen:
           identifier = task.project.identifier
           seen.add(identifier)
           yield task.project

这消除了理解中的副作用(这是有争议的)的需要,但是要清楚地知道正在收集什么。

为了避免另一个if / continue构造,我留下了两次访问task.project.identifier。使用promise库可以方便地消除这种情况。


此版本使用promises来避免重复访问task.project.identifier,而无需包含if / continue:

from peak.util.proxies import LazyProxy, get_cache # pip install ProxyTypes

def get_projects_of_tasks(task_list):
    seen = set()
    for task in task_list:
        identifier = LazyProxy(lambda:task.project.identifier) # a transparent promise
        if task.project is not None and identifier not in seen:
           seen.add(identifier)
           yield task.project

这对于AttributeErrors是安全的,因为在task.project.identifier被选中之前永远不会访问task.project

答案 2 :(得分:3)

怎么样:

project_list = {task.project.identifier:task.project for task in task_list if task.project is not None}
return project_list.values()

对于2.6-使用dict构造函数:

return dict((x.project.id, x.project) for x in task_list if x.project).values()

答案 3 :(得分:1)

有人说EAFP是pythonic,所以:

@staticmethod
def get_projects_of_tasks(task_list):

    projects = {}

    for task in task_list:
        try:
            if not task.project.identifier in projects:
                projects[task.project.identifier] = task.project
        except AttributeError:
            pass

    return projects.values()
如果很多任务没有投射,那么明确的检查也不会出错,当然会更好。

如果项目的顺序很重要,那么只有一个dict可以跟踪看到的标识符和项目就足够了,那么OrderedDict(python2.7 +)可以派上用场。

答案 4 :(得分:1)

已经有很多好的答案,而且,你确实已经接受了一个!但我想我会再增加一个选项。许多人已经看到使用生成器表达式或列表推导可以使您的代码更紧凑。我将建议使用生成器表达式进行初始过滤的混合样式,同时在最终过滤器中保持for循环。

这种风格优于原始代码的风格,它通过消除continue语句简化了控制流程。这种风格优于单个列表理解的优点是它避免了以自然方式多次访问task.project.identifier。它还透明地处理可变状态(seen集),我认为这很重要。

def get_projects_of_tasks(task_list):
    projects = (task.project for task in task_list)
    ids_projects = ((p.identifier, p) for p in projects if p is not None)

    seen = set()
    unique_projects = []
    for id, p in ids_projects:
        if id not in seen:
            seen.add(id)
            unique_projects.append(p)
    return unique_projects

因为这些是生成器表达式(括在括号中而不是括号中),所以它们不构建临时列表。第一个生成器表达式创建一个可迭代的项目;您可以将其视为在所有项目中同时执行原始代码中的project = task.project行。第二个生成器表达式创建一个(project_id, project)元组的可迭代。最后的if子句会过滤掉None值; (p.identifier, p)仅在p通过过滤器时进行评估。这两个生成器表达式一起消除了前两个if块。其余代码与您的代码基本相同。

另请注意Marcin/delnan使用yield创建生成器的优秀建议。这进一步减少了代码的冗长程度,将其归结为其基本要素:

def get_projects_of_tasks(task_list):
    projects = (task.project for task in task_list)
    ids_projects = ((p.identifier, p) for p in projects if p is not None)

    seen = set()
    for id, p in ids_projects:
        if id not in seen:
            seen.add(id)
            yield p

唯一的缺点 - 如果这不明显 - 是如果要永久存储项目,则必须将生成的可迭代项传递给list

projects_of_tasks = list(get_projects_of_tasks(task_list))