gibson
gibson

Reputation: 563

Python string slicing, special case if end of string included?

I'm new to Python, and I find the slice behaviour somewhat confusing.

If I do

test = 'abcdefgh'

for i in range(7):
    print test[-(8-i):-(6-i)]
    print i

the last iteration will misbehave. Since slicing [start:end] doesn't include end, it seems to me like I'd need to handle slices like this with a special case if the last character is in the range I want.

Did I miss something?

Upvotes: 2

Views: 2784

Answers (5)

6502
6502

Reputation: 114481

This is indeed an unfortunate consequence of the slice semantic.

The problem is that to mean "count from the end" you need to pass a negative number, and therefore you cannot ask "count 0 from the end" because -0 == 0 is not a negative number.

For counting 0 chars from the end you need to special case the issue with an if or other conditional trickery, because passing 0 means "0 elements from the start".

To have it working for these cases the semantic would have to be that -4 means counting 3 from the end (thus leaving room for -1 to mean "0 from the end"), but this would have been counter intuitive.

Being able to say x[-n:] to mean the last n chars of a string is a better compromise even if this doesn't work for n == 0 where instead of the empty string you get the full string.

Upvotes: 0

Carlos Hanson
Carlos Hanson

Reputation: 98

In the Python Tutorial (http://docs.python.org/2/tutorial/introduction.html), slice notation is defined as two indices separated by a colon.

In the last iteration of your example, the slice notation is [-2:0]. -2 is the index for the second to last character of the string, and 0 is the index for the first letter in the string. It does not make sense to take a slice from the second to last character to the first character.

If you want to go from the second to last character to the last character, simply eliminate the second index: [-2:]. That says, start at the second to last character and go to the end. Or be explicit and say [-2:len(test)].

For this example, I would suggest something like the following:

test = 'abcdefgh'
for i in range(7):
    start = -(8-i)
    end = -(6-i)
    # test your end condition
    if end == 0:
        end = None
    print test[start:end]
    print i

Upvotes: 0

jbh
jbh

Reputation: 1153

The issue here is -0 just is 0, so you're attempting to grab up to the first character of the string

so for the case of i = 6 you get

test[-2:0] = ''

a better way of handling this is look ahead

for i in range(len(test)-1):
     print test[i:i+2]

for indexing from the end to work the correct syntax would leave out the 0

test[-2:] = 'gh'

Upvotes: 1

MatthieuBizien
MatthieuBizien

Reputation: 1725

You can't start at -1 and go to +1. -1 is the end, 1 the secund item. You can do

for i in range(7):
   ....:         print test[i:(2+i)]
   ....:     
ab
bc
cd
de
ef
fg
gh

Upvotes: 1

mhlester
mhlester

Reputation: 23221

If you add another couple prints, you can see what's happening:

test = 'abcdefgh'

for i in range(7):
    print -(8-i), -(6-i)
    print test[-(8-i):-(6-i)]
    print i

Outputs:

-8 -6
ab
0
-7 -5
bc
1
-6 -4
cd
2
-5 -3
de
3
-4 -2
ef
4
-3 -1
fg
5
-2 0

All your ranges are negative, until the last, when it's 0

Adding or None to the end range will to avoid the 0 and act as if you didn't pass it in the first place:

for i in range(7):
    print test[-(8-i):(-(6-i) or None)]
    print i

Which outputs:

ab
0
bc
1
cd
2
de
3
ef
4
fg
5
gh
6

The way the or operator works, if the first argument is "falsish", the second argument is used, in this case None

Upvotes: 1

Related Questions