當前位置:
首頁 > 知識 > Python 的 Sequence 切片下標問題

Python 的 Sequence 切片下標問題

(點擊

上方藍字

,快速關注我們)




來源:Lin_R


segmentfault.com/a/1190000009008550


如有好文章投稿,請點擊 → 這裡了解詳情




前言



在python中, 切片是一個經常會使用到的語法, 不管是元組, 列表還是字元串, 一般語法就是:





sequence[ilow:ihigh:step] # ihigh,step 可為空; 為了簡短易懂, 暫時排除step的用法考慮




先來簡單示範下用法





sequence

=

[

1

,

2

,

3

,

4

,

5

]


sequence

[

ilow

:

ihigh

]

# 從ilow開始到ihigh-1結束


sequence

[

ilow

:

]

# 從ilow開始直到末尾


sequence

[

:

ihigh

]

# 從頭部開始直到ihigh結束


sequence

[

:

]

# 複製整個列表




語法很簡潔, 也很容易理解, 這種語法在我們日常使用中 是簡單又好用, 但我相信在我們使用這種切片語法時, 都會習慣性謹遵一些規則:






  1. ilow, ihigh均小於 sequece的長度



  2. ilow < ihigh




因為在大部分情況下, 只有遵循上面的規則, 才能得到我們預期的結果! 可是如果我不遵循呢? 切片會怎樣?




不管我們在使用元組, 列表還是字元串, 當我們想取中一個元素時, 我們會用到如下語法:





sequence

=

[

1

,

2

,

3

,

4

,

5

]


print

sequence

[

1

]

# 輸出2


print

sequence

[

2

]

# 輸出3




上面出現的 1,2 我們姑且稱之為下標, 不管是元組, 列表還是字元串, 我們都能通過下標來取出對應的值, 但是如果下標超過對象的長度, 那麼將觸發索引異常(IndexError)





sequence

=

[

1

,

2

,

3

,

4

,

5

]


print

sequence

[

15

]



### 輸出 ###


Traceback

(

most recent call

last

)

:


File

"test.py"

,

line

2

,

in

<

module

>


print

a

[

20

]


IndexError

:

list index out of

range




那麼對於切片呢? 兩種語法很相似, 假設我 ilow 和 ihigh分別是10和20, 那麼結果是怎樣呢




情景重現





# version: python2.7



a

=

[

1

,

2

,

3

,

5

]


print

a

[

10

:

20

]

# 結果會報異常嗎?




看到10和20, 完全超出了序列a的長度, 由於前面的代碼, 或者以前的經驗, 我們總會覺得這樣肯定也會導致一個IndexError,那我們開終端來試驗下:





>>>

a

=

[

1

,

2

,

3

,

5

]


>>>

print

a

[

10

:

20

]


[]




結果居然是: [], 這感覺有點意思.是只有列表才會這麼, 字元串呢, 元組呢





>>>

s

=

"23123123123"


>>>

print

s

[

400

:

2000

]


""


>>>

t

=

(

1

,

2

,

3

,

4

)


>>>

print

t

[

200

:

1000

]


()




結果都和列表的類似, 返回屬於各自的空結果.




看到結果的我們眼淚掉下來, 不是返回一個IndexError, 而是直接返回空, 這讓我們不禁想到, 其實語法相似, 背後的東西肯定還是不同的, 那我們下面一起來嘗試去解釋下這結果吧




原理分析




在揭開之前, 咱們要先搞清楚, python是怎樣處理這個切片的, 可以通過dis模塊來協助:





############# 切片 ################


[

root

@

iZ23pynfq19Z

~

]

# cat test.py


a

=

[

11

,

2

,

3

,

4

]


print

a

[

20

:

30

]



#結果:


[

root

@

iZ23pynfq19Z

~

]

# python -m dis test.py


1

0

LOAD

_

CONST

0

(

11

)


3

LOAD

_

CONST

1

(

2

)


6

LOAD

_

CONST

2

(

3

)


9

LOAD

_

CONST

3

(

4

)


12

BUILD

_

LIST

4


15

STORE

_

NAME

0

(

a

)



2

18

LOAD

_

NAME

0

(

a

)


21

LOAD

_

CONST

4

(

20

)


24

LOAD

_

CONST

5

(

30

)


27

SLICE

+

3


28

PRINT

_

ITEM


29

PRINT

_

NEWLINE


30

LOAD

_

CONST

6

(

None

)


33

RETURN_VALUE



############# 單下標取值 ################


[

root

@

gitlab

~

]

# cat test2.py


a

=

[

11

,

2

,

3

,

4

]


print

a

[

20

]



#結果:


[

root

@

gitlab

~

]

# python -m dis test2.py


1

0

LOAD

_

CONST

0

(

11

)


3

LOAD

_

CONST

1

(

2

)


6

LOAD

_

CONST

2

(

3

)


9

LOAD

_

CONST

3

(

4

)


12

BUILD

