Python内置数据结构大总结

内置据结构大总结 今天不讲解新的内容,主要回顾一下以往讲过的内置数据结构,来个大总结。

五种线性结构

  1. 列表
  2. 元组
  3. 字符串
  4. bytes
  5. bytearray

两种非线性结构

  1. 字典
  2. 集合

列表、元组、字符串属于线性结构,我们可以对其进行切片操作、解包/封包操作。

序列类型操作符

下表是所有序列类型都适用的操作符:

序列操作符

作用

seq[ind]

获得下标为ind的元素

seq[ind1:ind2]

获得下标从ind1到ind2间的元素集合

seq * expr

序列重复expr次

seq1 + seq2

连接序列seq1和seq2

obj in seq

判断obj元素是否包含在seq中

obj not in

判断obj元素是否不包含在seq中

几种数据结构的共性

这几种数据结构的共性:

  1. 都是顺序存储
  2. 顺序访问
  3. 可迭代对象(可迭代对象可以用len方法获取其长度)
  4. 通过索引进行元素的访问
  5. 可以进行切片操作

切片

切片不会对原有的序列做任何修改,切片的语法为:

seq[start:stop]

从索引start开始,到索引stop结束,不包含stop,返回新的序列,不会对原有的对象做任何修改。

几个特性:

  • start超出索引范围:start = 0
  • stop超出索引范围:stop = -1
  • 负数索引:实际上可转化为:len(seq) + index
  • start >= stop时,返回空列表

slice的实现:

lst = list(range(0, 10))


def slice(lst, start=0, stop=0):
    if start < 0:
        start = len(lst) + start
    if stop <= 0:
        stop = len(lst) + stop
    if stop <= start:
        return []
    if stop > len(lst):
        stop = len(lst)
    if start < 0:
        start = 0
    ret = []
    for i, v in enumerate(lst):
        if i >= start and i < stop:
            ret.append(v)
    return ret

print(slice(lst, 3, 2))
print(slice(lst, 2, 5))
print(slice(lst, -100, 100))

运行结果为:

 : []
 : [2, 3, 4]
 : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如果有了步长之后,上面的规则就会发生变化。接下来加入步长的slice实现:

def slice(lst, start=0, stop=0, step=1):
    ret = []
    if stop < 0:
        tmp = start
        start = tmp
        stop = start
    current = start
    while current < stop:
        try:
            ret.append(lst[current])
        except IndexError:
            pass
        current += step
    return ret

切片的一些常用操作:

>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> lst[:] # 等效于copy方法

>>> lst[-5:-3] # 支持负数索引

# start大于等于stop时,返回空列表
>>> lst[3:1]

# 列出偶数,步长为2
lst[::2]
[0, 2, 4, 6, 8]

# 列出偶数,步长为2,并倒序输出
lst[::2][::-1]
[8, 6, 4, 2, 0]

# 列出奇数,步长为2,并倒序输出
lst[::-2]
[9, 7, 5, 3, 1]

# 列出偶数,步长为2,并倒序输出
lst[-2::-2]
[8, 6, 4, 2, 0]

索引

如果索引超出范围,将引发IndexError的异常。修改元素的时候,如果超出索引范围,也同样引发IndexError异常。

  • index(value)方法根据value找索引
  • count(value)方法统计value出现的次数

enumerate的实现:

def enumerate(iterator):
    i = 0
    for v in iterator:
        yield i, v
        i += 1

def enumerate(iterator):
    ret = []
    i = 0
    for v in iterator:
        ret.append((i, v))
        i += 1
    return ret

引用

列表批量赋值:

## 当赋值的序列连续时
# 对切片赋值,会替代原来的元素
>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lst[3:5] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 'y', 'z', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = ['x']
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = 'x'
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
## 当赋值的序列不连续时
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 4, 'y', 6, 'z', 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x']
ValueError: attempt to assign sequence of size 1 to extended slice of size 3
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

不建议使用以上的方式对切片赋值的操作

解包/封包

解构与封装可以叫做解包与封包。

  • 解构把集合里的元素复制给变量;
  • 封装是用变量构建元组。

