问题
The title explains itself, how to get 2 out of the object
slice(0,2)
The documentation is somewhat confusing, or it is the wrong one
https://docs.python.org/2/c-api/slice.html
In particular I don't understand what is the meaning of the output of
slice(0,2).indices(0) # (0, 0, 1)
slice(0,2).indices(10 ** 10) # (0, 2, 1)
One possible workaround is to slice a list with the slice object
a = [1,2,3,4,5]
len(a[slice(0,2)]) # 2
But this will fail for an arbitrary large slice.
Thanks, I couldn't find an answer In other posts.
回答1:
There is no complete answer for this. slice
doesn't give you a length because the length of the result is always dependent on the size of the sequence being sliced, a short sequence (including an empty sequence) will produce fewer items, and if the slice
is unbounded, then the length will grow in tandem with the length of the sequence; a slice
might just go "to end of sequence" by having a start
or stop
of None
.
For a quick and easy way to compute the length for a sequence of a known length, you just combine .indices
with Py3's range
(or xrange
in Py2, though xrange
has limitations on values that Py3 range
does not). slice.indices gives you the concrete start, stop and stride values derived when a slice applies to a sequence of a given length, it's basically the values you'd fill in in a C-style for
loop that traverses the same indices as the slice
:
for (ssize_t i = start; i < stop; i += stride)
So to calculate the length of a slice
when applied to a sequence with 1000 elements, you'd do:
>>> len(range(*slice(0, 2).indices(1000)))
2
>>> len(range(*slice(10, None, 3).indices(1000)))
330
If you're on Python 2, and your values might exceed what xrange
can handle (it's limited to bounds and total length equal to what a ssize_t
can hold), you can just do the calculation by hand:
def slice_len_for(slc, seqlen):
start, stop, step = slc.indices(seqlen)
return max(0, (stop - start + (step - (1 if step > 0 else -1))) // step)
>>> slice_len_for(slice(10, None, 3), 1000)
330
Update: Unfortunately, slice.indices
itself won't accept a len
for the sequence beyond what a long
can hold, so this doesn't gain you anything over using xrange
in Py2. Left in place for those interested, but the workaround doesn't workaround anything unless you also perform the work slice does to convert negative values and None to concrete values based on the sequence length. Sigh.
回答2:
So it looks like slice.indices(n)
returns the arguments to be given to range
, to get the item indices which should be reflected in the slice of a sequence of length n
(although it's not documented edit: as @ShadowRanger pointed out, it is indeed documented). So the following lines evaluate to the same values:
# get some list to work on
my_list = list(range(100))
# slice syntax
print(my_list[1:15:3])
# regular item access
print(my_list[slice(1,15,3)])
# reinvent list slicing
print([my_list[i] for i in range(*slice(1,15,3).indices(len(my_list)))])
As you see, the resulting list's length is the same as the length of range(*slice(1,15,3).indices(len(my_list)))
, which depends on the slice
object itself, and the length of the sequence to be sliced. That's why len(range(*slice.indices(n)))
will give you the right answer in Python 3. (the range object is a generator, which fortunately has the __len__
function defined, so it can give you the item count, without the need to enumerate and count them.)
If you work with large numbers in python 2, you can replicate the calculation as @ShadowRanger suggests.
The original implementation of range.__len__
is the following:
/* Return number of items in range (lo, hi, step). step != 0
* required. The result always fits in an unsigned long.
*/
static unsigned long
get_len_of_range(long lo, long hi, long step)
{
/* -------------------------------------------------------------
If step > 0 and lo >= hi, or step < 0 and lo <= hi, the range is empty.
Else for step > 0, if n values are in the range, the last one is
lo + (n-1)*step, which must be <= hi-1. Rearranging,
n <= (hi - lo - 1)/step + 1, so taking the floor of the RHS gives
the proper value. Since lo < hi in this case, hi-lo-1 >= 0, so
the RHS is non-negative and so truncation is the same as the
floor. Letting M be the largest positive long, the worst case
for the RHS numerator is hi=M, lo=-M-1, and then
hi-lo-1 = M-(-M-1)-1 = 2*M. Therefore unsigned long has enough
precision to compute the RHS exactly. The analysis for step < 0
is similar.
---------------------------------------------------------------*/
assert(step != 0);
if (step > 0 && lo < hi)
return 1UL + (hi - 1UL - lo) / step;
else if (step < 0 && lo > hi)
return 1UL + (lo - 1UL - hi) / (0UL - step);
else
return 0UL;
}
And slice.indices
:
int
PySlice_GetIndices(PySliceObject *r, Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
/* XXX support long ints */
if (r->step == Py_None) {
*step = 1;
} else {
if (!PyInt_Check(r->step) && !PyLong_Check(r->step)) return -1;
*step = PyInt_AsSsize_t(r->step);
}
if (r->start == Py_None) {
*start = *step < 0 ? length-1 : 0;
} else {
if (!PyInt_Check(r->start) && !PyLong_Check(r->step)) return -1;
*start = PyInt_AsSsize_t(r->start);
if (*start < 0) *start += length;
}
if (r->stop == Py_None) {
*stop = *step < 0 ? -1 : length;
} else {
if (!PyInt_Check(r->stop) && !PyLong_Check(r->step)) return -1;
*stop = PyInt_AsSsize_t(r->stop);
if (*stop < 0) *stop += length;
}
if (*stop > length) return -1;
if (*start >= length) return -1;
if (*step == 0) return -1;
return 0;
}
The sources are from svn
回答3:
>>> slice(0,2).__getattribute__('stop')
2
>>> slice(0,2).__getattribute__('start')
0
来源:https://stackoverflow.com/questions/36188429/retrieve-length-of-slice-from-slice-object-in-python