_

LIST

4


15

STORE

_

NAME

0

(

a

)



2

18

LOAD

_

NAME

0

(

a

)


21

LOAD

_

CONST

4

(

20

)


24

BINARY

_

SUBSCR


25

PRINT

_

ITEM


26

PRINT

_

NEWLINE


27

LOAD

_

CONST

5

(

None

)


30

RETURN

_

VALUE




在這簡單介紹下dis模塊, 有經驗的老司機都知道, python在解釋腳本時, 也是存在一個編譯的過程, 編譯的結果就是我們經常看到的pyc文件, 這裡面codeobject對象組成的位元組碼, 而dis就是將這些位元組碼用比較可觀的方式展示出來, 讓我們看到執行的過程, 下面是dis的輸出列解釋:






  • 第一列是數字是原始源代碼的行號。



  • 第二列是位元組碼的偏移量:LOAD_CONST在第0行.以此類推。



  • 第三列是位元組碼人類可讀的名字。它們是為程序員所準備的



  • 第四列表示指令的參數



  • 第五列是計算後的實際參數




前面就不贅述了, 就是讀常量存變數的過程, 最主要的區別就是: test.py 切片是使用了位元組碼 SLICE+3實現的, 而test2.py 單下標取值主要通過位元組碼BINARY_SUBSCR實現的,如同我們猜測的一樣, 相似的語法卻是截然不同的代碼.因為我們要展開討論的是切片(SLICE+3), 所以就不再展開BINARY_SUBSCR, 感興趣的童鞋可以查看相關源碼了解具體實現, 位置: python/object/ceval.c




那我們下面來展開討論下 SLICE+3





/*取自: python2.7 python/ceval.c */



// 第一步:


PyEval_EvalFrameEx

(

PyFrameObject *

f

,

int

throwflag

)


{


....

// 省略n行代碼


TARGET_WITH_IMPL_NOARG

(

SLICE

,

_slice

)


TARGET_WITH_IMPL_NOARG

(

SLICE_1

,

_slice

)


TARGET_WITH_IMPL_NOARG

(

SLICE_2

,

_slice

)


TARGET_WITH_IMPL_NOARG

(

SLICE_3

,

_slice

)


_slice

:


{


if

((

opcode

-

SLICE

)

&

2

)


w

=

POP

();


else


w

=

NULL

;


if

((

opcode

-

SLICE

)

&

1

)


v

=

POP

();


else


v

=

NULL

;


u

=

TOP

();


x

=

apply_slice

(

u

,

v

,

w

);

// 取出v: ilow, w: ihigh, 然後調用apply_slice


Py_DECREF

(

u

);


Py_XDECREF

(

v

);


Py_XDECREF

(

w

);


SET_TOP

(

x

);


if

(

x

!=

NULL

)

DISPATCH

();


break

;


}



....

// 省略n行代碼


}



// 第二步:


apply_slice

(

PyObject *

u

,

PyObject *

v

,

PyObject *

w

)

/* return u[v:w] */


