programing

Python 3.x에서 2.x와 유사한 정렬 동작을 얻으려면 어떻게해야합니까?

randomtip 2021. 1. 15. 08:06
반응형

Python 3.x에서 2.x와 유사한 정렬 동작을 얻으려면 어떻게해야합니까?


3.x에서 Python 2.x의 정렬 동작을 복제 (가능한 경우 개선)하여 int, float등과 같은 상호 정렬 가능한 유형 이 예상대로 정렬되고 상호 정렬 불가능한 유형이 출력 내에서 그룹화되도록 노력하고 있습니다.

다음은 제가 말하는 내용의 예입니다.

>>> sorted([0, 'one', 2.3, 'four', -5])  # Python 2.x
[-5, 0, 2.3, 'four', 'one']
>>> sorted([0, 'one', 2.3, 'four', -5])  # Python 3.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() < int()

키 매개 변수에 대한 클래스를 사용하는 이전 시도 sorted()( 이종 시퀀스 정렬을 위해이 키 클래스가 이상하게 작동하는 이유 참조 )는 근본적으로 깨졌습니다.

  1. 값을 비교하려고 시도하고
  2. 실패하면 해당 유형의 문자열 표현을 비교하는 것으로 되돌아갑니다.

BrenBarn의 우수한 답변에서 설명했듯이 자동 정렬로 이어질 수 있습니다 .

코딩을 시도하지 않고 처음에 거부했던 순진한 접근 방식은 (type, value)튜플 을 반환하는 키 함수를 사용하는 것입니다 .

def motley(value):
    return repr(type(value)), value

그러나 이것은 내가 원하는 것을하지 않습니다. 우선 상호 정렬 가능한 유형의 자연스러운 순서를 깨뜨립니다.

>>> sorted([0, 123.4, 5, -6, 7.89])
[-6, 0, 5, 7.89, 123.4]
>>> sorted([0, 123.4, 5, -6, 7.89], key=motley)
[7.89, 123.4, -6, 0, 5]

둘째, 입력에 본질적으로 정렬 할 수없는 동일한 유형의 두 개체가 포함 된 경우 예외가 발생합니다.

>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()

... 이는 Python 2.x와 3.x 모두에서 표준 동작입니다.하지만 이상적으로는 이러한 유형을 함께 그룹화하고 싶습니다 (순서는 특별히 신경 쓰지 않지만 원래 순서를 유지한다는 Python의 안정적인 정렬 보장).

특수 케이스를 사용하여 숫자 유형에 대한 첫 번째 문제를 해결할 수 있습니다.

from numbers import Real
from decimal import Decimal

def motley(value):
    numeric = Real, Decimal
    if isinstance(value, numeric):
        typeinfo = numeric
    else:
        typeinfo = type(value)
    return repr(typeinfo), value

... 그것이 가능한 한 작동합니다.

>>> sorted([0, 'one', 2.3, 'four', -5], key=motley)
[-5, 0, 2.3, 'four', 'one']

...하지만 상호 순서가 가능한 다른 고유 한 (사용자 정의 된) 유형이있을 수 있다는 사실을 설명하지 않으며, 물론 본질적으로 순서가 불가능한 유형에서는 여전히 실패합니다.

>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()

임의적이고 구별되지만 상호 순서가 가능한 유형의 문제 본질적으로 순서가 불가능한 유형 의 문제 모두 해결하는 또 다른 접근 방식이 있습니까?


어리석은 생각 : 서로 비교할 수있는 그룹의 모든 다른 항목을 나누고 개별 그룹을 정렬 한 다음 마지막으로 연결하는 첫 번째 패스를 만드십시오. 항목이 그룹의 첫 번째 구성원과 비교할 수 있다면 그룹의 모든 구성원과 비교할 수 있다고 가정합니다. 다음과 같은 것 (Python3) :

import itertools

