4

我有一个从另一种语言移植的功能,你能帮我把它变成“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
4

5 回答 5

7

这段代码没有什么不符合 Python 的。一些可能的改进:

  • project_identifiers_seen可以是集合,而不是字典。
  • foo.has_key(bar)拼写更好bar in foo
  • 我怀疑这是staticmethod一类。通常不需要 Python 中的类,除非您实际上是在进行数据封装。如果这只是一个普通功能,请将其设为模块级功能。
于 2012-05-30T16:16:52.290 回答
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 库方便地消除。


此版本使用承诺来避免重复访问 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

于 2012-05-30T16:18:49.773 回答
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()
于 2012-05-30T16:21:24.990 回答
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+) 可能会派上用场。

于 2012-05-30T16:30:20.510 回答
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))
于 2012-05-30T21:31:37.387 回答