13

I'd like to create a slice object from a string; right now the only way seems through a cumbersome hacky eval statement

class getslice:
    def __getitem__(self, idx): return idx[0]
eval("getslice()[%s, 1]" %(":-1"))

thanks in advance.

Edit: Sorry if the original prompt was not clear, the input in this case was ":-1". The point was to parse the string. Ignacio Vazquez-Abrams's response at least solved the problem (and seems to work with reverse indexing as well), but I think my solution above is still more clear if not conceptually clean (and will work correctly if Python ever changes slicing syntax).

6
  • Can you be a bit more clear on what behaviour you're looking for? Commented Mar 25, 2009 at 9:30
  • -1: Why would you make a stand-alone slice object? Slices are first-class pieces of syntax? Without any background on why you'd do this, the question is very odd. Commented Mar 25, 2009 at 9:49
  • Jaffe: updated question. Lott: I'm doing a bit of language work, and would like to use Python slices as well. The actual object is pretty useful: it has a indices(len) function which will give a (start, stop, increment) given an array length Commented Mar 25, 2009 at 18:50
  • @gatoatigrado: Yes, a Slice() object is useful. What was wrong with the built-in slice function? Was the issue that it didn't parse the source representation properly? Commented Mar 25, 2009 at 19:43
  • 1
    The built-in slice function takes three arguments - start, stop, stride. I want to parse a string, e.g. "0:1" --> slice(0, 1, None); ":-1" --> slice(None, -1, None), "3::2" --> slice(3, None, 2). Commented Mar 28, 2009 at 21:00

11 Answers 11

7

slice(*map(lambda x: int(x.strip()) if x.strip() else None, mystring.split(':')))

for single arg slices '-1' or '1' so when mystring.split(':')==1 you just call int(x)

On request, took it out of comment section.

Sign up to request clarification or add additional context in comments.

1 Comment

A more pythonic way is without using map and just making a generator like this: slice(*(int(part) if part else None for part in sslice.split(':'))) and if you want to support spaces in fields, then either slice(*(int(part.strip()) if part.strip() else None for part in sslice.split(':'))) or slice(*(int(spart) if spart else None for part in sslice.split(':') for spart in [part.strip()])).
3
slice(*[{True: lambda n: None, False: int}[x == ''](x) for x in (mystring.split(':') + ['', '', ''])[:3]])

6 Comments

this is kinda wrong, e. g. '1:1:1:1' is not a correct slice string and '1 : 1 : 1 ' is. Should more something like this: slice(*map(lambda x: int(x.strip()) if x.strip() else None, mystring.split(':')))
pprzemek, your comment contains the best answer in my opinion, why don't you make a real answer out of it?
@pprzemek Hi, DanielJung is right, you really should convert your comment to an answer.
ugly may be but that seems the chosen answer seems the correct. if you try the chosen answer on the string '-1' you get slice(-1, None, None) but if you try pprzemeks's answer you get slice(None, -1, None) which is not right
This code does not handle correctly case with since number, since slice(2) == slice(None, 2, None), not slice(2, None, None).
|
3

If you want a slice object, why don't you just instantiate one?

s = slice(start, stop, step)

What are you meaning by "creating it from a string"?

Comments

2

I end up here because I wanted my script to accept a python-like splice argument and render it into a list of integers, I did it with a function that seems like it answers the OP's question:

# create a slice object from a string
def get_slice_obj(slicearg):
    slice_ints = tuple([ int(n) for n in slicearg.split(':') ])
    return apply(slice, slice_ints)

def ints_from_slicearg(slicearg):
    slice_obj = get_slice_obj(slicearg)
    return(range(slice_obj.start or 0, slice_obj.stop or -1, slice_obj.step or 1))

for t in ['1', '1:3', '4:9:2']:
    print t, "=>", ints_from_slicearg(t)

Output:

1 => [0]
1:3 => [1, 2]
4:9:2 => [4, 6, 8]

Comments

1

Here's another method (just a consolidation of the others posted here):

def make_slice(expr):
    def to_piece(s):
        return s and int(s) or None
    pieces = map(to_piece, expr.split(':'))
    if len(pieces) == 1:
        return slice(pieces[0], pieces[0] + 1)
    else:
        return slice(*pieces)

Example usages:

In [1]: make_slice(':')
Out[1]: slice(None, None, None)

In [2]: make_slice(':-2')
Out[2]: slice(None, -2, None)

