Pythonic Сравнительные Функции - PullRequest
1 голос
/ 06 августа 2009

Ради простоты, скажем, у меня есть класс Person в Python. В этом классе есть поля для имени, фамилии и имени.

class Person:
  def __init__(self, firstname, lastname, dob):
    self.firstname = firstname;
    self.lastname = lastname;
    self.dob = 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 и не нашел ничего похожего на то, что я описываю.

Таким образом, вопрос в том, какой самый пифоновский способ написать функцию сравнения для класса с несколькими сопоставимыми составляющими элементами?

Ответы [ 3 ]

10 голосов
/ 06 августа 2009

Если вы действительно хотите функцию сравнения, вы можете использовать

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))

Преимущество этого в том, что он а) короче и б) быстрее, потому что каждый ключ вычисляется только один раз (а не тонны кортежей, создаваемых в функции сравнения во время сортировки).

4 голосов
/ 06 августа 2009

Вот один из способов (возможно, не самый быстрый):

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

Это имеет то преимущество, что не создает два полных списка атрибутов, поэтому может быть быстрее, если списки атрибутов длинные или если много сравнений завершено для первого атрибута.

Наконец, как отмечает Мартин, вам может понадобиться ключевая функция, а не функция сравнения:

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'))
0 голосов
/ 06 августа 2009

Не можете ли вы использовать методы сравнения в классе, см. __ cmp __ и другие богатые методы сравнения ...

...