Python中关于Sequence切片的下标问题详解


Posted in Python onJune 15, 2017

前言

在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 [:]   # 复制整个列表

语法很简洁, 也很容易理解, 这种语法在我们日常使用中 是简单又好用, 但我相信在我们使用这种切片语法时, 都会习惯性谨遵一些规则:

  • ilow, ihigh均小于 sequece的长度
  • 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 < 0 || i2 < 0) {
   if (m->sq_length) {
    // 先做个简单的初始化, 如果左右下表小于, 将其加上sequence长度使其归为0
    Py_ssize_t l = (*m->sq_length)(s);
    if (l < 0)
     return NULL;
    if (i1 < 0)
     i1 += l;
    if (i2 < 0)
     i2 += l;
   }
  }
  // 真正调用对象的sq_slice函数, 来执行切片的操作
  return m->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 相关文章推荐
Pyramid Mako模板引入helper对象的步骤方法
Nov 27 Python
python解析中国天气网的天气数据
Mar 21 Python
使用Django的模版来配合字符串翻译工作
Jul 27 Python
Python中查看文件名和文件路径
Mar 31 Python
python的pdb调试命令的命令整理及实例
Jul 12 Python
python版简单工厂模式
Oct 16 Python
Python基础教程之利用期物处理并发
Mar 29 Python
Python基于pandas实现json格式转换成dataframe的方法
Jun 22 Python
python脚本监控Tomcat服务器的方法
Jul 06 Python
Python爬虫学习之翻译小程序
Jul 30 Python
详解mac python+selenium+Chrome 简单案例
Nov 08 Python
wxpython+pymysql实现用户登陆功能
Nov 19 Python
解决python3在anaconda下安装caffe失败的问题
Jun 15 #Python
带你了解python装饰器
Jun 15 #Python
解决Linux系统中python matplotlib画图的中文显示问题
Jun 15 #Python
Python之os操作方法(详解)
Jun 15 #Python
基于Linux系统中python matplotlib画图的中文显示问题的解决方法
Jun 15 #Python
Python使用QRCode模块生成二维码实例详解
Jun 14 #Python
Python中扩展包的安装方法详解
Jun 14 #Python
You might like
PHP 编写的 25个游戏脚本
2009/05/11 PHP
一个简单的网页密码登陆php代码
2012/07/17 PHP
Yii入门教程之Yii安装及hello world
2014/11/25 PHP
PHP中使用Imagick读取pdf并生成png缩略图实例
2015/01/21 PHP
php随机获取金山词霸每日一句的方法
2015/07/09 PHP
以实例全面讲解PHP中多进程编程的相关函数的使用
2015/08/18 PHP
PHP实现腾讯与百度坐标转换
2017/08/05 PHP
IE下window.onresize 多次调用与死循环bug处理方法介绍
2013/11/12 Javascript
node.js中的fs.open方法使用说明
2014/12/17 Javascript
JS实现物体带缓冲的间歇运动效果示例
2016/12/22 Javascript
JS轮播图实现简单代码
2021/02/19 Javascript
jquery写出PC端轮播图实例
2018/01/26 jQuery
vue中设置height:100%无效的问题及解决方法
2018/07/27 Javascript
浅析java线程中断的办法
2018/07/29 Javascript
vue实现局部刷新的实现示例
2019/04/16 Javascript
JS前端知识点offset,scroll,client,冒泡,事件对象的应用整理总结
2019/06/27 Javascript
JS window对象简单操作完整示例
2020/01/14 Javascript
JS自定义对象创建与简单使用方法示例
2020/01/15 Javascript
使用Python编写提取日志中的中文的脚本的方法
2015/04/30 Python
Python的爬虫包Beautiful Soup中用正则表达式来搜索
2016/01/20 Python
PyQt5通过信号实现MVC的示例
2021/02/06 Python
css3实现小箭头各种图形效果
2020/07/08 HTML / CSS
前端实现背景虚化但内容清晰且自适应 的实例代码
2019/08/01 HTML / CSS
Kidsroom台湾:来自德国的婴儿用品
2017/12/11 全球购物
德国运动营养和健身网上商店:Myprotein.de
2018/07/18 全球购物
印度服装购物网站:Limeroad
2018/09/26 全球购物
英国领先的票务代理商之一:The Ticket Factory
2019/02/09 全球购物
艺术系应届生的自我评价
2013/10/19 职场文书
实习生单位鉴定意见
2013/12/04 职场文书
化学实验员岗位职责
2013/12/28 职场文书
酒店个人求职信范文
2014/01/25 职场文书
心理学专业大学生职业生涯规划范文
2014/02/19 职场文书
学生党员的自我评价范文
2014/03/01 职场文书
驾驶员安全责任书范本
2014/07/24 职场文书
小学六一儿童节活动方案
2014/08/27 职场文书
vue实现移动端div拖动效果
2022/03/03 Vue.js