解构:按照元素顺序,把线性解构的元素依次赋值给变量。

封装的例子:

t = 1, 2
print(t)
(1, 2)

print(type(t))
<class 'tuple'>

定义一个元组,可以省略小括号。

t1 = (1, 2)
t2 = 1, 2
print(t1 == t2) # t1与t2是等效的
True

封装出来的是元组。封装没有什么难度。解构的变化多样,接下来重点看看解构。

先看一个例子:

In[29]: def swap(a, b):
    ...:     i = a
    ...:     a = b
    ...:     b = i
    ...:     return (a, b)
    ...: 

In[30]: swap(1, 3)
Out[30]: (3, 1)

对上面的代码进行改写,由3行代码,变成了一行代码:

In[31]: def swap(a, b):
    ...:     a, b = b, a
    ...:     return (a, b)
    ...: 

In[32]: swap(1, 3)
Out[32]: (3, 1)

对于如下的代码操作,就是解包:

In[33]: x, y = (1, 3)

In[34]: x
Out[34]: 1

In[35]: y
Out[35]: 3

上面的代码使用的是元组,列表也是可以的:

In[36]: a, b = 1, 3

In[37]: a
Out[37]: 1

In[38]: b
Out[38]: 3

接下来看一下封包:

In[39]: t = 1, 3

In[40]: t
Out[40]: (1, 3)

In[41]: type(t)
Out[41]: tuple

继续看例子:

In[42]: head, tail = list(range(0, 10))
# 将会得到如下的错误,因为=两边的元素数量不一致导致的
ValueError: too many values to unpack (expected 2)

In[43]: head, *tail = list(range(0, 10))

In[44]: head
Out[44]: 0

In[45]: tail
Out[45]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In[46]: *head, tail = list(range(0, 10))

In[47]: head
Out[47]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

In[48]: tail
Out[48]: 9

如果对一个含有2个元素的列表进行解包:

In[49]: head, *tail = [1, 2]

In[50]: head
Out[50]: 1

In[51]: tail
Out[51]: [2]

如果对一个含有一个元素的列表进行解包:

In[52]: head, *tail = [1]

In[53]: head
Out[53]: 1

In[54]: tail
Out[54]: []

如果对一个空列表进行解包:

In[55]: head, *tail = []
ValueError: not enough values to unpack (expected at least 1, got 0)

针对上述例子的总结:

  1. 左边不能只有一个星号,还要有其他元素
  2. 如果左边不用星号,那么左边的元素个数要与右边的元素个数相同
  3. 左边变量数小于右边元素个数,且左边没有加星号会报错
  4. 元素按照顺序赋值给变量
  5. 变量和元素必须匹配
  6. 加星号变量,可以接收任意个数的元素
  7. 加星号的变量不能单独出现

针对上述,写一个具体的例子:

def it(lst):
    if lst:
        head, *tail = lst
        print(head)
        it(tail)

it(list(range(0, 10)))
0
1
2
3
4
5
6
7
8
9

更复杂一点的例子:

In[63]: head, *tail = [1, 2, 3]

In[64]: head
Out[64]: 1

In[65]: tail
Out[65]: [2, 3]

下面这个例子,在Python2中不能实现:

In[59]: head, *mid, tail = [1, 2, 3, 4, 5]

In[60]: head
Out[60]: 1

In[61]: mid
Out[61]: [2, 3, 4]

In[62]: tail
Out[62]: 5

接下来还有更好远的,如果我们要丢弃=右边某个值,可以使用下划线来,演示如下:

In[66]: lst = list(range(0, 10))

In[67]: lst
Out[67]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In[68]: a, b, _, c, *_ = lst

In[69]: a
Out[69]: 0

In[70]: b
Out[70]: 1

In[71]: c
Out[71]: 3

如果我们只想要序列的首位两个元素,可以这样操作:

In[72]: head, *_, tail = lst

In[73]: head
Out[73]: 0

In[74]: tail
Out[74]: 9

再来一发,两边结构要一样:

In[75]: lst = [1, [2, 3], 4]

In[76]: a, (b, c), d = lst