def python2sort(x):
    it = iter(x)
    groups = [[next(it)]]
    for item in it:
        for group in groups:
            try:
                item < group[0]  # exception if not comparable
                group.append(item)
                break
            except TypeError:
                continue
        else:  # did not break, make new group
            groups.append([item])
    print(groups)  # for debugging
    return itertools.chain.from_iterable(sorted(group) for group in groups)

비교할 수있는 항목이없는 한심한 경우에는 2 차 실행 시간이 있지만 확실히 알 수있는 유일한 방법은 가능한 모든 조합을 확인하는 것입니다. 복소수와 같이 정렬 할 수없는 항목의 긴 목록을 정렬하려는 모든 사람에게 합당한 처벌로 2 차 동작을 참조하십시오. 일부 문자열과 정수가 혼합 된보다 일반적인 경우 속도는 일반 정렬의 속도와 유사해야합니다. 빠른 테스트 :

In [19]: x = [0, 'one', 2.3, 'four', -5, 1j, 2j,  -5.5, 13 , 15.3, 'aa', 'zz']

In [20]: list(python2sort(x))
[[0, 2.3, -5, -5.5, 13, 15.3], ['one', 'four', 'aa', 'zz'], [1j], [2j]]
Out[20]: [-5.5, -5, 0, 2.3, 13, 15.3, 'aa', 'four', 'one', 'zz', 1j, 2j]

또한 비교할 수없는 항목이 만나는 순서대로 그룹이 형성되기 때문에 '안정된 정렬'인 것 같습니다.


이 답변은 Python 3에서 모든 세부 사항에서 Python 2 정렬 순서를 충실히 재현하는 것을 목표로합니다.

실제 파이썬이 구현은 상당히 관련되어 있지만, object.c의는default_3way_compare 인스턴스가 정상 비교 규칙을 구현할 수있는 기회를 제공 한 후 최종 대체 않습니다. 이것은 개별 유형에 비교할 기회가 주어진 후입니다 ( __cmp__또는 __lt__후크 를 통해 ).

이 함수를 래퍼에서 순수 Python으로 구현하고 규칙 ( dict및 특히 ​​복소수)에 대한 예외를 에뮬레이션하면 Python 3에서 동일한 Python 2 정렬 의미를 얻을 수 있습니다.

from numbers import Number


# decorator for type to function mapping special cases
def per_type_cmp(type_):
    try:
        mapping = per_type_cmp.mapping
    except AttributeError:
        mapping = per_type_cmp.mapping = {}
    def decorator(cmpfunc):
        mapping[type_] = cmpfunc
        return cmpfunc
    return decorator


class python2_sort_key(object):
    _unhandled_types = {complex}

    def __init__(self, ob):
       self._ob = ob

    def __lt__(self, other):
        _unhandled_types = self._unhandled_types
        self, other = self._ob, other._ob  # we don't care about the wrapper

        # default_3way_compare is used only if direct comparison failed
        try:
            return self < other
        except TypeError:
            pass

        # hooks to implement special casing for types, dict in Py2 has
        # a dedicated __cmp__ method that is gone in Py3 for example.
        for type_, special_cmp in per_type_cmp.mapping.items():
            if isinstance(self, type_) and isinstance(other, type_):
                return special_cmp(self, other)

        # explicitly raise again for types that won't sort in Python 2 either
        if type(self) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(self).__name__))
        if type(other) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(other).__name__))

        # default_3way_compare from Python 2 as Python code
        # same type but no ordering defined, go by id
        if type(self) is type(other):
            return id(self) < id(other)

        # None always comes first
        if self is None:
            return True
        if other is None:
            return False

        # Sort by typename, but numbers are sorted before other types
        self_tname = '' if isinstance(self, Number) else type(self).__name__
        other_tname = '' if isinstance(other, Number) else type(other).__name__

        if self_tname != other_tname:
            return self_tname < other_tname

        # same typename, or both numbers, but different type objects, order
        # by the id of the type object
        return id(type(self)) < id(type(other))


