py/objslice: Add support for indices() method on slice objects.

Instances of the slice class are passed to __getitem__() on objects when
the user indexes them with a slice.  In practice the majority of the time
(other than passing it on untouched) is to work out what the slice means in
the context of an array dimension of a particular length.  Since Python 2.3
there has been a method on the slice class, indices(), that takes a
dimension length and returns the real start, stop and step, accounting for
missing or negative values in the slice spec.  This commit implements such
a indices() method on the slice class.

It is configurable at compile-time via MICROPY_PY_BUILTINS_SLICE_INDICES,
disabled by default, enabled on unix, stm32 and esp32 ports.

This commit also adds new tests for slice indices and for slicing unicode
strings.
pull/5469/head
Nicko van Someren 2019-11-16 17:07:11 -07:00 zatwierdzone przez Damien George
rodzic 007a704d82
commit 4c93955b7b
9 zmienionych plików z 160 dodań i 74 usunięć

Wyświetl plik

@ -73,6 +73,7 @@
#define MICROPY_PY_BUILTINS_SET (1)
#define MICROPY_PY_BUILTINS_SLICE (1)
#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1)
#define MICROPY_PY_BUILTINS_SLICE_INDICES (1)
#define MICROPY_PY_BUILTINS_FROZENSET (1)
#define MICROPY_PY_BUILTINS_PROPERTY (1)
#define MICROPY_PY_BUILTINS_RANGE_ATTRS (1)

Wyświetl plik

@ -97,6 +97,7 @@
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
#define MICROPY_PY_BUILTINS_FROZENSET (1)
#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1)
#define MICROPY_PY_BUILTINS_SLICE_INDICES (1)
#define MICROPY_PY_BUILTINS_ROUND_INT (1)
#define MICROPY_PY_ALL_SPECIAL_METHODS (1)
#define MICROPY_PY_BUILTINS_COMPILE (MICROPY_ENABLE_COMPILER)

Wyświetl plik

@ -90,6 +90,7 @@
#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1)
#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1)
#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1)
#define MICROPY_PY_BUILTINS_SLICE_INDICES (1)
#define MICROPY_PY_SYS_EXIT (1)
#define MICROPY_PY_SYS_ATEXIT (1)
#if MICROPY_PY_SYS_SETTRACE

Wyświetl plik

@ -894,6 +894,11 @@ typedef double mp_float_t;
#define MICROPY_PY_BUILTINS_SLICE_ATTRS (0)
#endif
// Whether to support the .indices(len) method on slice objects
#ifndef MICROPY_PY_BUILTINS_SLICE_INDICES
#define MICROPY_PY_BUILTINS_SLICE_INDICES (0)
#endif
// Whether to support frozenset object
#ifndef MICROPY_PY_BUILTINS_FROZENSET
#define MICROPY_PY_BUILTINS_FROZENSET (0)

Wyświetl plik

@ -778,8 +778,16 @@ static inline mp_map_t *mp_obj_dict_get_map(mp_obj_t dict) {
// set
void mp_obj_set_store(mp_obj_t self_in, mp_obj_t item);
// slice indexes resolved to particular sequence
typedef struct {
mp_int_t start;
mp_int_t stop;
mp_int_t step;
} mp_bound_slice_t;
// slice
void mp_obj_slice_get(mp_obj_t self_in, mp_obj_t *start, mp_obj_t *stop, mp_obj_t *step);
void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result);
// functions
@ -836,13 +844,6 @@ const mp_obj_t *mp_obj_property_get(mp_obj_t self_in);
// sequence helpers
// slice indexes resolved to particular sequence
typedef struct {
mp_uint_t start;
mp_uint_t stop;
mp_int_t step;
} mp_bound_slice_t;
void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times, void *dest);
#if MICROPY_PY_BUILTINS_SLICE
bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes);

Wyświetl plik

