web-dev-qa-db-fra.com

Pourquoi range (0) == range (2, 2, 2) True in Python 3?

Pourquoi les plages qui sont initialisées avec des valeurs différentes se comparent-elles égales les unes aux autres dans Python 3?

Lorsque j'exécute les commandes suivantes dans mon interprète:

>>> r1 = range(0)
>>> r2 = range(2, 2, 2)
>>> r1 == r2
True

Le résultat est True. Pourquoi cela est-il ainsi? Pourquoi deux objets range différents avec des valeurs de paramètre différentes sont-ils traités comme égaux?

50
root

Les objets range sont spéciaux:

Python comparera range les objets comme Sequences. Cela signifie essentiellement que la comparaison n'évalue pas comment ils représentent une séquence donnée mais plutôt ce que ils représentent.

Le fait que les paramètres start, stop et step soient complètement différents ne fait aucune différence ici car ils représentent tous une liste vide lorsqu'ils sont développés :

Par exemple, le premier objet range:

list(range(0))  # []

et le deuxième range:

list(range(2, 2, 2)) # []

Les deux représentent une liste vide et puisque deux listes vides comparent des objets égaux (True), les objets range que représentent eux.

Par conséquent, vous pouvez avoir des objets looking range complètement différents; s'ils représentent la même séquence, ils seront compare égaux:

range(1, 5, 100) == range(1, 30, 100) 

Les deux représentent une liste avec un seul élément [1] afin que ces deux comparent également égaux.


Non, les objets range sont vraiment spéciaux:

Notez cependant que même si la comparaison n'évalue pas comment ils représentent une séquence le résultat de la comparaison peut être obtenu en utilisant uniquement les valeurs de start, step avec le len du range; cela a des implications très intéressantes avec la vitesse des comparaisons:

r0 = range(1, 1000000)    
r1 = range(1, 1000000)

l0 = list(r0)    
l1 = list(r1)

Les gammes sont super rapides:

%timeit r0 == r1
The slowest run took 28.82 times longer than the fastest. This could mean that an intermediate result is being cached 
10000000 loops, best of 3: 160 ns per loop

d'autre part, les listes ..

%timeit l0 == l1
10 loops, best of 3: 27.8 ms per loop

Ouais..


Comme @ SuperBiasMan, cela ne s'applique qu'aux objets de plage dans Python 3. Python 2 range() est une ancienne fonction qui renvoie une liste tandis que l'objet 2.xxrange n'a pas le comparer les capacités (et pas seulement celles-ci ..) que les objets range ont dans Python 3 .

Regardez @ réponse de ajcr pour les citations directement à partir du code source sur Python 3 range objets. Il y est documenté ce que la comparaison entre deux plages différentes implique réellement: Opérations rapides simples. La fonction range_equals est utilisée dans la fonction range_richcompare (fonction pour les cas EQ et NE et affectés au emplacement tp_richcompare pour les types PyRange_Type.

Je crois que l'implémentation de range_equals est assez lisible (car c'est aussi simple que Nice) à ajouter ici:

/* r0 and r1 are pointers to rangeobjects */

/* Check if pointers point to same object, example:    
       >>> r1 = r2 = range(0, 10)
       >>> r1 == r2
   obviously returns True. */
if (r0 == r1)
    return 1;

/* Compare the length of the ranges, if they are equal 
   the checks continue. If they are not, False is returned. */
cmp_result = PyObject_RichCompareBool(r0->length, r1->length, Py_EQ);
/* Return False or error to the caller
       >>> range(0, 10) == range(0, 10, 2)  
   fails here */
if (cmp_result != 1)
    return cmp_result;

/* See if the range has a lenght (non-empty). If the length is 0
   then due to to previous check, the length of the other range is 
   equal to 0. They are equal. */
cmp_result = PyObject_Not(r0->length);
/* Return True or error to the caller. 
       >>> range(0) == range(2, 2, 2)  # True
   (True) gets caught here. Lengths are both zero. */
if (cmp_result != 0)
    return cmp_result;

/* Compare the start values for the ranges, if they don't match
   then we're not dealing with equal ranges. */
cmp_result = PyObject_RichCompareBool(r0->start, r1->start, Py_EQ);
/* Return False or error to the caller. 
   lens are equal, this checks their starting values
       >>> range(0, 10) == range(10, 20)  # False
   Lengths are equal and non-zero, steps don't match.*/
