user200783
user200783

Reputation: 14348

How to write a function which takes a slice?

I would like to write a function in Python which takes a slice as a parameter. Ideally a user would be to be able to call the function as follows:

foo(a:b:c)

Unfortunately, this syntax is not permitted by Python - the use of a:b:c is only allowed within [], not ().

I therefore see three possibilities for my function:

  1. Require the user to use a slice "constructor" (where s_ acts like the version provided by numpy):

    foo(slice(a, b, c))
    foo(s_[a:b:c])
    
  2. Put the logic of my function into a __getitem__ method:

    foo[a:b:c]
    
  3. Give up trying to take a slice and take start, stop and step individually:

    foo(a, b, c)
    

Is there a way to get the original syntax to work? If not, which of the workaround syntaxes would be preferred? Or is there another, better option?

Upvotes: 12

Views: 957

Answers (3)

Dan Getz
Dan Getz

Reputation: 9136

Slices make more sense when they're expressed as a slice of something. So, another alternative is to be more object-oriented: create a temporary object that represents your slice of something, and put your function as a method of it.

For example, if your function is really:

foo(bar, a:b:c)

or

bar.foo(a:b:c)

then you can replace this with:

bar[a:b:c].foo()

If bar[a:b:c] already has a different meaning, then come up with a another name baz and do:

bar.baz[a:b:c].foo()

It's hard to give convincing examples without a real context, because you're trying to name related things with names that make intuitive sense, let you write unambiguous code, and are relatively short.

If you're really just writing a function on its own operating on a slice, then either:

  1. Your function modifies a slice, returning a different slice:

    bar[foo(a:b:c)]
    

    If this is the case, whatever valid syntax you choose is going to look a little confusing. You probably don't want to use slices if you're aiming for a broad audience of Python programmers.

  2. Your function really operates on a slice of the integers, so you can make that explicit with a temporary object:

    the_integers[a:b:c].foo()
    

Upvotes: 2

hpaulj
hpaulj

Reputation: 231385

The use of [a:b:c] is, as you note, a syntax thing. The interpreter raises a syntax error for (a:b:c) right away, before your code has any chance to do something with the values. There isn't a way around this syntax without rewriting the interpreter.

It's worth keeping in mind that the interpreter translates foo[a:b:c] to

foo.__getitem__(slice(a,b,c))

The slice object itself is not very complicated. It just has 3 attributes (start,step,stop) and a method indices. It's the getitem method that makes sense of those values.

np.s_ and other functions/classes in np.lib.index_tricks are good examples of how __getitem__ and slice can be used to extend (or simplify) indexing. For example, these are equivalent:

np.r_[3:4:10j]
np.linspace(3,4,10)

As to the foo(a,b,c) syntax, the very common np.arange() uses it. As does range and xrange. So you, and your users, should be quite familiar with it.

Since the alternatives all end up giving you the start/step/stop trio of values, they are functionally equivalent (in speed). So the choice comes down to user preferences and familiarity.

While your function can't take a:b:c notation directly, it can be written to handle a variety of inputs - a slice, 3 positional arguments, a tuple, a tuple of slices (as from s_), or keyword arguments. And following the basic numpy indexing you could distinguish between tuples and lists.

Upvotes: 0

gg349
gg349

Reputation: 22681

Don't surprise your users.

If you use the slicing syntax consistently with what a developer expects from a slicing syntax, that same developer will expect square brackets operation, i.e. a __getitem__() method.

If instead the returned object is not somehow a slice of the original object, people will be confused if you stick to a __getitem__() solution. Use a function call foo(a, b, c), don't mention slices at all, and optionally assign default values if that makes sense.

Upvotes: 10

Related Questions