In[77]: a
Out[77]: 1

In[78]: b
Out[78]: 2

In[79]: c
Out[79]: 3

In[80]: d
Out[80]: 4

对上面的例子,再来稍微变化一下,不过两边的结构要一样,解构是支持多层次的。:

In[81]: lst = [1, [2, 3, 4, 5, 6, 8], 9]

In[82]: lst
Out[82]: [1, [2, 3, 4, 5, 6, 8], 9]

In[83]: a, (b, *_, c), d = lst

In[84]: a
Out[84]: 1

In[85]: b
Out[85]: 2

In[86]: c
Out[86]: 8

In[87]: d
Out[87]: 9

注意:

  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 只要两边结构一样就行
>>> a, (b, (c, (d,))) = [1, [2, [3, [4]]]]
>>> a
1
>>> b
2
>>> c
3
>>> d
4

python的一个惯例,使用单个下划线表示丢弃该变量。单个下划线也是Python合法的标识符,但是如果不是要丢弃一个变量,通常不要用单个下划线表示一个有意义的变量。

head, *_ = 'I love python'
print(head)
I
key, *_, value = 'env = properties'.partition('=')
print(key)
env
print(value)
properties

非常复杂的数据结构,多层嵌套的线性结构的时候,可以用解构快速提取其中的值。

本文总结

这是一个没有小结的小结,还是希望大家都能学会Python,能够上手写一定的代码并用到工作当中。最后,祝大家学习愉快,在学习的路上你并不孤单,加油。

原文发布于微信公众号 - 小白的技术客栈(XBDJSKZ)

原文发表时间:2017-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏软件开发 -- 分享 互助 成长

(虚)继承类的内存占用大小

(虚)继承类的内存占用大小 首先,平时所声明的类只是一种类型定义,它本身是没有大小可言的。 因此,如果用sizeof运算符对一个类型名操作,那得到的是具有该类...

1728
来自专栏lhyt前端之路

一些冷门的js操作0.前言1.数组2.位操作符2.1字符串转数字2.2更多的操作3. 构造类

大家学习的时候,一开始小白,然后接触到进阶的东西的时候,发现一切得心应手,有的人可能开始说精通了。突然有一天,发现了一些基于很基础的东西的骚操作,就开始怀疑人生...

642
来自专栏TungHsu

这或许是对小白最友好的python入门了吧——7,组织列表

有时候我们想要把列表元素进行排序,那应该怎么办呢? ? 原列表 (一)通过sort()让列表元素根据字母顺序排列,这个方法是会改变原有列表的 和普通的函数比如t...

2784
来自专栏恰同学骚年

剑指Offer面试题:13.调整数组顺序使奇数位于偶数前面

  例如有以下一个整数数组:12345,经过调整后可以为:15342、13542、13524等等。

906
来自专栏水击三千

浅谈JavaScript的函数表达式(闭包)

  前文已经简单的介绍了函数的闭包。函数的闭包就是有权访问另一个函数作用域的函数,也就是函数内部又定义了一个函数。 1 var Super=function(n...

2135
来自专栏LinkedBear的个人空间

唠唠SE的面向对象-09——interface接口

Java是单继承机制,那么一个类需要有多个操作并且来自不同类的时候,就需要接口来弥补这种缺点。

551
来自专栏编程理解

正则表达式(二):断言

上面的例子反映了一个明显的正则匹配规则:贪婪匹配,即在符合正则表达式规则的情况下,总会匹配尽量多内容。 如果想使得正则表达式按最小内容匹配,只需要在次数元符号...

651
来自专栏老九学堂

十七个C语言新手编程时常犯的错误及解决方式

编译程序把a和A认为是两个不同的变量名,而显示出错信息。C认为大写字母和小写字母是两个不同的字符。习惯上,符号常量名用大写,变量名用小写表示,以增加可读性。

1264
来自专栏司想君

JavaScript闭包,只学这篇就会了

昨天发的文章,排版出现了重大失误。让大家的眼睛受累了。今天再发一遍。 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透...

2648
来自专栏菩提树下的杨过

python:函数的高级特性

1693

扫码关注云+社区