@per_type_cmp(dict)
def dict_cmp(a, b, _s=object()):
    if len(a) != len(b):
        return len(a) < len(b)
    adiff = min((k for k in a if a[k] != b.get(k, _s)), key=python2_sort_key, default=_s)
    if adiff is _s:
        # All keys in a have a matching value in b, so the dicts are equal
        return False
    bdiff = min((k for k in b if b[k] != a.get(k, _s)), key=python2_sort_key)
    if adiff != bdiff:
        return python2_sort_key(adiff) < python2_sort_key(bdiff)
    return python2_sort_key(a[adiff]) < python2_sort_key(b[bdiff])

후크 를 통해 유형 자체에서 지원되기 때문에 Python 2에서 구현 된 사전 정렬 처리를 통합했습니다 __cmp__. 나는 자연스럽게 키와 값에 대한 Python 2 주문을 고수했습니다.

또한 다음과 같이 정렬하려고 할 때 Python 2에서 예외가 발생하므로 복소수에 대한 특수 대 / 소문자를 추가했습니다.

>>> sorted([0.0, 1, (1+0j), False, (2+3j)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: no ordering relation is defined for complex numbers

Python 2 동작을 정확하게 에뮬레이트하려면 더 많은 특수 사례를 추가해야 할 수 있습니다.

어쨌든 복소수를 정렬하려면 숫자 가 아닌 그룹에 일관되게 배치해야합니다. 예 :

# Sort by typename, but numbers are sorted before other types
if isinstance(self, Number) and not isinstance(self, complex):
    self_tname = ''
else:
    self_tname = type(self).__name__
if isinstance(other, Number) and not isinstance(other, complex):
    other_tname = ''
else:
    other_tname = type(other).__name__

일부 테스트 사례 :

>>> sorted([0, 'one', 2.3, 'four', -5], key=python2_sort_key)
[-5, 0, 2.3, 'four', 'one']
>>> sorted([0, 123.4, 5, -6, 7.89], key=python2_sort_key)
[-6, 0, 5, 7.89, 123.4]
>>> sorted([{1:2}, {3:4}], key=python2_sort_key)
[{1: 2}, {3: 4}]
>>> sorted([{1:2}, None, {3:4}], key=python2_sort_key)
[None, {1: 2}, {3: 4}]

여기서 Python 3을 실행하지 않지만 아마도 이와 같은 것이 작동 할 것입니다. "값"에 대해 "보다 작음"비교를 수행하면 예외가 발생하는지 확인한 다음 문자열로 변환하는 것과 같이 해당 사례를 처리하기 위해 "무언가"를 수행하는지 테스트합니다.

물론 동일한 유형은 아니지만 상호 순서가 가능한 다른 유형이 목록에있는 경우 더 특별한 처리가 필요합니다.

from numbers import Real
from decimal import Decimal

def motley(value):
    numeric = Real, Decimal
    if isinstance(value, numeric):
        typeinfo = numeric
    else:
        typeinfo = type(value)

    try:
        x = value < value
    except TypeError:
        value = repr(value)

    return repr(typeinfo), value

>>> print sorted([0, 'one', 2.3, 'four', -5, (2+3j), (1-3j)], key=motley)
[-5, 0, 2.3, (1-3j), (2+3j), 'four', 'one']

예외의 사용을 피하고 유형 기반 솔루션을 사용하기 위해 다음과 같이 생각해 냈습니다.

#! /usr/bin/python3

import itertools

def p2Sort(x):
    notImpl = type(0j.__gt__(0j))
    it = iter(x)
    first = next(it)
    groups = [[first]]
    types = {type(first):0}
    for item in it:
        item_type = type(item)
        if item_type in types.keys():
            groups[types[item_type]].append(item)
        else:
            types[item_type] = len(types)
            groups.append([item])

    #debuggng
    for group in groups:
        print(group)
        for it in group:
            print(type(it),)
    #

    for i in range(len(groups)):
        if type(groups[i][0].__gt__(groups[i][0])) == notImpl:
            continue
        groups[i] = sorted(groups[i])

    return itertools.chain.from_iterable(group for group in groups)

x = [0j, 'one', 2.3, 'four', -5, 3j, 0j,  -5.5, 13 , 15.3, 'aa', 'zz']
print(list(p2Sort(x)))

목록에 다른 유형을 보유하기위한 추가 사전과 유형 보유 변수 (notImpl)가 필요합니다. 또한 여기에서는 float와 int가 혼합되지 않습니다.

산출:

================================================================================
05.04.2017 18:27:57
~/Desktop/sorter.py
--------------------------------------------------------------------------------
[0j, 3j, 0j]
<class 'complex'>
<class 'complex'>
<class 'complex'>
['one', 'four', 'aa', 'zz']
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
[2.3, -5.5, 15.3]
<class 'float'>
<class 'float'>
<class 'float'>
[-5, 13]
<class 'int'>
<class 'int'>
[0j, 3j, 0j, 'aa', 'four', 'one', 'zz', -5.5, 2.3, 15.3, -5, 13]

Python 3.2+의 한 가지 방법은 functools.cmp_to_key(). 이를 통해 값을 비교 한 다음 유형의 문자열 표현을 비교하는 솔루션을 신속하게 구현할 수 있습니다. 순서가 지정되지 않은 유형을 비교할 때 발생하는 오류를 피하고 원래 경우와 같이 순서를 유지할 수도 있습니다.

from functools import cmp_to_key

def cmp(a,b):
    try:
        return (a > b) - (a < b)
    except TypeError:
        s1, s2 = type(a).__name__, type(b).__name__
        return (s1 > s2) - (s1 < s2)

예 ( Martijn Pieters의 답변 에서 가져온 입력 목록 ) :

sorted([0, 'one', 2.3, 'four', -5], key=cmp_to_key(cmp))
# [-5, 0, 2.3, 'four', 'one']
sorted([0, 123.4, 5, -6, 7.89], key=cmp_to_key(cmp))
# [-6, 0, 5, 7.89, 123.4]
sorted([{1:2}, {3:4}], key=cmp_to_key(cmp))
# [{1: 2}, {3: 4}]
sorted([{1:2}, None, {3:4}], key=cmp_to_key(cmp))
# [None, {1: 2}, {3: 4}]

이는 항상 3 원 비교가 수행되어 시간 복잡성이 증가한다는 단점이 있습니다. 그러나 솔루션은 오버 헤드가 적고 짧고 깨끗하며 cmp_to_key()이런 종류의 Python 2 에뮬레이션 사용 사례를 위해 개발 되었다고 생각 합니다.


이 문제는 다음과 같은 방법으로 해결할 수 있습니다.

  1. 유형별로 그룹화합니다.
  2. 각 유형의 단일 대표를 비교하여 비교할 수있는 유형을 찾으십시오.
  3. 유사한 유형의 그룹을 병합합니다.
  4. 가능한 경우 병합 된 그룹을 정렬합니다.
  5. (정렬 된) 병합 된 그룹의 수익

를 사용하여 유형에서 결정적이고 정렬 가능한 키 함수를 얻을 수 있습니다 repr(type(x)). 여기서 '유형 계층'은 유형 자체의 재현에 의해 결정됩니다. 이 방법의 결함은 두 유형이 동일한 경우 __repr__(인스턴스가 아닌 유형 자체) 유형을 '혼동'하게된다는 것입니다. 이것은 tuple을 반환하는 키 함수를 사용하여 해결할 수 (repr(type), id(type))있지만이 솔루션에서는 구현하지 않았습니다.

Bas Swinkel에 비해 내 방법의 장점은 정렬 할 수없는 요소 그룹을 더 깔끔하게 처리한다는 것입니다. 우리는 2 차 행동이 없습니다. 대신, 함수는 sorted ()) 동안 첫 번째 순서를 시도한 후에 포기합니다.

내 방법은 iterable에 매우 많은 수의 다른 유형이있는 시나리오에서 최악의 기능을합니다. 이것은 드문 시나리오이지만 일어날 수 있다고 생각합니다.

def py2sort(iterable):
        by_type_repr = lambda x: repr(type(x))
        iterable = sorted(iterable, key = by_type_repr)
        types = {type_: list(group) for type_, group in groupby(iterable, by_type_repr)}

        def merge_compatible_types(types):
            representatives = [(type_, items[0]) for (type_, items) in types.items()]

            def mergable_types():
                for i, (type_0, elem_0) in enumerate(representatives, 1):
                    for type_1, elem_1 in representatives[i:]:
                         if _comparable(elem_0, elem_1):
                             yield type_0, type_1

            def merge_types(a, b):
                try:
                    types[a].extend(types[b])
                    del types[b]
                except KeyError:
                    pass # already merged

            for a, b in mergable_types():
                merge_types(a, b)
            return types

        def gen_from_sorted_comparable_groups(types):
            for _, items in types.items():
                try:
                    items = sorted(items)
                except TypeError:
                    pass #unorderable type
                yield from items
        types = merge_compatible_types(types)
        return list(gen_from_sorted_comparable_groups(types))

    def _comparable(x, y):
        try:
            x < y
        except TypeError:
            return False
        else:
            return True

    if __name__ == '__main__':    
        print('before py2sort:')
        test = [2, -11.6, 3, 5.0, (1, '5', 3), (object, object()), complex(2, 3), [list, tuple], Fraction(11, 2), '2', type, str, 'foo', object(), 'bar']    
        print(test)
        print('after py2sort:')
        print(py2sort(test))

나는 대상 시스템을 자세히 설명하면서 이런 종류의 작업 (이것에 매우 가까운 다른 시스템의 동작을 모방하는 것과 같은)을 시작하는 것이 좋습니다. 다른 코너 케이스에서 어떻게 작동해야합니까? 이를 수행하는 가장 좋은 방법 중 하나는 올바른 동작을 보장하기 위해 여러 테스트를 작성하는 것입니다. 이러한 테스트를 받으면 다음을 얻을 수 있습니다.

  • 어떤 요소가 어떤 요소 앞에 와야하는지 더 잘 이해
  • 기본 문서화
  • 일부 리팩토링 및 기능 추가에 대해 시스템을 견고하게 만듭니다. 예를 들어 규칙이 하나 더 추가되면 이전 규칙이 깨지지 않도록하는 방법은 무엇입니까?

다음과 같은 테스트 케이스를 작성할 수 있습니다.

sort2_test.py

import unittest
from sort2 import sorted2


class TestSortNumbers(unittest.TestCase):
    """
    Verifies numbers are get sorted correctly.
    """

    def test_sort_empty(self):
        self.assertEqual(sorted2([]), [])

    def test_sort_one_element_int(self):
        self.assertEqual(sorted2([1]), [1])

    def test_sort_one_element_real(self):
        self.assertEqual(sorted2([1.0]), [1.0])

    def test_ints(self):
        self.assertEqual(sorted2([1, 2]), [1, 2])

    def test_ints_reverse(self):
        self.assertEqual(sorted2([2, 1]), [1, 2])


class TestSortStrings(unittest.TestCase):
    """
    Verifies numbers are get sorted correctly.
    """

    def test_sort_one_element_str(self):
        self.assertEqual(sorted2(["1.0"]), ["1.0"])


class TestSortIntString(unittest.TestCase):
    """
    Verifies numbers and strings are get sorted correctly.
    """

    def test_string_after_int(self):
        self.assertEqual(sorted2([1, "1"]), [1, "1"])
        self.assertEqual(sorted2([0, "1"]), [0, "1"])
        self.assertEqual(sorted2([-1, "1"]), [-1, "1"])
        self.assertEqual(sorted2(["1", 1]), [1, "1"])
        self.assertEqual(sorted2(["0", 1]), [1, "0"])
        self.assertEqual(sorted2(["-1", 1]), [1, "-1"])


class TestSortIntDict(unittest.TestCase):
    """
    Verifies numbers and dict are get sorted correctly.
    """

    def test_string_after_int(self):
        self.assertEqual(sorted2([1, {1: 2}]), [1, {1: 2}])
        self.assertEqual(sorted2([0, {1: 2}]), [0, {1: 2}])
        self.assertEqual(sorted2([-1, {1: 2}]), [-1, {1: 2}])
        self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])
        self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])
        self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])