@ -27,7 +27,7 @@
#include <stdlib.h>
#include <assert.h>
#include "py/obj.h"
#include "py/runtime.h"
/******************************************************************************/
/* slice object */
@ -53,6 +53,22 @@ STATIC void slice_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t
mp_print_str(print, ")");
}
#if MICROPY_PY_BUILTINS_SLICE_INDICES
STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) {
mp_int_t length = mp_obj_int_get_checked(length_obj);
mp_bound_slice_t bound_indices;
mp_obj_slice_indices(self_in, length, &bound_indices);
mp_obj_t results[3] = {
MP_OBJ_NEW_SMALL_INT(bound_indices.start),
MP_OBJ_NEW_SMALL_INT(bound_indices.stop),
MP_OBJ_NEW_SMALL_INT(bound_indices.step),
};
return mp_obj_new_tuple(3, results);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices);
#endif
#if MICROPY_PY_BUILTINS_SLICE_ATTRS
STATIC void slice_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
@ -60,22 +76,37 @@ STATIC void slice_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
return;
}
mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in);
if (attr == MP_QSTR_start) {
dest[0] = self->start;
} else if (attr == MP_QSTR_stop) {
dest[0] = self->stop;
} else if (attr == MP_QSTR_step) {
dest[0] = self->step;
#if MICROPY_PY_BUILTINS_SLICE_INDICES
} else if (attr == MP_QSTR_indices) {
dest[0] = MP_OBJ_FROM_PTR(&slice_indices_obj);
dest[1] = self_in;
#endif
}
}
#endif
#if MICROPY_PY_BUILTINS_SLICE_INDICES && !MICROPY_PY_BUILTINS_SLICE_ATTRS
STATIC const mp_rom_map_elem_t slice_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_indices), MP_ROM_PTR(&slice_indices_obj) },
};
STATIC MP_DEFINE_CONST_DICT(slice_locals_dict, slice_locals_dict_table);
#endif
const mp_obj_type_t mp_type_slice = {
{ &mp_type_type },
.name = MP_QSTR_slice,
.print = slice_print,
#if MICROPY_PY_BUILTINS_SLICE_ATTRS
.attr = slice_attr,
#elif MICROPY_PY_BUILTINS_SLICE_INDICES
.locals_dict = (mp_obj_dict_t*)&slice_locals_dict,
#endif
};
@ -96,4 +127,69 @@ void mp_obj_slice_get(mp_obj_t self_in, mp_obj_t *start, mp_obj_t *stop, mp_obj_
*step = self->step;
}
// Return the real index and step values for a slice when applied to a sequence of
// the given length, resolving missing components, negative values and values off
// the end of the sequence.
void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) {
mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in);
mp_int_t start, stop, step;
if (self->step == mp_const_none) {
step = 1;
} else {
step = mp_obj_get_int(self->step);
if (step == 0) {
mp_raise_ValueError("slice step cannot be zero");
}
}
if (step > 0) {
// Positive step
if (self->start == mp_const_none) {
start = 0;
} else {
start = mp_obj_get_int(self->start);
if (start < 0) {
start += length;
}
start = MIN(length, MAX(start, 0));
}
if (self->stop == mp_const_none) {
stop = length;
} else {
stop = mp_obj_get_int(self->stop);
if (stop < 0) {
stop += length;
}
stop = MIN(length, MAX(stop, 0));
}
} else {
// Negative step
if (self->start == mp_const_none) {
start = length - 1;
} else {
start = mp_obj_get_int(self->start);
if (start < 0) {
start += length;
}
start = MIN(length - 1, MAX(start, -1));
}
if (self->stop == mp_const_none) {
stop = -1;
} else {
stop = mp_obj_get_int(self->stop);
if (stop < 0) {
stop += length;
}
stop = MIN(length - 1, MAX(stop, -1));
}
}
result->start = start;
result->stop = stop;
result->step = step;
}
#endif

Wyświetl plik

@ -46,78 +46,20 @@ void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times
#if MICROPY_PY_BUILTINS_SLICE
bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) {
mp_obj_t ostart, ostop, ostep;
mp_int_t start, stop;
mp_obj_slice_get(slice, &ostart, &ostop, &ostep);
mp_obj_slice_indices(slice, len, indexes);
if (ostep != mp_const_none && ostep != MP_OBJ_NEW_SMALL_INT(1)) {
indexes->step = mp_obj_get_int(ostep);
if (indexes->step == 0) {
mp_raise_ValueError("slice step cannot be zero");
}
} else {
indexes->step = 1;
}
if (ostart == mp_const_none) {
if (indexes->step > 0) {
start = 0;
} else {
start = len - 1;
}
} else {
start = mp_obj_get_int(ostart);
}
if (ostop == mp_const_none) {
if (indexes->step > 0) {
stop = len;
} else {
stop = 0;
}
} else {
stop = mp_obj_get_int(ostop);
if (stop >= 0 && indexes->step < 0) {
stop += 1;
}
}
// Unlike subscription, out-of-bounds slice indexes are never error
if (start < 0) {
start = len + start;
if (start < 0) {
if (indexes->step < 0) {
start = -1;
} else {
start = 0;
}
}
} else if (indexes->step > 0 && (mp_uint_t)start > len) {
start = len;
} else if (indexes->step < 0 && (mp_uint_t)start >= len) {
start = len - 1;
}
if (stop < 0) {
stop = len + stop;
if (stop < 0) {
stop = -1;
}
if (indexes->step < 0) {
stop += 1;
}
} else if ((mp_uint_t)stop > len) {
stop = len;
// If the index is negative then stop points to the last item, not after it
if (indexes->step < 0) {
indexes->stop++;
}
// CPython returns empty sequence in such case, or point for assignment is at start
if (indexes->step > 0 && start > stop) {
stop = start;
} else if (indexes->step < 0 && start < stop) {
stop = start + 1;
if (indexes->step > 0 && indexes->start > indexes->stop) {
indexes->stop = indexes->start;
} else if (indexes->step < 0 && indexes->start < indexes->stop) {
indexes->stop = indexes->start + 1;
}
indexes->start = start;
indexes->stop = stop;
return indexes->step == 1;
}

Wyświetl plik

@ -0,0 +1,27 @@
# Test builtin slice indices resolution
# A class that returns an item key
class A:
def __getitem__(self, idx):
return idx
# Make sure that we have slices and .indices()
try:
A()[2:5].indices(10)
except:
print("SKIP")
raise SystemExit
print(A()[:].indices(10))
print(A()[2:].indices(10))
print(A()[:7].indices(10))
print(A()[2:7].indices(10))
print(A()[2:7:2].indices(10))
print(A()[2:7:-2].indices(10))
print(A()[7:2:2].indices(10))
print(A()[7:2:-2].indices(10))
print(A()[2:7:2].indices(5))
print(A()[2:7:-2].indices(5))
print(A()[7:2:2].indices(5))
print(A()[7:2:-2].indices(5))

Wyświetl plik

@ -0,0 +1,12 @@
# Test slicing of Unicode strings
s = "Привет"
print(s[:])
print(s[2:])
print(s[:5])
print(s[2:5])
print(s[2:5:1])
print(s[2:10])
print(s[-3:10])
print(s[-4:10])