python python-3.x sorting order python-2.x

¿Cómo puedo obtener un comportamiento de clasificación similar a 2.x en Python 3.x?



python-3.x sorting (10)

@ martijn-pieters No sé si la lista en python2 también tiene un __cmp__ para manejar la comparación de objetos de lista o cómo se manejó en python2.

De todos modos, además de la respuesta de @ martijn-pieters , utilicé el siguiente comparador de listas, por lo que al menos no proporciona una salida ordenada diferente en función de un orden diferente de elementos en el mismo conjunto de entrada.

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

Entonces, uniéndolo con la respuesta original de 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)

PD: Tiene más sentido crearlo como un comentario, pero no tenía suficiente reputación para hacer un comentario. Entonces, lo estoy creando como una respuesta.

Estoy tratando de replicar (y si es posible mejorar) el comportamiento de clasificación de Python 2.x en 3.x, de modo que los tipos que se pueden ordenar mutuamente como int , float , etc. se ordenan como se esperaba, y los tipos que no se pueden ordenar mutuamente se agrupan dentro de la salida.

Aquí hay un ejemplo de lo que estoy hablando:

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

Mi intento anterior de esto, usando una clase para el parámetro clave para sorted() (ver ¿Por qué esta clase clave para ordenar secuencias heterogéneas se comporta de manera extraña? ) Está fundamentalmente rota, porque su enfoque de

  1. Tratando de comparar valores, y
  2. Si eso falla, recurrir a la comparación de la representación de cadena de sus tipos

puede conducir a un orden intransitivo, como lo explica la excelente respuesta de BrenBarn .

Un enfoque ingenuo, que inicialmente rechacé sin siquiera intentar codificarlo, sería utilizar una función de tecla que devuelva una tupla (type, value) :

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

Sin embargo, esto no hace lo que quiero. En primer lugar, rompe el orden natural de los tipos que se pueden pedir mutuamente:

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

En segundo lugar, genera una excepción cuando la entrada contiene dos objetos del mismo tipo intrínsecamente no ordenable:

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

... que sin duda es el comportamiento estándar en Python 2.xy 3.x, pero idealmente me gustaría que estos tipos se agrupen (no me importa especialmente su orden, pero parece estar en consonancia con La garantía de Python de una clasificación estable de que conservan su orden original).

Puedo solucionar el primero de estos problemas para los tipos numéricos con mayúsculas especiales:

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

... que funciona hasta donde llega:

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

... pero no tiene en cuenta el hecho de que puede haber otros tipos distintos (posiblemente definidos por el usuario) que se pueden pedir mutuamente y, por supuesto, todavía falla con los tipos intrínsecamente no ordenados:

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

¿Existe otro enfoque que resuelva tanto el problema de los tipos arbitrarios, distintos pero mutuamente ordenables como el de los tipos intrínsecamente no ordenables?


Aquí hay un método para lograr esto:

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)


Esta respuesta tiene como objetivo recrear fielmente el orden de clasificación de Python 2, en Python 3, en cada detalle.

La implementación real de Python 2 es bastante complicada, pero default_3way_compare object.c el object.c final después de que las instancias hayan tenido la oportunidad de implementar reglas de comparación normales. Esto es después de que los tipos individuales hayan tenido la oportunidad de comparar (a través de los ganchos __cmp__ o __lt__ ).

Implementar esa función como Python puro en un contenedor, además de emular las excepciones a las reglas (específicamente dict y números complejos) nos da la misma semántica de clasificación de Python 2 en Python 3:

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

Incorporé el manejo de la ordenación del diccionario como se implementó en Python 2 , ya que eso sería compatible con el tipo en sí a través de un __cmp__ . Me he adherido al pedido de Python 2 para las claves y los valores también, naturalmente.

También agregué una carcasa especial para números complejos, ya que Python 2 genera una excepción cuando intenta ordenarlos:

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

Es posible que deba agregar más casos especiales si desea emular exactamente el comportamiento de Python 2.

Si de todos modos desea ordenar números complejos, deberá colocarlos consistentemente con el grupo que no sea de números; p.ej:

# 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__

Algunos casos de prueba:

>>> 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}]


Idea estúpida: haga un primer paso para dividir todos los elementos diferentes en grupos que se pueden comparar entre sí, ordenar los grupos individuales y finalmente concatenarlos. Supongo que un elemento es comparable a todos los miembros de un grupo, si es comparable con el primer miembro de un grupo. Algo como esto (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)

