странное поведение объекта hashable с нулевыми кортежами в словарях Python 3.6 [duplicate]

Архитектура

Назначение значений по умолчанию в вызове функции - это запах кода.

def a(b=[]):
    pass

Это сигнатура функции, которая не подходит. Не только из-за проблем, описанных в других ответах. Я не буду вдаваться в это.

Эта функция направлена ​​на то, чтобы сделать две вещи. Создайте новый список и выполните функциональность, скорее всего, в указанном списке.

Функции, которые делают две вещи, являются плохими функциями, поскольку мы учимся на чистых практиках кода.

Атака на эту проблему с полиморфизмом мы будем расширять список python или переносить его в класс, а затем выполнять свою функцию на нем.

Но подождите, пока вы скажете, мне нравятся мои однострочные.

Ну , Угадай, что. Код - это не просто способ управления поведением оборудования. Это способ:

  • общаться с другими разработчиками, работающими над одним и тем же кодом.
  • может изменить поведение аппаратного обеспечения при появлении новых требований.
  • , способный понять поток программы после того, как вы снова забираете код через два года, чтобы сделать упомянутое выше изменение.

Не оставляйте бомбы замедленного действия для себя

Отделяя эту функцию от двух вещей, которые она делает, нам нужен класс

class ListNeedsFives(object):
    def __init__(self, b=None):
        if b is None:
            b = []
        self.b = b

    def foo():
        self.b.append(5)

Выполнено

a = ListNeedsFives()
a.foo()
a.b

И почему это лучше, чем слияние всего вышеописанного кода с одной функцией.

def dontdothis(b=None):
    if b is None:
        b = []
    b.append(5)
    return b

Почему бы не сделать это?

Если вы не сработаете в своем проекте, ваш код будет жить. Скорее всего, ваша функция будет делать больше, чем это. Правильный способ создания поддерживаемого кода состоит в том, чтобы разделить код на атомные части с должным образом ограниченным объемом.

Конструктор класса является очень общепризнанным компонентом для всех, кто сделал объектно-ориентированное программирование. Размещение логики, которая обрабатывает экземпляр списка в конструкторе, делает когнитивную нагрузку понимания того, что делает код меньше.

Метод foo() не возвращает список, почему бы и нет?

При возврате отдельного списка вы можете предположить, что безопасно делать то, что вам хочется. Но это может быть не так, поскольку он также разделяется объектом a. Заставляя пользователя ссылаться на него как a.b, он напоминает, где находится список. Любой новый код, который хочет изменить a.b, естественно, будет помещен в класс, где он принадлежит.

Функция подписи def dontdothis(b=None): не имеет ни одного из этих преимуществ.

16
задан hamogu 22 June 2011 в 15:47
поделиться

2 ответа

Проблема заключается в том, что NaN не равен самому себе, как определено в стандарте IEEE для чисел с плавающей запятой:

>>> float("nan") == float("nan")
False

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

  1. Вычислить хэш ключа для поиска.
  2. Для каждого ключа в dict с тем же хэшем проверьте, соответствует ли он ключевому слову, который нужно найти. Эта проверка состоит из a. Проверка на идентификацию объекта: если ключ в словаре и клавиша для поиска - это тот же объект, что и указатель оператора is, ключ был найден. б. Если первая проверка не удалась, проверьте равенство, используя оператор __eq__.

Первый пример завершается успешно, так как np.nan и np.nan являются одним и тем же объектом, поэтому это не имеет значения они не сравниваются с равными:

>>> numpy.nan is numpy.nan
True

Во втором случае np.float64(np.nan) и np.float64(np.nan) не являются одним и тем же объектом - два вызова конструктора создают два разных объекта:

>>> numpy.float64(numpy.nan) is numpy.float64(numpy.nan)
False

Поскольку объекты также не сравниваются одинаково, словарь завершает, что ключ не найден и выбрасывает KeyError.

Вы даже можете сделать это:

>>> a = float("nan")
>>> b = float("nan")
>>> {a: 1, b: 2}
{nan: 1, nan: 2}

В заключение, кажется, более разумная идея избегать NaN в качестве словарного ключа.

30
ответ дан Sven Marnach 21 August 2018 в 04:16
поделиться
  • 1
    ops ... не проверил это правильно :) – JBernardo 22 June 2011 в 16:04
  • 2
    Последнее утверждение заслуживает гораздо большего внимания. – job 22 June 2011 в 16:50
  • 3
    Есть ли гарантия, что все float('nan') имеют одинаковое расположение памяти, т. Е. float('nan') является одноэлементным? Без него даже использование plain float('nan') - плохая идея. Тот же вопрос о np.nan. – max 17 April 2015 в 22:44
  • 4
    @max: каждый вызов float('nan') создает новый экземпляр float, так как каждый вызов float(1) создает новый экземпляр float. Это не само по себе плохо. np.nan является глобальным именем в модуле NumPy и указывает на тот же объект, если вы его не переназначаете, поэтому при нормальных обстоятельствах np.nan - это одно значение. (Я бы не назвал его singleton, так как это имя используется для класса, который разрешает только один экземпляр, например NoneType.) – Sven Marnach 20 April 2015 в 14:15
  • 5
    @SvenMarnach А это имеет смысл. В принципе, безопасно использовать nan и dict, если и хранилище, и поиск выполняются с помощью np.nan. "Дикий" nan s, как и созданные float('nan') или float('inf') - float('inf'), не будут работать как словарные ключи. – max 21 April 2015 в 03:21

Обратите внимание, что это не так в Python 3.6:

>>> d = float("nan")
>>> d
nan
>>> c = {"a": 3, d: 4}
>>> c["a"]
3
>>> c[d]
4

Как я понимаю:

d = объект nan c - словарь, который содержит 3, связанных с «а» и «4» связаны с наном, но, поскольку внешний вид Python 3.6 в словарях изменился, теперь он сравнивает два указателя, и если они указывают на один и тот же объект, они считают, что это равенство сохраняется.

Это означает, что хотя:

>>> d == d
False

Из-за того, как IEEE754 указывает, что NAN не равен самому себе, при поиске словаря сначала учитываются указатели и потому, что они указывают на тот же nan-объект, который он возвращает 4.

Обратите также внимание на то, что:

>>> e = float("nan")
>>> e == d
False
>>> c[e]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: nan
>>> c[d]
4

Таким образом, не каждый нанок указывает на 4, поэтому сохраняется какой-то IEEE754. Это было реализовано, потому что соблюдение стандарта, что нан никогда не совпадает с самим собой, снижает эффективность, а не игнорирует стандарт. Именно потому, что вы храните что-то в словаре, к которому вы не можете получить доступ в предыдущих версиях.

2
ответ дан Josep 21 August 2018 в 04:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: