Python:как работает функция functools cmp to key?

в Python, как list.sort способ и sorted встроенная функция принимает необязательный параметр key, которая является функцией, которая, учитывая элемент из списка, возвращает его ключ сортировки.

более старые версии Python использовали другой подход, используя cmp параметр вместо этого, который является функцией, которая, учитывая два элемента из списка возвращает отрицательное число, если первый меньше второго, ноль, если есть равные и положительное число, если первый более значительный. В какой-то момент этот параметр устарел и не был включен в Python 3.

на днях я хотел отсортировать список элементов таким образом, чтобы a cmp функция была намного проще написать, чем key один. Я не хотел использовать устаревшую функцию, поэтому я прочитал документацию и обнаружил, что есть функция с именем cmp_to_key на functools модуль, который, как указано в его имени, получает cmp функция и возвращает key один... или я так думал. пока я не прочитаю исходный код (или, по крайней мере, эквивалентную версию) этой функции высокого уровня, включенной в docs

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K(object):
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

несмотря на то, что cmp_to_key работает, как ожидалось, я удивлен тем, что этот funtion не возвращает функцию, но класса. Почему? Как это работает? Мое предположение, что sorted функция внутренне проверяет, является ли cmp функцией или классом K или чем-то подобным, но я не конечно.

П. С.: несмотря на его wierdness, я обнаружил, что класс K очень полезен. Проверьте этот код:

from functools import cmp_to_key

def my_cmp(a, b):
    # some sorting comparison which is hard to express using a key function

class MyClass(cmp_to_key(my_cmp)):
    ...

таким образом, любой список экземпляров MyClass может быть по умолчанию отсортирован по критериям, определенным в my_cmp

3 ответов


нет, . И сортировка при сортировке значений сравнивает эти объекты (в-effect), вызывая ваш mycmp() функция, чтобы определить, является ли значение меньше или больше, чем другой объект.

пример с печати заявления -

>>> def cmp_to_key(mycmp):
...     'Convert a cmp= function into a key= function'
...     class K(object):
...         def __init__(self, obj, *args):
...             print('obj created with ',obj)
...             self.obj = obj
...         def __lt__(self, other):
...             print('comparing less than ',self.obj)
...             return mycmp(self.obj, other.obj) < 0
...         def __gt__(self, other):
...             print('comparing greter than ',self.obj)
...             return mycmp(self.obj, other.obj) > 0
...         def __eq__(self, other):
...             print('comparing equal to ',self.obj)
...             return mycmp(self.obj, other.obj) == 0
...         def __le__(self, other):
...             print('comparing less than equal ',self.obj)
...             return mycmp(self.obj, other.obj) <= 0
...         def __ge__(self, other):
...             print('comparing greater than equal',self.obj)
...             return mycmp(self.obj, other.obj) >= 0
...         def __ne__(self, other):
...             print('comparing not equal ',self.obj)
...             return mycmp(self.obj, other.obj) != 0
...     return K
...
>>> def mycmp(a, b):
...     print("In Mycmp for", a, ' ', b)
...     if a < b:
...         return -1
...     elif a > b:
...         return 1
...     return 0
...
>>> print(sorted([3,4,2,5],key=cmp_to_key(mycmp)))
obj created with  3
obj created with  4
obj created with  2
obj created with  5
comparing less than  4
In Mycmp for 4   3
comparing less than  2
In Mycmp for 2   4
comparing less than  2
In Mycmp for 2   4
comparing less than  2
In Mycmp for 2   3
comparing less than  5
In Mycmp for 5   3
comparing less than  5
In Mycmp for 5   4
[2, 3, 4, 5]

Я только что понял, что, несмотря на то, что это не функция, класс K вызывается, потому что это класс! и классы являются вызываемыми, которые при вызове создают новый экземпляр, инициализируют его, вызывая соответствующий __init__ и затем возвращает этот экземпляр.

таким образом, он ведет себя как key функция, потому что K получает объект при вызове и обертывает этот объект в экземпляр K, который можно сравнить с другими экземплярами K.

поправьте меня, если я неправильный. Я чувствую, что попадаю на незнакомую мне территорию мета-классов.


Я не смотрел в источник, но я считаю, что результат ключевой функции также может быть чем угодно, и, следовательно, также сопоставимым объектом. И cmp_to_key просто маскирует создание тех K объектов, которые сравниваются друг с другом во время сортировки.

Если я попытаюсь создать сортировку по отделам и обратным номерам комнат, как это:

departments_and_rooms = [('a', 1), ('a', 3),('b', 2)]
departments_and_rooms.sort(key=lambda vs: vs[0])
departments_and_rooms.sort(key=lambda vs: vs[1], reverse=True)
departments_and_rooms # is now [('a', 3), ('b', 2), ('a', 1)]

это не то, что я хочу, и я думаю, что сортировка стабильна только при каждом вызове,документация is вводящий в заблуждение ИМО:

метод sort () гарантированно будет стабильным. Сортировка стабильна, если она гарантирует не изменять относительный порядок элементов, которые сравниваются равными - это полезно для сортировки в нескольких проходах (например, сортировка по отделу, а затем по классу зарплаты).

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

def mycmp(a, b):                             
    return cmp((a[0], -a[1]), (b[0], -b[1]))

departments_and_rooms = [('a', 1), ('a', 3),('b', 2)]
departments_and_rooms.sort(key=cmp_to_key(mycmp))
departments_and_rooms # is now [('a', 3), ('a', 1), ('b', 2)]

это важно разница в том, что нельзя сделать несколько проходов просто из коробки. Значения / результаты ключевой функции должны быть сортируемыми относительно, а не элементы, которые должны быть отсортированы. Поэтому маска cmp_to_key: создайте те сопоставимые объекты, которые нужно заказать.

надеюсь, что это поможет. и спасибо за понимание в коде cmp_to_key, мне тоже очень помогло:)