Esto tendrá un tiempo de ejecución cuadrático en el caso patético de que ninguno de los elementos es comparable, pero supongo que la única forma de saberlo con certeza es verificar todas las combinaciones posibles. Vea el comportamiento cuadrático como un castigo merecido para cualquiera que intente ordenar una larga lista de elementos no ordenables, como números complejos. En un caso más común de una mezcla de algunas cadenas y algunos enteros, la velocidad debería ser similar a la velocidad de un tipo normal. Examen rápido:

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]

Parece ser también un "tipo estable", ya que los grupos se forman en el orden en que se encuentran los elementos incomparables.


Me gustaría recomendar comenzar este tipo de tarea (como imitar el comportamiento de otro sistema muy cercano a este) con una aclaración detallada del sistema de destino. ¿Cómo debería funcionar con diferentes casos de esquina? Una de las mejores formas de hacerlo es escribir un montón de pruebas para garantizar un comportamiento correcto. Tener tales pruebas da:

  • Comprenda mejor qué elementos deben preceder a qué
  • Documentación básica
  • Hace que el sistema sea robusto frente a algunas funciones de refactorización y adición. Por ejemplo, si se agrega una regla más, ¿cómo asegurarse de que las anteriores no se rompan?

Uno puede escribir tales casos de prueba:

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

El siguiente puede tener esa función de clasificación:

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

El uso es bastante simple y está documentado en pruebas:

>>> 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}]


No ejecuta Python 3 aquí, pero tal vez algo como esto funcione. Pruebe para ver si hacer un "menos que" comparar en "valor" crea una excepción y luego haga "algo" para manejar ese caso, como convertirlo en una cadena.

Por supuesto, aún necesitaría un manejo más especial si hay otros tipos en su lista que no son del mismo tipo pero que se pueden pedir mutuamente.

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'']


Para evitar el uso de excepciones y buscar una solución basada en tipos, se me ocurrió esto:

#! /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)))

Tenga en cuenta que se necesita un diccionario adicional para contener los diferentes tipos en la lista y una variable de retención de tipos (notImpl). Nota adicional, que las carrozas y las entradas no se mezclan aquí.

Salida:

================================================================================ 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]


Podemos resolver este problema de la siguiente manera.

  1. Agrupar por tipo.
  2. Encuentre qué tipos son comparables intentando comparar un solo representante de cada tipo.
  3. Fusionar grupos de tipos comparables.
  4. Ordenar grupos combinados, si es posible.
  5. rendimiento de grupos fusionados (ordenados)

Podemos obtener una función de clave determinista y ordenable de los tipos mediante el uso de repr(type(x)) . Tenga en cuenta que la ''jerarquía de tipos'' aquí está determinada por la reproducción de los tipos mismos. Una falla en este método es que si dos tipos tienen __repr__ idénticos (los tipos mismos, no las instancias), ''confundirá'' los tipos. Esto se puede resolver mediante el uso de una función de tecla que devuelve una tupla (repr(type), id(type)) , pero no lo he implementado en esta solución.

La ventaja de mi método sobre Bas Swinkel es un manejo más limpio de un grupo de elementos que no se pueden ordenar. No tenemos comportamiento cuadrático; en cambio, la función se da por vencida después del primer intento de ordenar durante sorted ()).

Mi método funciona peor en el escenario donde hay un número extremadamente grande de tipos diferentes en el iterable. Este es un escenario raro, pero supongo que podría surgir.

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


Traté de implementar el código de clasificación de Python 2 en python 3 con la mayor fidelidad posible.

Úselo así: mydata.sort(key=py2key()) o 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


Una forma para Python 3.2+ es usar functools.cmp_to_key() . Con esto, puede implementar rápidamente una solución que intente comparar los valores y luego recurrir a la comparación de la representación de cadena de los tipos. También puede evitar que se genere un error al comparar tipos desordenados y dejar el orden como en el caso original:

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)

Ejemplos (listas de entrada tomadas de la respuesta de 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}]

Esto tiene la desventaja de que la comparación de tres vías siempre se realiza, lo que aumenta la complejidad del tiempo. Sin embargo, la solución es baja, corta, limpia y creo que cmp_to_key() se desarrolló para este tipo de caso de uso de emulación de Python 2.