Как написать функцию, которая принимает срез?
Я хотел бы написать функцию в Python, которая принимает срез как параметр. В идеале пользователь должен был бы вызвать функцию следующим образом:
foo(a:b:c)
К сожалению, этот синтаксис не разрешен Python - использование a:b:c
разрешено только внутри []
, а не ()
.
Поэтому я вижу три возможности для моей функции:
-
Требовать от пользователя использования "конструктора" среза (где s_
действует как версия, предоставляемая numpy):
foo(slice(a, b, c))
foo(s_[a:b:c])
-
Поместите логику моей функции в метод __getitem__
:
foo[a:b:c]
-
Откажитесь от попыток взять срез и начать, остановить и выполнить индивидуально:
foo(a, b, c)
Есть ли способ заставить исходный синтаксис работать? Если нет, какой из синтаксисов обходных путей будет предпочтительнее? Или есть другой, лучший вариант?
Ответы
Ответ 1
Не удивляйте своих пользователей.
Если вы используете синтаксис разрезания в соответствии с тем, что ожидает разработчик от синтаксиса разреза, тот же самый разработчик будет ожидать операции с квадратными скобками, т.е. метод __getitem__()
.
Если вместо этого возвращенный объект не является каким-то фрагментом исходного объекта, люди будут смущены, если вы будете придерживаться решения __getitem__()
. Используйте вызов функции foo(a, b, c)
, не упоминайте обрезания вообще и необязательно назначайте значения по умолчанию, если это имеет смысл.
Ответ 2
Срезы имеют больше смысла, когда они выражаются как часть чего-то. Таким образом, другая альтернатива - быть более объектно-ориентированной: создать временный объект, представляющий ваш фрагмент чего-либо, и поместить вашу функцию в качестве метода.
Например, если ваша функция действительно:
foo(bar, a:b:c)
или
bar.foo(a:b:c)
то вы можете заменить его на:
bar[a:b:c].foo()
Если bar[a:b:c]
уже имеет другое значение, тогда придумайте другое имя baz
и выполните:
bar.baz[a:b:c].foo()
Трудно дать убедительные примеры без реального контекста, потому что вы пытаетесь назвать связанные вещи с именами, которые делают интуитивный смысл, позволяют писать однозначный код и относительно короткие.
Если вы на самом деле просто пишете функцию, которая работает на срезе, то либо:
-
Ваша функция изменяет срез, возвращая другой срез:
bar[foo(a:b:c)]
Если это так, любой правильный синтаксис, который вы выберете, будет выглядеть немного запутанным. Вероятно, вы не хотите использовать срезы, если вы нацелены на широкую аудиторию программистов на Python.
-
Ваша функция действительно работает на фрагменте целых чисел, поэтому вы можете сделать это явным с временным объектом:
the_integers[a:b:c].foo()
Ответ 3
Использование [a:b:c]
является, как вы заметили, синтаксисом. Интерпретатор сразу же поднимает syntax error
для (a:b:c)
, прежде чем ваш код сможет что-то сделать со значениями. Этот синтаксис не существует, не переписывая интерпретатор.
Следует иметь в виду, что интерпретатор переводит foo[a:b:c]
в
foo.__getitem__(slice(a,b,c))
Сам объект slice
не очень сложный. Он имеет только 3 атрибута (start
, step
, stop
) и метод indices
. Это метод getitem
, который имеет смысл этих значений.
np.s_
и другие функции/классы в np.lib.index_tricks
являются хорошими примерами того, как __getitem__
и slice
могут использоваться для расширения (или упрощения) индексации. Например, они эквивалентны:
np.r_[3:4:10j]
np.linspace(3,4,10)
Что касается синтаксиса foo(a,b,c)
, используется его очень распространенный np.arange()
. Как и range
и xrange
. Поэтому вы и ваши пользователи должны быть хорошо знакомы с ним.
Поскольку все альтернативы дают вам трио значений start/step/stop
, они функционально эквивалентны (по скорости). Таким образом, выбор сводится к предпочтениям пользователей и знакомым.
В то время как ваша функция не может принимать обозначение a:b:c
напрямую, ее можно записать для обработки множества входов - среза, 3 позиционных аргумента, кортежа, кортежа фрагментов (как из s_
), или ключевые слова. И после базовой индексации numpy
вы можете различать кортежи и списки.