다음은 다음과 같은 정렬 기능을 가질 수 있습니다.

sort2.py

from numbers import Real
from decimal import Decimal
from itertools import tee, filterfalse


def sorted2(iterable):
    """

    :param iterable: An iterable (array or alike)
        entity which elements should be sorted.
    :return: List with sorted elements.
    """
    def predicate(x):
        return isinstance(x, (Real, Decimal))

    t1, t2 = tee(iterable)
    numbers = filter(predicate, t1)
    non_numbers = filterfalse(predicate, t2)
    sorted_numbers = sorted(numbers)
    sorted_non_numbers = sorted(non_numbers, key=str)
    return sorted_numbers + sorted_non_numbers

사용법은 매우 간단하며 테스트에 문서화되어 있습니다.

>>> from sort2 import sorted2
>>> sorted2([1,2,3, "aaa", {3:5}, [1,2,34], {-8:15}])
[1, 2, 3, [1, 2, 34], 'aaa', {-8: 15}, {3: 5}]

가능한 한 충실하게 파이썬 3에서 파이썬 2 정렬 c 코드를 구현하려고했습니다.

그래서처럼 사용 mydata.sort(key=py2key())하거나mydata.sort(key=py2key(lambda x: mykeyfunc))

def default_3way_compare(v, w):  # Yes, this is how Python 2 sorted things :)
    tv, tw = type(v), type(w)
    if tv is tw:
        return -1 if id(v) < id(w) else (1 if id(v) > id(w) else 0)
    if v is None:
        return -1
    if w is None:
        return 1
    if isinstance(v, (int, float)):
        vname = ''
    else:
        vname = type(v).__name__
    if isinstance(w, (int, float)):
        wname = ''
    else:
        wname = type(w).__name__
    if vname < wname:
        return -1
    if vname > wname:
        return 1
    return -1 if id(type(v)) < id(type(w)) else 1

