Reputation: 14348
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:
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])
Put the logic of my function into a __getitem__
method:
foo[a:b:c]
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
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:
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.
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
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
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