评论

收藏

[python] python 元组的实现和探析

编程语言 编程语言 发布于:2021-06-24 09:59 | 阅读数:671 | 评论:0

  其实就表面感官来说,元组和列表的样子大同小异,面试中经常会遇到的, tuple和 list    有什么区别?这种问题几乎都问烂了,大部分人可以回答的七七八八了,什么 tuple不能变, list可以进行增删改; tuple创建是通过 (), list是通过[],短短两句话道尽其功能与生成,然而道不尽其本质与性能,其丰富的内涵还需要细细展开与推演。

源码解析对比

  首先来分析 list 列表,它的具体结构如下所示:
typedef struct {
  PyObject_VAR_HEAD
  /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
  PyObject **ob_item;
  /* ob_item contains space for 'allocated' elements.  The number
   * currently in use is ob_size.
   * Invariants:
   *   0 <= ob_size <= allocated
   *   len(list) == ob_size
   *   ob_item == NULL implies ob_size == allocated == 0
   * list.sort() temporarily sets allocated to -1 to detect mutations.
   *
   * Items must normally not be NULL, except during construction when
   * the list is not yet visible outside the function that builds it.
   */
  Py_ssize_t allocated;
} PyListObject;
  有兴趣的读者,可直接阅读 list 列表实现的源码文件listobject.h 和 listobject.c。
  在最近的一篇文章中我们分析到 list 本质上是一个长度可变的连续数组,其中 ob_item是一个指针列表,里边的每一个指针都指向列表中的元素,而 allocated则用于存储该列表目前已被分配的空间大小。需要注意的是 allocated 和列表的实际空间大小不同,列表实际空间大小,指的是 len(list)返回的结果,也就是上边代码中注释中的  ob_size,表示该列表总共存储了多少个元素,而在实际情况中,为了优化存储结构,避免每次增加元素都要重新分配内存,列表预分配的空间 allocated 往往会大于 ob_size。
  因此 allocated 和 ob_size 的关系是: allocated >= len(list) = ob_size >= 0。
  如果当前列表分配的空间已满(即 allocated == len(list)),则会向系统请求更大的内存空间,并把原来的元素全部拷贝过去。
  接下来再分析元组,如下所示为 Python 3.7 tuple 元组的具体结构:
typedef struct {
  PyObject_VAR_HEAD
  PyObject *ob_item[1];
  /* ob_item contains space for 'ob_size' elements.
   * Items must normally not be NULL, except during construction when
   * the tuple is not yet visible outside the function that builds it.
   */
} PyTupleObject;
  有兴趣的读者,可阅读 tuple元组实现的源码文件 tupleobject.h 和 tupleobject.c。
  他的 memory layout 如下所示

DSC0000.png

  • free_list[0] 用于存储长度为 0 的 tuple 对象,整个解释器的生命周期里面只有一个长度为 0 的 tuple 对象实例
  • free_list[1] 用于存储长度为 1 的 tuple 对象,可以通过 tuple 对象的 ob_item[0] 指针遍历到下一个长度为 1 的 tuple 对象
  • free_list[2] 用于存储长度为 2 的 tuple 对象,上图画的 free_list[2] 中只有一个对象
  • free_list 每一格最多能存储 PyTuple_MAXFREELIST个 tuple 链表长度,这里定义的是 2000
  我们来看下 PyTuple_New 函数
/* Objects/tupleobject.c 79 - 136 行 */
 
PyObject *
PyTuple_New(Py_ssize_t size)
{
  PyTupleObject *op;
  Py_ssize_t i;
  if (size < 0) {
    PyErr_BadInternalCall();
    return NULL;
  }
/* 如果启动了 free_list 存储 */
#if PyTuple_MAXSAVESIZE > 0
  if (size == 0 && free_list[0]) {
    /* 如果 size 为 0, 则返回 free_list 的第一个元素 */
    op = free_list[0];
    Py_INCREF(op);
#ifdef COUNT_ALLOCS
    _Py_tuple_zero_allocs++;
#endif
    return (PyObject *) op;
  }
  if (size < PyTuple_MAXSAVESIZE && (op = free_list[size]) != NULL) {
    /* 如果 size 在 free_list 范围内,并且 free_list[size] 存有之前回收过的对应size的tuple 对象 
    把 free_list[size] 指向 op 的下一个链表地址
    */
    free_list[size] = (PyTupleObject *) op->ob_item[0];
    numfree[size]--;
#ifdef COUNT_ALLOCS
    _Py_fast_tuple_allocs++;
#endif
    /* Inline PyObject_InitVar */
#ifdef Py_TRACE_REFS
    Py_SIZE(op) = size;
    Py_TYPE(op) = &PyTuple_Type;
#endif
    _Py_NewReference((PyObject *)op);
  }
  else
#endif
  {
    /* free_list 未找到对应大小的 tuple 并且 size 不为 0,向操作系统分配 */
    /* Check for overflow */
    if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - sizeof(PyTupleObject) -
          sizeof(PyObject *)) / sizeof(PyObject *)) {
      return PyErr_NoMemory();
    }
    op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size);
    if (op == NULL)
      return NULL;
  }
  /* 把 tuple 里面的元素设置为空指针 */
  for (i=0; i < size; i++)
    op->ob_item[i] = NULL;
#if PyTuple_MAXSAVESIZE > 0
  if (size == 0) {
    free_list[0] = op;
    ++numfree[0];
    Py_INCREF(op);      /* extra INCREF so that this is never freed */
  }
#endif
#ifdef SHOW_TRACK_COUNT
  count_tracked++;
#endif
  _PyObject_GC_TRACK(op);
  return (PyObject *) op;
}
  从函数名也大概可以猜出功能了

元组真的不能修改吗?

  嘿嘿,这倒不一定,比如说进行以下操作
>>> t = ([1,2,3,4],[5,4,5,6,4])
>>> t
([1, 2, 3, 4], [5, 4, 5, 6, 4])
>>> t[1].append("hello yerik")
>>> t
([1, 2, 3, 4], [5, 4, 5, 6, 4, 'hello yerik'])
>>> t[1].pop()
'hello yerik'
>>> t[1].pop(2)
5
>>> t
([1, 2, 3, 4], [5, 4, 6, 4])
  这个tuple定义的时候有2个元素,都是两个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?
  别急别急,案例中的t包含两个元素,都是list
关注下面的标签,发现更多相似文章