def py2key(func=None):  # based on cmp_to_key
    class K(object):
        __slots__ = ['obj']
        __hash__ = None

        def __init__(self, obj):
            self.obj = func(obj) if func else obj

        def __lt__(self, other):
            try:
                return self.obj < other.obj
            except TypeError:
                return default_3way_compare(self.obj, other.obj) < 0

        def __gt__(self, other):
            try:
                return self.obj > other.obj
            except TypeError:
                return default_3way_compare(self.obj, other.obj) > 0

        def __eq__(self, other):
            try:
                return self.obj == other.obj
            except TypeError:
                return default_3way_compare(self.obj, other.obj) == 0

        def __le__(self, other):
            try:
                return self.obj <= other.obj
            except TypeError:
                return default_3way_compare(self.obj, other.obj) <= 0

        def __ge__(self, other):
            try:
                return self.obj >= other.obj
            except TypeError:
                return default_3way_compare(self.obj, other.obj) >= 0
    return K

이를 수행하는 한 가지 방법은 다음과 같습니다.

lst = [0, 'one', 2.3, 'four', -5]
a=[x for x in lst if type(x) == type(1) or type(x) == type(1.1)] 
b=[y for y in lst if type(y) == type('string')]
a.sort()
b.sort()
c = a+b
print(c)

