【问题标题】:Pythonic Comparison FunctionsPythonic 比较函数
【发布时间】:2009-08-06 15:39:06
【问题描述】:

为了简单起见,假设我在 Python 中有一个 Person 类。此类具有名字、姓氏和出生日期的字段。

class Person:
  def __init__(self, firstname, lastname, dob):
    self.firstname = firstname;
    self.lastname = lastname;
    self.dob = dob;

在某些情况下,我想按姓氏、名字和出生日期对人员列表进行排序。在其他情况下,我想先按 dob 排序,然后按姓氏排序,最后按名字排序。有时我只想按名字排序。

创建第一个比较函数的简单解决方案是这样的:

def comparepeople(person1, person2):
  if cmp(person1.lastname, person2.lastname) == 0:
    if cmp(person1.firstname, person2.firstname) == 0:
      return cmp(person1.dob, person2.dob);
    return cmp(person1.firstname, person2.firstname);
  return cmp(person1.lastname, person2.lastname);

似乎应该有一种简单的方法来使用元编程方法来定义这样的比较函数,我需要做的就是按优先顺序提供字段名称,而不是编写这些非常冗长、丑陋的比较方法。但是我最近才开始玩 Python,还没有找到像我描述的那样的东西。

所以问题是,为具有多个可比较组成成员的类编写比较函数的最 Pythonic 方式是什么?

【问题讨论】:

  • 顺便说一句——删除那些分号!它们没有任何用处,也不是好的 Python 风格。

标签: comparison metaprogramming python


【解决方案1】:

如果你真的想要一个比较功能,你可以使用

def comparepeople(p1, p2):
    o1 = p1.lastname, p1.firstname, p1.dob
    o2 = p2.lastname, p2.firstname, p2.dob
    return cmp(o1,o2)

这依赖于元组比较。如果要对列表进行排序,则不应编写比较函数,而应编写关键函数:

l.sort(key=lambda p:(p.lastname, p.firstname, p.dob))

这样做的优点是 a) 更短 b) 更快,因为每个键只计算一次(而不是在排序期间在比较函数中创建大量元组)。

【讨论】:

  • 正如罗伯托在评论内德的回答时指出的那样,l.sort(operator.attrgetter('lastname', 'firstname', 'dob')) 甚至会(稍微)更快(虽然不会更短;-)。
【解决方案2】:

这是一种方法(可能不是最快的):

def compare_people_flexibly(p1, p2, attrs):
    """Compare `p1` and `p2` based on the attributes in `attrs`."""
    v1 = [getattr(p1, a) for a in attrs]
    v2 = [getattr(p2, a) for a in attrs]
    return cmp(v1, v2)

def compare_people_firstname(p1, p2):
    return compare_people_flexibly(p1, p2, ['firstname', 'lastname', 'dob'])

def compare_people_lastname(p1, p2):
    return compare_people_flexibly(p1, p2, ['lastname', 'firstname', 'dob'])

之所以可行,是因为 getattr 可用于获取由字符串命名的属性,并且 Python 根据对第一个不相等项的比较来按您所期望的那样比较列表。

另一种方式:

def compare_people_flexibly(p1, p2, attrs):
    """Compare `p1` and `p2` based on the attributes in `attrs`."""
    for a in attrs:
        c = cmp(getattr(p1, a), getattr(p2, a))
        if c:
            return c
    return 0

这样做的好处是它不会构建两个完整的属性列表,因此如果属性列表很长,或者如果在第一个属性上完成了许多比较,可能会更快。

最后,正如 Martin 提到的,您可能需要一个关键函数而不是比较函数:

def flexible_person_key(attrs):
    def key(p):
        return [getattr(p, a) for a in attrs]
    return key

l.sort(key=flexible_person_key('firstname', 'lastname', 'dob'))

【讨论】:

  • flexible_person_key = operator.attrgetter ;)
  • 是的,因为可以使用多个参数调用 Python 2.5 attrgetter 以获得具有多个属性的元组。
  • 我从来不知道!谢谢你的课。
【解决方案3】:

能不能不用类上的比较方法,见__cmp__等丰富的比较方法...

【讨论】:

  • 重点是,我需要比较函数在不同情况下有所不同,即使我确实在 Person 上实现了 cmp,我仍然必须使用 Ned 之类的东西描述实现。
猜你喜欢
  • 2012-04-28
  • 2010-10-17
  • 2014-10-10
  • 1970-01-01
  • 2013-08-31
  • 2018-07-09
  • 2012-10-05
  • 2012-02-14
相关资源
最近更新 更多