web-dev-qa-db-fra.com

Set littéral donne un résultat différent de l'appel de fonction set

Pourquoi l'appel de la fonction set efface-t-il les dupes, mais l'analyse d'un ensemble littéral ne le fait pas?

>>> x = Decimal('0')
>>> y = complex(0,0)
>>> set([0, x, y])
{0}
>>> {0, x, y}
{Decimal('0'), 0j}

(Python 2.7.12. Probablement la même cause racine que pour this question similaire)

56
wim

Les ensembles testent l'égalité et jusqu'à ce qu'il y ait de nouvelles versions Python, l'ordre dans lequel elles le font peut différer en fonction de la forme que vous donnez les valeurs à l'ensemble en cours de construction, comme je vais le montrer au dessous de.

Puisque 0 == x Est vrai et 0 == y Est vrai, mais x == y Est false , le comportement ici est vraiment indéfini , car l'ensemble suppose que x == y doit être vrai si les deux premiers tests le sont également.

Si vous inversez la liste passée à set(), alors vous obtenez la même sortie qu'en utilisant un littéral, car l'ordre des tests d'égalité changements:

>>> set([y, x, 0])
set([0j, Decimal('0')])

et de même pour inverser le littéral:

>>> {y, x, 0}
set([0])

Ce qui se passe, c'est que l'ensemble littéral charge les valeurs dans la pile, puis les valeurs de la pile sont ajoutées au nouvel objet ensemble dans l'ordre inverse.

Tant que 0 Est chargé en premier , les deux autres objets sont ensuite testés par rapport à 0 Déjà dans l'ensemble. Dès que l'un des deux autres objets est chargé en premier, le test d'égalité échoue et vous obtenez deux objets ajoutés:

>>> {y, 0, x}
set([Decimal('0'), 0j])
>>> {x, 0, y}
set([0j, Decimal('0')])

Cet ensemble de littéraux ajoute des éléments à l'envers est un bogue présent dans toutes les versions de Python qui prennent en charge la syntaxe, jusqu'à Python 2.7.12 et 3.5. 2. Il a été récemment corrigé, voir problème 2602 (partie de 2.7.13, 3.5.3 et 3.6, dont aucun n'a encore été publié). Si vous regardez 2.7.12, vous pouvez voir que BUILD_SET dans ceval.c lit la pile de haut en bas:

# oparg is the number of elements to take from the stack to add
for (; --oparg >= 0;) {
    w = POP();
    if (err == 0)
        err = PySet_Add(x, w);
    Py_DECREF(w);
}

tandis que le bytecode ajoute des éléments à la pile dans l'ordre inverse (en poussant 0 sur la pile en premier):

>>> from dis import dis
>>> dis(compile('{0, x, y}', '', 'eval'))
  2           0 LOAD_CONST               1 (0)
              3 LOAD_GLOBAL              0 (x)
              6 LOAD_GLOBAL              1 (y)
              9 BUILD_SET                3
             12 RETURN_VALUE

Le correctif consiste à lire les éléments de la pile dans l'ordre inverse; version Python 2.7.1 utilise PEEK() au lieu de POP() (et un STACKADJ() pour supprimer les éléments de la pile par la suite):

for (i = oparg; i > 0; i--) {
    w = PEEK(i);
    if (err == 0)
        err = PySet_Add(x, w);
    Py_DECREF(w);
}
STACKADJ(-oparg);

Le problème du test d'égalité a la même cause fondamentale que l'autre question; la classe Decimal() a quelques problèmes d'égalité avec complex ici, qui a été corrigé dans Python 3.2 (en faisant Decimal() supporte les comparaisons avec complex et quelques autres types numériques qu'il ne supportait pas auparavant ).

55
Martijn Pieters

Tout dépend de l'ordre dans lequel l'ensemble est construit, combiné avec le bug que vous avez découvert avec votre autre question . Il semble que le littéral soit construit dans l'ordre inverse de la conversion à partir d'une liste.

>>> {0, x, y}
set([0j, Decimal('0')])
>>> {y, x, 0}
set([0])
7
Mark Ransom