@ martijn-pieters python2의 목록에 __cmp__목록 객체 비교를 처리하거나 python2에서 처리하는 방법이 있는지 모르겠습니다 .

어쨌든 @ martijn-pieters의 답변 외에도 다음 목록 비교기를 사용했기 때문에 적어도 동일한 입력 세트에서 요소의 다른 순서를 기반으로 다른 정렬 된 출력을 제공하지 않습니다.

@per_type_cmp(list) def list_cmp(a, b): for a_item, b_item in zip(a, b): if a_item == b_item: continue return python2_sort_key(a_item) < python2_sort_key(b_item) return len(a) < len(b)

따라서 Martijn의 원래 답변과 결합하십시오.

from numbers import Number


# decorator for type to function mapping special cases
def per_type_cmp(type_):
    try:
        mapping = per_type_cmp.mapping
    except AttributeError:
        mapping = per_type_cmp.mapping = {}
    def decorator(cmpfunc):
        mapping[type_] = cmpfunc
        return cmpfunc
    return decorator


class python2_sort_key(object):
    _unhandled_types = {complex}

    def __init__(self, ob):
       self._ob = ob

    def __lt__(self, other):
        _unhandled_types = self._unhandled_types
        self, other = self._ob, other._ob  # we don't care about the wrapper

        # default_3way_compare is used only if direct comparison failed
        try:
            return self < other
        except TypeError:
            pass

        # hooks to implement special casing for types, dict in Py2 has
        # a dedicated __cmp__ method that is gone in Py3 for example.
        for type_, special_cmp in per_type_cmp.mapping.items():
            if isinstance(self, type_) and isinstance(other, type_):
                return special_cmp(self, other)

        # explicitly raise again for types that won't sort in Python 2 either
        if type(self) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(self).__name__))
        if type(other) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(other).__name__))

        # default_3way_compare from Python 2 as Python code
        # same type but no ordering defined, go by id
        if type(self) is type(other):
            return id(self) < id(other)

        # None always comes first
        if self is None:
            return True
        if other is None:
            return False

        # Sort by typename, but numbers are sorted before other types
        self_tname = '' if isinstance(self, Number) else type(self).__name__
        other_tname = '' if isinstance(other, Number) else type(other).__name__

        if self_tname != other_tname:
            return self_tname < other_tname

        # same typename, or both numbers, but different type objects, order
        # by the id of the type object
        return id(type(self)) < id(type(other))


@per_type_cmp(dict)
def dict_cmp(a, b, _s=object()):
    if len(a) != len(b):
        return len(a) < len(b)
    adiff = min((k for k in a if a[k] != b.get(k, _s)), key=python2_sort_key, default=_s)
    if adiff is _s:
        # All keys in a have a matching value in b, so the dicts are equal
        return False
    bdiff = min((k for k in b if b[k] != a.get(k, _s)), key=python2_sort_key)
    if adiff != bdiff:
        return python2_sort_key(adiff) < python2_sort_key(bdiff)
    return python2_sort_key(a[adiff]) < python2_sort_key(b[bdiff])

@per_type_cmp(list)
def list_cmp(a, b):
    for a_item, b_item in zip(a, b):
        if a_item == b_item:
            continue
        return python2_sort_key(a_item) < python2_sort_key(b_item)
    return len(a) < len(b)

추신 : 댓글로 작성하는 것이 더 합리적이지만 댓글을 달 수있는 평판이 충분하지 않았습니다. 그래서 대신 답으로 만들고 있습니다.

참조 URL : https://stackoverflow.com/questions/26575183/how-can-i-get-2-x-like-sorting-behaviour-in-python-3-x

반응형