In [3]: x = [1, 2, 3]

In [4]: x[make_slice('::-1')]
Out[4]: [3, 2, 1]

Comments

1

The one-liner from Ignacio Vazquez-Abrams is short but hardly readable and handles a single number inconsistently with slice. This tries to parse it in a cleaner way.

def parse_slice(value):
    """
    Parses a `slice()` from string, like `start:stop:step`.
    """
    if value:
        parts = value.split(':')
        if len(parts) == 1:
            # slice(stop)
            parts = [None, parts[0]]
        # else: slice(start, stop[, step])
    else:
        # slice()
        parts = []
    return slice(*[int(p) if p else None for p in parts])
# unit tests:
try:
    assert parse_slice('')
    assert False, 'It should raise TypeError'
except TypeError:
    pass
assert parse_slice('2') == slice(2)
assert parse_slice('2:3') == slice(2, 3)
assert parse_slice(':3') == slice(None, 3)
assert parse_slice(':') == slice(None, None)
assert parse_slice('2:') == slice(2, None)
assert parse_slice('2:3:4') == slice(2, 3, 4)
assert parse_slice(':3:4') == slice(None, 3, 4)
assert parse_slice('2::4') == slice(2, None, 4)
assert parse_slice('2:3:') == slice(2, 3, None)
assert parse_slice('::4') == slice(None, None, 4)
assert parse_slice('2::') == slice(2, None, None)
assert parse_slice('::') == slice(None, None, None)
assert parse_slice('-12:-13:-14') == slice(-12, -13, -14)
assert parse_slice('2:3:-4') == slice(2, 3, -4)
try:
    parse_slice('1:2:3:4')
    assert False, 'It should raise TypeError'
except TypeError:
    pass

1 Comment

I think this code handles a single number incorrectly. I would postulate that with mylist = [0, 1, 2, 3, 4, 5, 6, 7], mylist[parse_slice("5")] == mylist[5], and that is not the case.
0

Based on @pprzemak drafted the following function for elaborate parsing:

def parse_slice(v: Text):
    """
    Parses text like python "slice" expression (ie ``-10::2``).

    :param v:
        the slice expression or a lone integer
    :return:
        - None if input is None/empty
        - a ``slice()`` instance (even if input a lone numbrt)
    :raise ValueError:
        input non-empty but invalid syntax
    """
    orig_v = v
    v = v and v.strip()
    if not v:
        return

    try:
        if ':' not in v:
            ## A lone number given.
            v = int(v)
            return slice(v, v + 1)

        return slice(*map(lambda x: int(x.strip()) if x.strip() else None,
                          v.split(':')))
    except Exception:
        pass

    ## An alternative is to return `slice(None)` here.
    raise trt.TraitError("Syntax-error in '%s' slice!" % orig_v)

Comments

0

How 'bout this (for simple non empty slice intervals) :

sliceStr = "3:8"
mySlice = slice( *map(int, sliceStr.split(':') ) )

Comments

0

I just needed to do this 12 years later so here's my answer using regex :)

import re
def parse_slice(string: str) -> slice:
    """
    Parse a string representation of a slice and return a slice object
    """
    # Matches one required colon, one optional colon, and up to three
    # positive or negative numbers between them
    match = re.match(r"^(-?[\d]*):(-?[\d]*)[:]?(-?[\d]*)$", string)
    if match:
        args = tuple(map(lambda s: int(s) if s else None, match.group(1, 2, 3)))
        return slice(*args)
    raise ValueError("Could not parse slice")

Comments

-1

A slice object is usually created using subscript notation, this notation uses slice() internally, as stated on the slice() documentation. What you want to do is:

your_string[start:end]

From the python tutorial:

Strings can be subscripted (indexed); like in C, the first character of a string has subscript (index) 0. There is no separate character type; a character is simply a string of size one. Like in Icon, substrings can be specified with the slice notation: two indices separated by a colon.

>>> word = 'Help' + 'A' 
>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp'

Slice indices have useful defaults; an omitted first index defaults to zero, an omitted second index defaults to the size of the string being sliced.

>>> word[:2]    # The first two characters
'He'
>>> word[2:]    # Everything except the first two characters
'lpA'

4 Comments

