Numpy: «Управляемое» создание массивов объектов

Созданный на 4 дек. 2019  ·  45Комментарии  ·  Источник: numpy/numpy

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

Воспроизведение примера кода:

Matplotlib содержит следующий фрагмент:

    # <named ("string") colors are handled earlier>
    # tuple color.
    c = np.array(c)
    if not np.can_cast(c.dtype, float, "same_kind") or c.ndim != 1:
        # Test the dtype explicitly as `map(float, ...)`, `np.array(...,
        # float)` and `np.array(...).astype(float)` all convert "0.5" to 0.5.
        # Test dimensionality to reject single floats.
        raise ValueError(f"Invalid RGBA argument: {orig_c!r}")

но иногда функция вызывается с массивом цветов в различных форматах (например, ["red", (0.5, 0.5, 0.5), "blue"] ) - мы перехватываем ValueError и вместо этого конвертируем каждый элемент по одному.

Теперь вызов np.array (c) выдаст DeprecationWarning. Как мы можем это обойти? Даже что-то вроде np.min_scalar_type(c) излучает предупреждение (которое, я полагаю, не должно?), Поэтому мне не очевидно, как проверить, "если бы мы преобразовали эту вещь в массив, каким был бы dtype?"

Информация о версии Numpy / Python:


1.19.0.dev0 + bd1adc3 3.8.0 (по умолчанию, 6 ноября 2019 г., 21:49:08)
[GCC 7.3.0]

57 - Close?

Самый полезный комментарий

Может ли кто-нибудь указать на пример operator.mod ?

Что касается оператора == , тот, который я видел, делал что-то вроде np.array(vals, dtype=object) == vals where vals=[1, [2, 3]] (перефразируя код), поэтому решение состоит в том, чтобы заранее создать массив справа боковая сторона.

Многие из scipy сбоев, похоже, имеют форму np.array([0.25, np.array([0.3])]) , где смешивание скаляров и ndarray с shape==(1,) нарушит обнаружение измерений и создаст массив объектов. xref gh-15075

Все 45 Комментарий

Один из вариантов был бы
`` питон
пытаться:
# опередить игру и продвинуть устаревание до ошибки, которая заменит его
с warnings.catch_warnings ():
warnings.filterwarnings ('поднять', DeprecationWarning, message = "...")
c_arr = np.asarray (с)
кроме (DeprecationWarning, ValueError):
# все, что вы сейчас делаете для ValueError

Я предполагаю, что это, и неудачный тест, упомянутый в gh-15045, - это случаи, когда выдача DeprecationWarning в течение нескольких лет вместо прямого испускания ValueError вызывает больше оттока кода, чем необходимо.

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

Я думаю, что отток кода стоит периода устаревания.

Matplotlib запускает свой набор тестов с предупреждениями о сбоях, чтобы своевременно уловить именно такие изменения, так что мне кажется, что система работает :).

Но AFAICT нет даже достаточно простого исправления (как указано выше, предлагаемое исправление не является потокобезопасным) для него: /

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

Проблема в том, что на сегодняшний день у автора библиотеки нет возможности спросить: «Будет ли это выдавать предупреждение?» Без фактического ... выдачи предупреждения, а его подавление не является потокобезопасным.

Что касается предупреждений о безопасности потоков: https://bugs.python.org/issue37604

AFAIK, устаревание находится в ветке выпуска. Мы хотим вернуть его обратно? В противном случае для исправлений потребуются резервные копии. Я все еще не понимаю, почему предупреждения не появлялись в колесах ветки выпуска и не отображались в ночных сборках до последних двух сборок. Я ничего не менял после ветки, и с тех пор в основной ветке ничего не выглядит подозрительно, кроме, возможно, # 15040.

ИМХО (и в соответствии с точкой @mattip выше) это такие изменения, которые было бы намного проще обрабатывать ниже по течению, если бы переход на повышение произошел без периода устаревания. Не уверен, что это вариант: /

Или, возможно, multibuild обрабатывает ветки иначе, чем master.

