Как написать функцию, которая принимает срез?

Я хотел бы написать функцию в 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 вы можете различать кортежи и списки.