sorry, that's not what I was asking, I want to parse the slice, e.g. "-3::2" from a string, and return a slice object [see original question].
It's not clear from your question that that's what you were asking. I'm sorry I can't help you anymore, what I recommend you though is to read the Zen of Python and reconsider if what you are trying to do doesn't go against it, if it does it's probably a bad idea.
sure, I think most of my code meets that (thanks for the ref). I'm sorry I don't know how to clarify more; did you read the revised question (and comments below)? regards, Nicholas
"It's not clear from your question that that's what you were asking." - could you explain more? What's not clear, and how can I improve it?
-1

My solution to parse numpy style advanced indexing from string: my gist. Although this is an old post, it's the only one I can find on this topic. Hope it helps.

Upon suggestion, I paste the code here, which could be a little bit long ... The code usage is (assuming a is an array-like object): a[parse_slice('1')] gives a[1]; a[parse_slice('2:,-1')] gives a[2:,-1]; etc.

import re

SLICE_TEMPLATES = [
    ('s', r'(?P<i>[+-]?\d+)'),
    ('sp', r'\((?P<i>[+-]?\d+)\)'),
    ('a', r'::?'),
    ('ri-', r'(?P<i>[+-]?\d+)::?'),
    ('ri-k', r'(?P<i>[+-]?\d+)::(?P<k>[+-]?\d+)'),
    ('r-j', r':(?P<j>[+-]?\d+):?'),
    ('r-jk', r':(?P<j>[+-]?\d+):(?P<k>[+-]?\d+)'),
    ('rij', r'(?P<i>[+-]?\d+):(?P<j>[+-]?\d+):?'),
    ('rijk', r'(?P<i>[+-]?\d+):(?P<j>[+-]?\d+):(?P<k>[+-]?\d+)'),
    ('r--k', r'::(?P<k>[+-]?\d+)'),
    ('l', r'\.\.\.'),
    ('eb', r'\[(?P<e>[+-]?\d+(,[+-]?\d+)*,?)\]'),
    ('ep', r'\((?P<e>[+-]?\d+(,[+-]?\d+)+,?)\)'),
    ('ep1', r'\((?P<e>[+-]?\d+,)\)'),
]
SLICE_TEMPLATES = [(k, re.compile(v)) for k, v in SLICE_TEMPLATES]


def tokenize_slice_groups(string):
    # tokenize groups
    groups = []
    sbuf = []
    expecting = {'(': ')', '[': ']'}
    pbbuf = []
    LEGAL_CHARS = '0123456789()[]+-:.'
    WHITESPACE_CHARS = ' \t'

    for c in string:
        if c in WHITESPACE_CHARS:
            pass
        elif c == ',':
            if len(pbbuf) not in (0, 2):
                sbuf.append(c)
            else:
                groups.append(''.join(sbuf))
                sbuf.clear()
                pbbuf.clear()
        elif c in LEGAL_CHARS:
            sbuf.append(c)
            if c in '([':
                if pbbuf:
                    raise ValueError('too many brackets in axis {}'.format(
                        len(groups)))
                pbbuf.append(c)
            elif c in ')]':
                if not pbbuf:
                    raise ValueError('brackets not match in axis {}'.format(
                        len(groups)))
                if c != expecting[pbbuf[0]]:
                    raise ValueError('brackets not match in axis {}'.format(
                        len(groups)))
                pbbuf.append(c)
        else:
            raise ValueError('illegal char `{}\''.format(c))
    groups.append(''.join(sbuf))
    return groups


def parse_slice_group(string):
    for name, tem in SLICE_TEMPLATES:
        matched = tem.fullmatch(string)
        if matched:
            if name[0] == 's':
                return int(matched.group('i'))
            if name[0] == 'a':
                return slice(None, None, None)
            if name[0] == 'r':
                i, j, k = None, None, None
                if 'i' in name:
                    i = int(matched.group('i'))
                if 'j' in name:
                    j = int(matched.group('j'))
                if 'k' in name:
                    k = int(matched.group('k'))
                return slice(i, j, k)
            if name[0] == 'l':
                return ...
            # if name[0] == 'e'
            return list(map(int, filter(None, matched.group('e').split(','))))
    raise ValueError('illegal group "{}"'.format(string))


def parse_slice(string):
    groups = tokenize_slice_groups(string)
    if groups == ['']:
        raise ValueError('index must not be empty')
    if groups and groups[-1] == '':
        del groups[-1]
    index = tuple(map(parse_slice_group, groups))
    if index.count(...) > 1:
        raise ValueError('ellipsis may occur at most once')
    return index

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.