FWIW Я всегда был как минимум -1 при этом изменении, особенно как заядлый пользователь рваных структур данных, но в любом случае теперь мне нужно выяснить, что делать с сотнями неудачных тестов для подготовки SciPy 1.4.0rc2 в https://github.com/scipy/scipy/pull/11161

теперь мне нужно выяснить, что делать с сотнями неудачных тестов

Простым вариантом будет:

  • Подавить предупреждение в вашей конфигурации pytest
  • Откройте проблему, чтобы исправить ее позже

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

AFAIK, устаревание находится в ветке выпуска. Мы хотим вернуть его обратно?

Думаю, да, идет дождь. Теперь у нас есть список того, что ломается в Pandas, Matplotlib, SciPy, внутри numpy.testing и NumPy ufuncs, == и т. Д. Я думаю, нам следует отменить изменения сейчас и оценить / исправить все эти вещи, а затем снова ввести устаревание.

Можем ли мы пойти на компромисс в отношении ожидающего предупреждения об устаревании?

Таким образом, последующие проекты могут добавить его в свои списки игнорирования, и когда мы вернемся к DeprecationWarning, они снова примут решение.

Похоже, что мы отклонились от исходной проблемы, которая, как представляется, «с учетом последовательности значений, как matplotlib может определить, являются ли они одним цветом или списком цветов». Я думаю, должно быть решение, которое не требует преобразования значений в ndarray и проверки dtype этого массива. Лучшим решением может быть какая-то рекурсивная функция is_a_color() .

Я отменил изменение для 1.18.x в # 15053.

Считается, что взлом scipy и pandas CI достаточно раздражает, чтобы временно отменить его и в мастере. Я бы хотел, чтобы он вернулся в основном по расписанию (скажем, в течение месяца). Тем не менее, нам может потребоваться найти решение. Также меня немного беспокоят исправления, которые делают панды, поскольку они используют catch_warnings .

Если действительно нет способа, и нам нужно поточно-безопасное подавление предупреждений. np.seterr может содержать слот для него: /.

Похоже, что мы отклонились от исходной проблемы, которая, как представляется, «с учетом последовательности значений, как matplotlib может определить, являются ли они одним цветом или списком цветов».