{


PyTypeObject *

tp

=

u

->

ob_type

;


PySequenceMethods *

sq

=

tp

->

tp_as_sequence

;



if

(

sq

&&

sq

->

sq_slice

&&

ISINDEX

(

v

)

&&

ISINDEX

(

w

))

{

// v,w的類型檢查,要整型/長整型對象


Py_ssize_t

ilow

=

0

,

ihigh

=

PY_SSIZE_T_MAX

;


if

(

!

_PyEval_SliceIndex

(

v

,

&

ilow

))

// 將v對象再做檢查, 並將其值轉換出來,存給ilow


return

NULL

;


if

(

!

_PyEval_SliceIndex

(

w

,

&

ihigh

))

// 同上


return

NULL

;


return

PySequence_GetSlice

(

u

,

ilow

,

ihigh

);

// 獲取u對象對應的切片函數


}


else

{


PyObject *

slice

=

PySlice_New

(

v

,

w

,

NULL

);


if

(

slice

!=

NULL

)

{


PyObject *

res

=

PyObject_GetItem

(

u

,

slice

);


Py_DECREF

(

slice

);


return

res

;


}


else


return

NULL

;


}



// 第三步:


PySequence_GetSlice

(

PyObject *

s

,

Py_ssize_t

i1

,

Py_ssize_t

i2

)


{


PySequenceMethods *

m

;


PyMappingMethods *

mp

;



if

(

!

s

)

return

null_error

();



m

=

s

->

ob_type

->

tp_as_sequence

;


if

(

m

&&

m

->

sq_slice

)

{


if

(

i1

sq_length

)

{


// 先做個簡單的初始化, 如果左右下表小於, 將其加上sequence長度使其歸為0


Py_ssize

_

t

l

=

(

*

m

->

sq_length

)(

s

);


if

(

l

sq_slice

(

s

,

i1

,

i2

);


}

else

if

((

mp

=

s

->

ob_type

->

tp_as_mapping

)

&&

mp

->

mp_subscript

)

{


PyObject *

res

;


PyObject *

slice

=

_PySlice_FromIndices

(

i1

,

i2

);


if

(

!

slice

)


return

NULL

;


res

=

mp

->

mp_subscript

(

s

,

slice

);


Py_DECREF

(

slice

);


return

res

;


}



return

type_error

(

""%.200s" object is unsliceable"

,

s

);




雖然上面的代碼有點長, 不過關鍵地方都已經注釋出來, 而我們也只需要關注那些地方就足夠了. 如上, 我們知道最終是要執行 m->sq_slice(s, i1, i2), 但是這個sq_slice有點特別, 因為不同的對象, 它所對應的函數不同, 下面是各自的對應函數:





// 字元串對象


StringObject

.

c

:

(

ssizessizeargfunc

)

string_slice

,

/*sq_slice*/



// 列表對象


ListObject

.

c

:

(

ssizessizeargfunc

)

list_slice

,

/* sq_slice */



// 元組


TupleObject

.

c

:

(

ssizessizeargfunc

)

tupleslice

,

/* sq_slice */




因為他們三個的函數實現大致相同, 所以我們只分析其中一個就可以了, 下面是對列表的切片函數分析:





/*

取自

ListObject

.

c

*/


static

PyObject

*


list_slice

(

PyListObject

*

a

,

Py_ssize_t

ilow

,

Py_ssize_t

ihigh

)


{


PyListObject

*

np

;


PyObject

**

src

,

**

dest

;


Py_ssize

_

t

i

,

len

;


if

(

ilow

<

0

)


ilow

=

0

;


else

if

(

ilow

>

Py_SIZE

(

a

))

//

如果

ilow

大於

a

長度

,

那麼重新賦值為

a

的長度


ilow

=

Py_SIZE

(

a

);


if

(

ihigh

<

ilow

)


ihigh

=

ilow

;


else

if

(

ihigh

>

Py_SIZE

(

a

))

//

如果

ihigh

大於

a

長度

,

那麼重新賦值為

a

的長度


ihigh

=

Py_SIZE

(

a

);


len

=

ihigh

-

ilow

;


np

=

(

PyListObject

*

)

PyList_New

(

len

);

//

創建一個

ihigh

-

ilow

的新列表對象


if

(

np

==

NULL

)


return

NULL

;



src

=

a

->

ob_item

+

ilow

;


dest

=

np

->

ob_item

;


for

(

i

=

0

;

i

<

len

;

i

++

)

{

//

a

處於該範圍內的成員

,

添加到新列表對象


PyObject

*

v

=

src

[

i

];


Py_INCREF

(

v

);


dest

[

i

]

=

v

;


}


return

(

PyObject

*

)

np

;


}




結論




從上面的sq_slice函數對應的切片函數可以看到, 如果在使用切片時, 左右下標都大於sequence的長度時, 都將會被重新賦值成sequence的長度, 所以咱們一開始的切片: print a[10:20], 實際上運行的是: print a4:4. 通過這次的分析, 以後在遇到下標大於對象長度的切片, 應該不會再懵逼了~




看完本文有收穫?請轉

發分享給更多人


關注「Python開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

用 Python 寫一個簡單的Web框架
如何用Python對數據進行差分
英特爾Python發行版助力數據科學
Python 的正則表達式彩蛋
tomd: 用Python將HTML轉換回Markdown

TAG:Python |

您可能感興趣

FAQ詳解「Meltdown和Spectre」問題,接踵而來的「Skyfall和Solace」是否僅是騙局?
Gradle Kotlin DSL的accessors 生成問題
常規外企面試問題1 Tell Me About Yourself
iOS 惹禍:iPhone更新後AirPods問題不斷
iOS 惹禍 iPhone更新後AirPods問題不斷
Belle&Sebastian:如何解決人類的問題?
Andrew Neeme現身Reddit AMA回答粉絲問題
英特爾:Spectre和Meltdown漏洞的修補程序存在問題
Galaxy S9/S9 Plus又被泄露了 硬剛iPhone X應該沒問題
英特爾:修復Spectre和Meltdown漏洞的補丁存在問題
解決畫面撕裂問題:Microsoft 微軟 Xbox One S/X 即將支持 AMD FreeSync「防撕裂」技術
你遇到的問題,它是problem,還是question?
Samsung承認Android Oreo更新後可能存在隨機重新啟動問題
性能測試loadrunner場景問題之HTTP
Oculus修復Rift當機問題,微軟改進SteamVR遊戲性能
8大關鍵問題 讓你全面了解Social Lending 蜂巢星球
國人iPhone4s太卡上述蘋果,iPhoneX再爆新通話問題
Python 之父Guido van Rossum在2013年對幾個問題的回應
FB將召開緊急會議 讓員工提出有關Cambridge Analytica的問題
霍金先生思考過的最大的問題:More is different?