if (cmp_result != 1)
    return cmp_result;

/* Check if the length is equal to 1. 
   If start is the same and length is 1, they represent the same sequence:
       >>> range(0, 10, 10) == range(0, 20, 20)  # True */
one = PyLong_FromLong(1);
if (!one)
    return -1;
cmp_result = PyObject_RichCompareBool(r0->length, one, Py_EQ);
Py_DECREF(one);
/* Return True or error to the caller. */
if (cmp_result != 0)
    return cmp_result;

/* Finally, just compare their steps */
return PyObject_RichCompareBool(r0->step, r1->step, Py_EQ);

J'ai également dispersé certains de mes propres commentaires ici; regardez @ réponse ajcr pour l'équivalent Python équivalent.

71

Citation directe de les docs (c'est moi qui souligne):

Tester l'égalité des objets de plage avec == et! = Les compare en tant que séquences. Autrement dit, deux objets de plage sont considérés comme égaux s'ils représentent la même séquence de valeurs . (Notez que deux objets de plage qui se comparent égaux peuvent avoir des attributs de début, d'arrêt et de pas différents, par exemple plage (0) == plage (2, 1, 3) ou plage (0, 3, 2) == plage (0, 4, 2).)

Si vous comparez ranges avec la "même" liste, vous obtiendrez également une inégalité, comme indiqué dans la documentation :

Les objets de différents types, à l'exception de différents types numériques, ne sont jamais comparables.

Exemple:

>>> type(range(1))
<class 'range'>
>>> type([0])
<class 'list'>
>>> [0] == range(1)
False
>>> [0] == list(range(1))
True

Notez que cela ne s'applique explicitement qu'à Python 3. Dans Python 2, où range renvoie simplement une liste, range(1) == [0] est évalué comme True.

13
Jasper

Pour ajouter quelques détails supplémentaires aux excellentes réponses sur cette page, deux range objets r0 Et r1 Sont comparés à peu près comme suit :

if r0 is r1:                 # True if r0 and r1 are same object in memory
    return True
if len(r0) != len(r1):       # False if different number of elements in sequences
    return False
if not len(r0):              # True if r0 has no elements
    return True
if r0.start != r1.start:     # False if r0 and r1 have different start values
    return False
if len(r0) == 1:             # True if r0 has just one element
    return True
return r0.step == r1.step    # if we made it this far, compare step of r0 and r1

La longueur d'un objet range est facile à calculer à l'aide des paramètres start, stop et step. Dans le cas où start == stop, Par exemple, Python peut immédiatement savoir que la longueur est 0. Dans les cas non triviaux, Python peut il suffit de faire un calcul arithmétique simple en utilisant les valeurs start, stop et step.

Donc, dans le cas de range(0) == range(2, 2, 2), Python fait ce qui suit:

  1. voit que range(0) et range(2, 2, 2) sont des objets différents en mémoire.
  2. calcule la longueur des deux objets; les deux longueurs sont 0 (car start == stop dans les deux objets) donc un autre test est nécessaire.
  3. voit que len(range(0)) est 0. Cela signifie que len(range(2, 2, 2)) est également 0 (le précédent test d'inégalité a échoué) et donc la comparaison doit retourner True.
10
Alex Riley

res = range(0) == range(2, 2, 2)

Où:

range(0)

signifie la plage de 0 à 0 - 0 étapes (ici step est égal à la valeur par défaut 1), liste sans valeurs.

range(2, 2, 2)

signifie la plage de 2 à 2 avec un pas égal à 2, liste sans valeurs.

Donc, ces plages sont vraiment égales

6
pivanchy

range(0) renvoie range(0,0). Vous commencez de 0 à 0 avec l'étape 1, qui n'est pas définie car le troisième argument ne peut pas être 0 [par défaut]. Vous ne pouvez pas atteindre 0 avec 1. Aucune action de compteur en place, donc 0.

range(2, 2, 2) renvoie range(2, 2, 2). Vous commencez de 2 à 2 mais avec un pas de 2. Ce qui est à nouveau fondamentalement 0 puisque vous ne comptez jusqu'à rien.

range(0) == range(2,2,2) 

Vrai et exactement pareil.

5
喬治扎菲