Я думаю, что проблема, которую поднимает

  • создать ndarray(flexible_input)
  • если `new_ndarray.dtype.kind == 'O': обработать это
  • еще: use_the_array

что делать, если к такому коду нельзя добавить dtype=object ?

Также меня немного беспокоят исправления, которые делают панды, поскольку они используют catch_warnings .

@seberg не лучше ли для этого suppress_warnings ?

@rgommers нет, suppress_warnings решил проблему постоянного подавления предупреждений, хотя этого быть не должно. Это было исправлено в более новых версиях python, поэтому он нам больше не нужен (он имеет лучшие свойства, так как поддерживает вложение, но не поддерживает потокобезопасность. Я не уверен, что это возможно за пределами python, и даже если бы было то наверное не желательно)

Не совсем уверен, что проблемные случаи не совпадают с первоначальным намерением (https://numpy.org/neps/nep-0034.html), чего мы просто не ожидаем.

Как бы то ни было, выходом было бы явно включить старое поведение по принципу «принимая во внимание вашу озабоченность, но мы явно хотим контекстно-зависимый объект dtype и сами будем обрабатывать проблемный ввод». Что-то вроде одного из

~~~
np.array (данные, dtype = 'allow_object')

np.array (данные, allow_object_dtype = True)

с np.array_create_allow_object_dtype ():
np.array (данные)
~~~

все не очень красиво и название обязательно будет улучшено. Но это дает чистый выход для библиотек, которые полагались на поведение и хотят его сохранить (по крайней мере, на данный момент).

На самом деле это не случай matplotlib:

with np.forbid_ragged_arrays_immediately():
    np.array(data)

действительно ли вы хотите поймать ошибку, а не получить объект dtype?

Отменить устаревание, ожидающее в настоящее время для master, нельзя. Я не думаю, что его следует полностью отменить, как это было в 1.18, потому что это также удалило исправления, которые, я думаю, мы хотим сохранить. @mattip Было бы

FWIW Я думаю, что большинство мест в mpl, которые поражают это, можно исправить (с более или менее реструктуризацией - в одном случае код оказывается намного быстрее после ...).
Я думаю, что предлагаемый @timhoffm API был бы лучше, чем with np.forbid_ragged_arrays_immediately: потому что последнее можно легко записать в терминах первого (поднять, если np.array(..., allow_object=True).dtype == object ), тогда как противоположное ( try: with np.forbid: ... except ValueError: ... ) будет менее эффективным, если мы все же хотим создать массив объектов. Но CM (просто «локально продвигающийся после периода устаревания») лучше, чем ничего.

(Опять же, я думаю, что изменение хорошее, это просто вопрос того, как оно выполняется.)

Да, нам просто нужно выяснить, как должно выглядеть API. Как отмечают многие, в настоящее время есть две основные проблемы:

  1. Путаница object и "allow ragged" . Если объекты имеют разумный тип (скажем, Decimal ), вы действительно хотите получить предупреждение / ошибку, но также может потребоваться передать dtype=object
  2. Невозможно подписаться на новое поведение или продолжать использовать старое (без предупреждения). Кажется, что по крайней мере Opt-In, вероятно, необходим для внутреннего использования, если мы не предоставляем его, мы в основном предполагаем, что (возможно, косвенно) только конечные пользователи сталкиваются с этими случаями?

Наконец, нам нужно придумать, как втиснуть это в наш код :). ndmin может быть еще одной целью втиснуть флаги, по крайней мере, контролирующие рваное поведение.

Отменить устаревание, ожидающее в настоящее время для master, нельзя. Я не думаю, что его следует полностью отменить, как это было в 1.18, потому что это также удалило исправления, которые, я думаю, мы хотим сохранить. @mattip Было бы

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

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

Нисходящий CI красный, это _ очень_ бесполезно. Теперь у нас есть их список неудач, и нам не нужно оставлять красный цвет их КИ, чтобы нам было немного легче здесь.

И, по крайней мере, CI Matplotlib работает против pip install --pre not master ветки

И, по крайней мере, CI Matplotlib работает против pip install --pre not master ветки

Похоже, это тянет из-за ночных колес. Изменение уже было отменено для 1.18.0rc1, поэтому вы не должны его видеть, если будете устанавливать с помощью --pre из PyPI.

Некоторые из приведенных выше комментариев сводятся к переосмыслению предлагаемых изменений в NEP 34. Я не уверен, что эта ветка является подходящим местом для продолжения этого обсуждения, но продолжаю. (Ничего страшного, если это следует обсудить в другом месте - копировать и вставлять комментарии очень просто.: Smile: Кроме того, некоторые из вас видели вариацию этих комментариев при обсуждении Slack.)

Подумав об этом недавно, я @timhoffm (и эта идея, вероятно, предлагалась в другое время в последние несколько месяцев): определить конкретную строку или одноэлементный объект, который, если задан как аргумент dtype для array позволяет функции обрабатывать ввод неровной формы, создавая массив одномерных объектов. Фактически, это обеспечивает поведение dtype=None до NEP-34, при котором входные данные рваной формы автоматически преобразуются в массив объектов. Если задано любое другое значение для dtype (включая None или object ), выдается предупреждение об устаревании, если ввод имеет рваную форму. В будущей версии NumPy это предупреждение будет преобразовано в ошибку.

Я думаю, теперь ясно, что использование dtype=object для обработки ввода неровной формы не является хорошим решением проблемы. В идеале мы бы отделили понятия «массив объектов» от «рваного массива». Но мы не можем полностью разделить их, потому что, когда мы хотим обработать рваный массив, единственный выбор, который у нас есть, - это создать массив объектов. С другой стороны, иногда нам нужен массив объектов, но мы не хотим автоматического преобразования входных данных неровной формы в массив объектов последовательностей.

Например (см. Пункт 1 в последнем комментарии @seberg ), предположим, что f1 , f2 , f3 и f4 равны Fraction objects, и я работаю с массивами объектов Fraction s. Меня не интересует создание рваного массива. Если я случайно напишу a = np.array([f1, f2, [f3, f4]], dtype=object) , я _ хочу_, чтобы это сгенерировало ошибку по всем причинам, по которым у нас есть NEP 34. Однако с NEP 34 это создаст 1-мерный массив длиной 3.

Альтернативы, которые добавляют новый аргумент ключевого слова, такие как второе предложение @timhoffm , кажутся более сложными, чем необходимо. Проблема, которую мы пытаемся решить, - это «ножное ружье», когда неровный ввод автоматически преобразуется в массив одномерных объектов. Проблема возникает только тогда, когда dtype=None передается в array . Требовать от пользователей заменить dtype=None на dtype=<special-value-that-enables-ragged-handling> для сохранения прежнего проблемного поведения - это простое изменение API, которое легко объяснить. Неужели нам действительно нужно что-то большее, чем это?

Я думаю, теперь ясно, что использование dtype=object для обработки ввода неровной формы не является хорошим решением проблемы. В идеале мы бы отделили понятия «массив объектов» от «рваного массива».

Возможно, звучит разумно. Также хорошо отметить, что в NumPy нет настоящей концепции «рваного массива» . Это то, что мы в основном не поддерживаем (ищите "рваный" в документации, в системе отслеживания проблем или в списке рассылки, чтобы подтвердить, хотите ли вы), это то, что поддерживают DyND и XND, и мы только начали говорить о том, чтобы иметь краткое фраза для обсуждения «мы хотим удалить поведение np.array([1, [2, 3]]) которое сбивает пользователей с толку». Следовательно, запекание «рваных массивов» как нового API должно выполняться с особой осторожностью, это абсолютно не то, что мы хотим продвигать. Так что было бы хорошо прояснить это при именовании всего dtype=some_workaround мы можем добавить.

Похоже, что общее мнение объединяется вокруг решения о продлении срока устаревания (возможно, на неопределенный срок) путем разрешения np.array(vals, dtype=special) которое будет вести себя как до NEP 34. Я предпочитаю синглтон, а не строку, поскольку это означает, что использование библиотеки может делать special = getattr(np.special, None) и их код будут работать во всех версиях.

Теперь нам нужно определиться с именем и местом его отображения. Возможно, never_fail или guess_dimensions ? Что касается того, где его выставлять, я бы предпочел не вешать его на np а на какой-то другой внутренний модуль, возможно, с _ чтобы указать, что это действительно частный интерфейс.

Я думаю, что путь вперед состоит в том, чтобы внести поправки в NEP 34, а затем выставить обсуждение в списке рассылки.

Обратите внимание, что также было несколько сообщений о проблемах с использованием операторов (как минимум == и operator.mod ). Вы предлагаете игнорировать это или как-то сохранить это состояние в массиве?

Почти во всех случаях, вероятно, известно, что один из операндов является массивом numpy. Таким образом, вероятно, должно быть возможно получить четко определенное поведение путем ручного преобразования в массив numpy.

Может ли кто-нибудь указать на пример operator.mod ?

Что касается оператора == , тот, который я видел, делал что-то вроде np.array(vals, dtype=object) == vals where vals=[1, [2, 3]] (перефразируя код), поэтому решение состоит в том, чтобы заранее создать массив справа боковая сторона.

Многие из scipy сбоев, похоже, имеют форму np.array([0.25, np.array([0.3])]) , где смешивание скаляров и ndarray с shape==(1,) нарушит обнаружение измерений и создаст массив объектов. xref gh-15075

Может ли кто-нибудь указать на пример operator.mod ?

Видел это в PR Pandas от @jbrockmendel , но я думаю, что с тех пор он изменился (больше не вижу явного operator.mod в комментариях).

Что касается оператора == , тот, который я видел, делал что-то вроде np.array(vals, dtype=object) == vals where vals=[1, [2, 3]] (перефразируя код), поэтому решение состоит в том, чтобы заранее создать массив справа боковая сторона.

В этот момент он становится np.array(vals, dtype=object) == np.array(vals, dtype=object) , так что лучше просто удалите тест :)

@mattip написал:

Я предпочитаю синглтон, а не строку, поскольку это означает, что использование библиотек может выполнять special = getattr (np.special, None), и их код будет работать в разных версиях.

Для меня это звучит нормально.

Теперь нам нужно определиться с именем и местом его отображения. Возможно, never_fail или guess_dimensions ? Что касается того, где его выставлять, я бы предпочел не вешать его на np, а на какой-то другой внутренний модуль, возможно, с _, чтобы указать, что это действительно частный интерфейс.

Мое текущее рабочее имя для этого - legacy_auto_dtype , но, вероятно, есть много других имен, к которым у меня не было бы претензий.

Я не уверен, что это имя должно быть личным. Согласно любому практическому определению _private_ и _public_, это будет _public_ объект. Он предоставляет пользователям возможность сохранить прежнее поведение, например, array(data) , переписав это как array(data, dtype=legacy_auto_dtype) . Я предполагаю, что обновленный NEP объяснит, что именно так следует модифицировать код, чтобы сохранить прежнее поведение (для тех, кто должен это сделать). В этом случае объект определенно не является частным. Фактически, похоже, что это общедоступный объект, который останется в NumPy на неопределенный срок. Но, возможно, мое понимание того, как будет разворачиваться модифицированный NEP 34, неверно.

Согласен с описанием публичного / приватного, которое дает @WarrenWeckesser ; либо он общедоступен, либо не должен использоваться кем-либо за пределами NumPy.

Re name: выберите имя, которое описывает функциональность. Такие вещи, как «наследие», почти никогда не бывают хорошей идеей.

выберите имя, которое описывает функциональность.

auto_object , auto_dtype , auto ?

Подумать немного вслух ...

Что делает этот объект?

В настоящее время, когда NumPy предоставляется объект Python, содержащий подпоследовательности, длина которых не согласуется с обычным массивом nd, NumPy создаст массив с типом данных object , с объектами на первом уровне, где возникает несогласованность формы осталось как объекты Python. Например, array([[1, 2], [1, 2, 3]]) имеет форму (2,) , np.array([[1, 2], [3, [99]]]) имеет форму (2, 2) и т. Д. В NEP 34 мы отказываемся от такого поведения, поэтому пытаемся создать массив с «рваным» вводом в конечном итоге приведет к ошибке, если он явно не включен. Особая ценность, о которой мы говорим, позволяет старому поведению.

Какое хорошее имя для этого ? ragged_as_object ? inconsistent_shapes_as_object ?

В этот момент он становится np.array(vals, dtype=object) == np.array(vals, dtype=object) , так что лучше просто удалите тест :)

Ну, я перефразировал. Фактический тест больше похож на my_func(vals) == vals должен стать my_func(vals) == np.array(vals, dtype=object)

Я предложу расширение NEP 34, чтобы разрешить специальное значение для dtype.

Обратите внимание, что похоже, что scipy не нужен этот часовой для прохождения тестов с scipy / scipy # 11310 и scipy / scipy # 11308

gh-15119 был объединен, что повторно реализовало нэп. Если он не будет отменен, мы можем закрыть эту проблему.

Я собираюсь закрыть это, так как мы не занимались этим до выпуска 1.19. И я, по крайней мере, надеюсь, что причина этого была в том, что обсуждение утихло, поскольку все крупные проекты смогли найти разумные решения созданных им проблем.
Пожалуйста, поправьте меня, если я ошибаюсь, особенно если это по-прежнему подвержено проблемам с pandas, matplotlib и т. Д. Но я предполагаю, что мы слышали об этом во время цикла выпуска кандидатов на выпуск 1.19.x.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги