Почему NaN не вызывает никаких ошибок в python?

#python

Вопрос:

На мой взгляд, такие вещи, как float('nan') следует оптимизировать, но, по-видимому, они не в Python.

 >>> NaN = float('nan')
>>> a = [ 1, 2, 3, NaN ]
>>> NaN in a
True
>>> float('nan') in a
False
 

Имеет ли это какой-то смысл в том, чтобы не оптимизировать nan , как другие вещи?
На мой взгляд, nan это всего лишь nan .

Кроме того, когда вы используете эти sorted вещи, они дают странные результаты:

 >>> sorted([3, nan, 4, 2, nan, 1])
[3, nan, 1, 2, 4, nan]


>>> 3 > float('nan')
False
>>> 3 < float('nan')
False
 

Сравнение nan определяется так, но мне это не кажется «питоническим». Почему это не вызывает ошибки?

Комментарии:

1. Мне нравится это наблюдение, и я тоже думаю, что оно сбивает с толку. Какое из 7 утверждений должно вызывать исключение?

2. В C# в результате [NaN, NaN, 1,2,3,4] получается более питонический, ИМХО.

3. Простой ответ: «потому что так определяет его IEEE754 по причинам, которые, по крайней мере, в то время, когда он был определен, имели смысл, по крайней мере, для людей, определяющих стандарт».

4. Одной из интересных особенностей NaN является то, что он нарушает протокол «(a is b) подразумевает (a==b)», и in оператор проверяет личность, прежде чем проверять равенство. Это означает, что x in somelist это может быть правдой одновременно с all(x != y for y in somelist) .

5. Что вы подразумеваете под «оптимизацией»?

Ответ №1:

Тестирование на членство

Два разных экземпляра float('nan') не равны друг другу. Они «Не являются числом», поэтому имеет смысл, что они также не должны быть равными. Это разные экземпляры объектов, которые не являются числами:

 print(float('nan') == float('nan'))  # False
 

Как описано здесь:

Для типов контейнеров, таких как список, кортеж, набор, набор, диктант или коллекции.дек, выражение x в y эквивалентно любому(x равно e или x == e для e в y).

Есть проверка личности! вот почему вы видите такое поведение в своем вопросе и почему NaN in a возвращается True и float('nan') in a не возвращается.


Сортировка на Python

Python использует алгоритм Timsort для своей sorted() функции. (Также смотрите это для текстового объяснения.) Я не собираюсь вдаваться в это. Я просто хочу продемонстрировать простой пример:

Это мой класс A . Это будет нашей float('nan') целью. Он действует так float('nan') , как если бы он возвращался False для всех операций сравнения:

 class A:
    def __init__(self, n):
        self.n = n

    def __lt__(self, other):
        print(self, 'lt is calling', other)
        return False

    def __gt__(self, other):
        print(self, 'gt is calling', other)
        return False

    def __repr__(self):
        return f'A({self.n})'

class B:
    def __init__(self, n):
        self.n = n

    def __lt__(self, other):
        print(self, 'lt is calling', other)
        return False

    def __gt__(self, other):
        print(self, 'gt is calling', other)
        return False

    def __repr__(self):
        return f'B({self.n})'
 

Когда мы используем sorted() функцию (или .sort() метод a list ) без reverse=True аргумента, мы просим, чтобы итерируемые объекты были отсортированы в порядке возрастания. Для этого Python пытается вызвать __lt__ метод последовательно, начиная со второго объекта в списке, чтобы увидеть, меньше ли он предыдущего объекта и так далее:

 lst = [A(1), B(2), A(3), B(4)]
print(sorted(lst))
 

выход :

 B(2) lt is calling A(1)
A(3) lt is calling B(2)
B(4) lt is calling A(3)
[A(1), B(2), A(3), B(4)]
 

Теперь возвращаемся к вашему примеру:

 lst = [3, A(1), 4, 2, A(1), 1]
print(sorted(lst))
 

выход:

 A(1) lt is calling 3
A(1) gt is calling 4
A(1) gt is calling 2
A(1) lt is calling 2
A(1) lt is calling 4
A(1) gt is calling 1
[3, A(1), 1, 2, 4, A(1)]
 
  1. A(1).__lt__(3) будет возвращать False . Это означает A(1)
    , что не менее 3 или это средство 3 находится в правильном положении относительно A(1) .
  2. Затем здесь int.__lt__(4, A(1)) вызывается и, поскольку он возвращает NotImplemented объект, Python проверяет, A(1) реализован __gt__ ли он, и да, поэтому A(1).__gt__(4) вернется False снова, и это означает A(1) , что объект находится в правильном месте относительно 4 .
  3. (И Т.д.)

Вот почему результат sorted() кажется странным, но он предсказуем. A(1) объект в обоих случаях, я имею в виду, когда int класс возвращается NotImplemented и когда __lt__ вызывается из A(1) , вернет False.

Лучше проверить алгоритм Timsort и учесть эти моменты. Я бы включил оставшиеся шаги, если бы внимательно прочитал алгоритм Timsort.

Комментарии:

1. @don’talkjustcode Спасибо, исправлено, а также обновлено с лучшим примером.

2. Да, лучше с B. Можно также заставить B вместо этого вести себя как int, т. Е. Нормально сравнивать с другими объектами B и возвращать не реализованные объекты A. Тогда мы увидим этот int.__lt__(4, A(1)) вызов в выходных данных