ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python有序序列完全指南:字符串/列表/元组/range的索引切片与常用方法

Python有序序列完全指南:字符串/列表/元组/range的索引切片与常用方法 我从后台翻了翻留言发现很多刚接触Python的朋友都会卡在同一个地方字符串、列表、元组、range明明长得不一样操作起来却总有相似之处。今天这篇就是来把这层窗户纸捅破的——有序序列Sequence其实就是Python里所有能排队、能按序号取东西的数据类型的总称。理解它之后学切片、学遍历、学数据清洗都会顺很多。这篇不只讲概念我会把索引、切片、通用操作、可变与不可变这几个核心知识点串成一条线每个部分都有代码示例和踩坑提醒。适合刚学完变量和基本数据类型、准备进阶的初学者也适合那些Python语法会一点但总觉得自己写不熟的老朋友。1. 从一串字符串说起到底什么才算有序序列先做个最简单的实验。你写一句s hello然后想取第二个字符Python语法是s[1]出来的是e。你会发现字符串里的字母是按照固定顺序排列的每个字都有自己的位置编号这个位置编号就是索引。再写一个列表lst [苹果, 香蕉, 橘子]想取第二个水果也是lst[1]。两个完全不同类型的东西操作方式一模一样这背后就是同一个逻辑它们都是有序序列。1.1 有序序列的四个主要成员Python里有四大常见有序序列你最好把它们的名字和特征刻在脑子里字符串str不可变的字符序列。Python就是一串排好队的字符里面每个字母都占一个位置。列表list可变的容器序列。能往里加东西、删东西、改东西日常开发最常用。元组tuple不可变的容器序列。一旦创建里面的元素就不能增删改适合存放不该被修改的数据。range对象表示一个整数范围的序列。它本身不把所有数字一次性存下来而是惰性生成用来配合循环非常舒服。这四个成员虽然具体用途不同但都支持一整套通用序列操作索引、切片、拼接、成员判断、长度计算、遍历等等。这也是为什么标题里强调基本方法——这些方法不是在某个类型里单独存在的而是有序序列这个大家族共享的通用规则。1.2 为什么说有序这两个字是破局点很多初学者想不通set集合和dict字典也能存多个数据为什么它们不算有序序列关键就在有序二字。有序意味着每一个元素都有确定的位置编号你可以通过编号直接锁定它、可以按照顺序一片一片地取出来、可以判断谁在前谁在后。而set和dict是基于哈希表实现的存储顺序由哈希值决定所以它们不支持索引也不支持切片。换句话说有序是一切高级操作的基石。1.3 前期怎么理解方法和函数这两个词标题里的方法指的是依附在对象上的函数。字符串、列表、元组本身自带一些功能函数比如lst.append(4)就是调用列表的append方法s.upper()就是调用字符串的upper方法。而len()、max()这类是内置函数直接传参数进去用。最开始不需要把这两者分得太死你只需要知道有序序列自带一批工具内置函数又给了一批通用工具两批配合着用就行。2. 索引与切片序列操作的第一把钥匙索引和切片是有序序列最核心的两个操作。索引负责单个精准打击切片负责范围成片收割两者配合起来几乎可以应付所有取值需求。2.1 正索引从0开始的游戏规则Python的索引起点是0不是1。hello[0]得到的是hhello[1]得到的是e。这个规则对列表、元组、range全都适用。新手最容易在这翻车想取第一个元素却写lst[1]拿到的其实是第二个。记忆技巧把索引理解为偏移量。第一个元素距离起点偏移0次所以是[0]第二个元素偏移1次所以是[1]。lst [10, 20, 30] print(lst[0]) # 10 print(lst[1]) # 20 print(lst[2]) # 302.2 负索引从尾巴往前数的野路子Python有个独门绝技——负索引。lst[-1]拿到的是最后一个元素lst[-2]拿到的是倒数第二个。很多新手一开始觉得很怪但用习惯之后会真香尤其是在不知道序列长度的时候想取最后一个元素根本不需要先len()再算下标。name Python print(name[-1]) # n print(name[-2]) # o注意索引越界会直接报IndexError。比如lst [10, 20, 30]你写lst[5]程序立刻崩溃。但切片不会这点放到下一节说。2.3 切片一次取出一片左闭右开是灵魂切片的基本语法是序列[start:stop:step]表示从start开始到stop之前结束每次按step步长取一个。这里最反直觉的就是到 stop 之前结束也就是左闭右开规则。nums [0, 1, 2, 3, 4, 5] print(nums[1:4]) # [1, 2, 3]注意没有4 print(nums[:3]) # [0, 1, 2]start省略表示从头开始 print(nums[2:]) # [2, 3, 4, 5]stop省略表示一直到结尾 print(nums[:]) # 整个序列的副本为什么设计成左闭右开因为这样能让切片长度直接等于stop - start。比如nums[1:4]长度就是 4-13不用再纠结加不加一。而且这种设计在代码里到处都在用比如range(5)生成的是0,1,2,3,4天然就不包含5习惯之后会觉得很统一。2.4 步长与反转切片的高级玩法步长默认是1你可以改成2、3等实现跳跃取样nums [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(nums[::2]) # [0, 2, 4, 6, 8]隔一个取一个 print(nums[1::2]) # [1, 3, 5, 7, 9]步长也可以是负数负步长会让切片从右往左走最经典的用法就是[::-1]实现序列反转s Python print(s[::-1]) # nohtyP不过这里有个大坑如果start和stop没配合好负步长容易得到空结果。比如Python[0:5:-1]逻辑上你想从前往后取但步长是负数让它想从后往前矛盾了结果就是空串。正确写法应该是Python[5::-1]。切片越界不报错这点和索引完全不同。nums[1:100]不会报错只会把从第1个到结尾的所有元素返回。这个宽容特性在数据清洗时很有用不用担心边界溢出。2.5 切片的复制属性别把引用和副本搞混切片返回的是一个新的序列对象。对列表来说lst[:]得到的是一份浅拷贝修改新列表不会影响原列表。这个特性经常被用来快速复制列表a [1, 2, 3] b a[:] b.append(4) print(a) # [1, 2, 3] print(b) # [1, 2, 3, 4]但注意如果列表里装的是可变对象比如子列表切片浅拷贝只复制外层内层的子列表仍然是同一个对象。这个深浅拷贝问题后面单独说现在心里有根弦就行。3. 通用操作全家桶每个序列都能用的能力除了索引和切片有序序列还共享一批通用操作。这些操作不属于某个具体类型而是所有有序序列都支持的规则学一次四处生效。3.1 拼接与重复加号与乘号的真面目可以用来拼接两个相同类型的序列字符串拼接成更长字符串列表拼接成新列表。*可以重复序列若干遍生成拼接后的新序列。s1 Py s2 thon print(s1 s2) # Python lst1 [1, 2] lst2 [3, 4] print(lst1 lst2) # [1, 2, 3, 4] print(lst1 * 3) # [1, 2, 1, 2, 1, 2] print(ab * 3) # ababab关键点拼接和重复都不会修改原序列而是生成新序列。字符串和元组本来就是不可变的列表哪怕可变用也是返回新列表。如果你想在原列表基础上追加应该用extend()方法或在尾部拼接而不是之后重新赋值。3.2 成员判断in 和 not inin用来判断某个元素是不是在序列中结果是布尔值。这个操作简单但极其常用比如检查用户名是否在列表中、判断某个字符是否出现在字符串中。fruits [苹果, 香蕉, 橘子] print(苹果 in fruits) # True print(西瓜 in fruits) # False s hello python print(py in s) # True注意字符串的in是子串匹配字符串的in稍微特殊一点它判断的是子串不是单个字符。如果有半句话切片的返回值是新序列而index返回的是整数值位置下标两者完全不冲突只是它们分别服务于取数据和找位置两个需求。3.3 len、max、min、sum内置函数大礼包len()返回序列长度也就是里面有多少个元素。max()返回序列中最大的元素。min()返回序列中最小的元素。sum()返回序列中所有元素之和注意它只对数值管用。nums [5, 2, 8, 1, 9] print(len(nums)) # 5 print(max(nums)) # 9 print(min(nums)) # 1 print(sum(nums)) # 25这几个函数能直接用于任何有序序列。加一个细节max和min对字符串序列按字典序比较对数值序列按数字大小比较如果你对字典列表求max直接做会报错必须先指定比较关键字的参数这个属于进阶内容入门阶段可以暂时不碰。但即使现在用不上也要记住**能支持就支持不支持的等遇到再解决**不用一头扎进所有特殊场景。3.4 序列比较字典序规则有序序列可以比较大小规则是从左到右逐元素比较直到出现第一个不同的元素谁大谁决定结果。这跟字典排序逻辑一致所以叫字典序。print([1, 2, 3] [1, 2, 4]) # True列表逐元素比较 print(abc abd) # True字符串逐字符比较 print(Python python) # True大写字母在小写字母前面我见过不少初学者在排序后不理解为什么banana会排在Banana前面其实就是因为字典序先比较第一个字符B的ASCII码66小于b98所以大写字母总是先出现。3.5 查找位置index方法的基本用法前面提到index方法这里正式说一遍。基本语法是序列.index(要查找的元素)返回第一个匹配元素的下标。如果元素不存在会抛出ValueError。words [a, b, c, b] print(words.index(b)) # 1返回第一个b的位置如果你只想检查有没有先in判断再做index是更稳的写法if b in words: pos words.index(b) print(找到了位置在, pos) else: print(没有这个元素)4. 可变与不可变列表和元组的分界线有序序列内部又分成两派可变序列和不可变序列。列表是可变派的代表字符串和元组是保守派的代表。这一章内容会在你写实用代码时报很多坑值得认真看。4.1 可变序列列表的增删改查列表支持在原对象上直接修改。常用方法有lst [1, 2, 3] lst.append(4) # 在结尾追加一个元素 - [1, 2, 3, 4] lst.insert(0, 0) # 在指定位置插入一个元素 - [0, 1, 2, 3, 4] lst.extend([5, 6]) # 在结尾拼接一个可迭代对象 - [0, 1, 2, 3, 4, 5, 6] lst.pop() # 弹出并返回末尾元素也可以pop(下标) lst.remove(2) # 删除第一个匹配的元素 lst[0] 100 # 通过索引直接修改 - [100, 1, 2, 3, 4, 5, 6]重要提醒append和extend的区别是头号混淆点。append是把参数当作一个元素追加进去extend是把参数里的每个元素逐个拼进去。看例子a [1, 2] a.append([3, 4]) print(a) # [1, 2, [3, 4]]列表变成了3个元素 b [1, 2] b.extend([3, 4]) print(b) # [1, 2, 3, 4]列表还是4个整数这种错误我见过太多次了尤其是想把一个列表合并到另一个列表的时候用错append会得到嵌套列表遍历时逻辑全乱。4.2 不可变序列字符串和元组的改不动字符串和元组不能通过索引修改也不能直接添加元素。你写s abc; s[1] B立刻报TypeError: str object does not support item assignment。很多新手会疑惑那我想改字符串怎么办 答案是创建新字符串而不是修改旧的。s hello s2 s.replace(h, H) # 返回新字符串原s不变 print(s) # hello print(s2) # Hello t (1, 2, 3) t2 t (4,) print(t2) # (1, 2, 3, 4)元组也一样想加元素是重新拼接创建新元组原元组纹丝不动。这里请注意一个小语法细节写单元素元组必须带逗号(4,)是元组(4)只是整数4加了个括号。4.3 为什么需要不可变序列很多初学者会问既然列表这么灵活为什么要用元组 核心原因有两个。第一个是数据保护。有些数据在逻辑上不该被修改比如颜色配置里的RGB值、数据库里的一行记录如果写成元组就杜绝了后面代码不小心改掉它的可能。第二个是可哈希性。元组可以当作字典的键列表不行因为字典要求键必须是不可变的。这个点等你学到字典时会很有感受先混个眼熟。字符串不可变带来的好处是任何字符串操作都不会产生副作用用它做数据处理的逻辑更容易推演和控制。4.4 可变序列的引用陷阱变量不是盒子这个概念我称之为引用陷阱新手几乎必踩。以下写法的逻辑是什么a [1, 2, 3] b a b.append(4) print(a) # ??很多新手以为a还是[1, 2, 3]但实际上a也会变成[1, 2, 3, 4]。原因是b a并没有复制列表而是让b和a指向了同一个列表对象。这就像两个人共用同一台冰箱你往里放东西另一个人打开时也会看到。如果真想复制一份独立列表用a[:]或list(a)a [1, 2, 3] b a[:] # 基于切片复制一个新列表 b.append(4) print(a) # [1, 2, 3] print(b) # [1, 2, 3, 4]字符串和元组因为不可变天然没有这个烦恼——你修改变量只是换了个新对象原来的对象不会被影响。5. 常用方法盘点count、index、sort、reverse现在按功能类别把有序序列的常用方法过一遍。这节会花不少篇幅因为很多方法看起来差不多选错的概率很高。5.1 count与index统计与定位count(x)统计x在序列中出现的次数index(x)返回x第一次出现的位置。nums [1, 2, 3, 2, 2, 5] print(nums.count(2)) # 3 print(nums.index(2)) # 1字符串同样支持且更强一些还能指定起始范围s hello world print(s.count(o)) # 2 print(s.index(world)) # 6容易踩坑的点index在元素不存在时会抛异常如果要写健壮代码要么先in判断要么用try/except。入门阶段建议先in判断逻辑更直观。count不会报错不存在就返回0。5.2 sort与sorted排序到底用哪个这是Python里最常用的排序方式两者经常被混淆。list.sort()是就地排序直接修改原列表返回None。sorted(seq)是返回新列表原序列不变。nums [3, 1, 4, 1, 5] nums.sort() print(nums) # [1, 1, 3, 4, 5]原列表被改了 nums2 [3, 1, 4, 1, 5] sorted_nums sorted(nums2) print(nums2) # [3, 1, 4, 1, 5]原列表没变 print(sorted_nums) # [1, 1, 3, 4, 5]一个经典错误是nums [3, 1, 4] new_nums nums.sort() print(new_nums) # None因为sort()不返回新列表如果你想要原列表不变同时得到新排序列表用sorted如果你只想对列表原地排个序用sort。还有人会问字符串和元组能sort()吗不能只有列表有sort()方法。但字符串和元组都可以用sorted()结果返回列表。5.3 reverse与reversed反转也有两支人马list.reverse()是就地反转返回None。reversed(seq)是内置函数返回一个反向迭代器需要用list()或循环取出来。nums [1, 2, 3] nums.reverse() print(nums) # [3, 2, 1] nums2 [1, 2, 3] rev_nums list(reversed(nums2)) print(rev_nums) # [3, 2, 1] print(nums2) # [1, 2, 3]原序列不变这种就地方法返回None内置函数返回新结果的成对设计在Python里经常出现记住这个规律可以少走很多弯路。5.4 字符串的独门方法split、join、replace、strip虽然这章的标题是序列的基本方法但字符串作为最常用的有序序列有几个特殊方法必须掌握因为它们用得太频繁了。s apple,banana,orange parts s.split(,) # [apple, banana, orange] new_s -.join(parts) # apple-banana-orange s2 hello print(s2.strip()) # hello去掉两端空白 s3 I love Python print(s3.replace(love, like)) # I like Pythonsplit按分隔符把字符串切成列表join是反操作把列表里的字符串用指定分隔符合并成新字符串。这两个几乎永远成对出现做数据处理时经常会用到。注意join的语法有点反直觉分隔符在前被合并的列表在后即分隔符.join(列表)。写反了会直接报错多练几次就好。5.5 元组的方法有多贫瘠元组因为不可变只有count和index两个方法没有增删改、没有排序和反转。这个贫瘠恰恰是它的设计特色——能做的操作越少越不容易被误用。需要更丰富功能时把元组转成列表再操作即可。6. 遍历与实战把方法用起来才算学会学了一大堆方法如果只停留在读懂代码的层面很快会忘。真实场景里有序序列最常和循环绑在一起使用。下面通过几个小程序把前面所有知识串起来。6.1 基础遍历for循环直接读序列fruits [苹果, 香蕉, 橘子] for fruit in fruits: print(fruit)这段代码会依次打印苹果、香蕉、橘子。字符串、元组、range都可以这样遍历这是最自然的遍历方式。6.2 带上索引的遍历enumerate函数如果你需要同时知道元素内容和元素序号用enumeratefruits [苹果, 香蕉, 橘子] for idx, fruit in enumerate(fruits): print(idx, fruit) # 0 苹果 # 1 香蕉 # 2 橘子enumerate返回的是索引元素的配对解包成两个变量来接收代码简洁还不容易丢掉序号。如果你想起始序号从1开始可以写成enumerate(fruits, start1)。6.3 实战一用列表和切片做一个简易排行榜假设你有一个成绩列表需求是取出前三名并保留原始顺序。scores [78, 92, 85, 96, 88] sorted_scores sorted(scores, reverseTrue) # 从高到低排序 top3 sorted_scores[:3] print(top3) # [96, 92, 88] # 如果还想知道前三名在原列表中的顺序呢 # 先把索引和分数打包成元组列表 pairs list(enumerate(scores)) pairs.sort(keylambda x: x[1], reverseTrue) top3_pairs pairs[:3] print(top3_pairs) # [(3, 96), (1, 92), (4, 88)]这里使用了匿名函数lambda x: x[1]告诉sort按元组的第二个元素排序也就是分数。这种做法在处理带附件的排序时非常实用你会频繁用到 key 参数。6.4 实战二字符串清洗与统计读取一行句子统计每个单词出现的次数。这个需求在文本分析里很常见。text python is great and python is easy words text.split() # 默认按空白字符分割 word_count {} for word in words: word_count[word] word_count.get(word, 0) 1 print(word_count) # {python: 2, is: 2, great: 1, and: 1, easy: 1}我先用split()把句子切分成单词列表然后遍历列表用字典做次数累加。get(word, 0)表示如果字典里没有这个单词就返回0这样第一次遇到单词时能顺利从0加起。6.5 实战三用range构造等差数列range是有序序列经常配合for循环造出有规律的数列。# 打印1到10之间所有偶数 for num in range(2, 11, 2): print(num) # 2 4 6 8 10 # 转成列表也方便 nums list(range(5)) print(nums) # [0, 1, 2, 3, 4]range(2, 11, 2)从2开始到11之前结束步长为2正好生成偶数序列。理解左闭右开后这种边界估算会特别快你只需要知道到11之前结束就永远不会包含11。6.4 练习建议别只看动手敲这篇内容对初学者来说算密度很高了最好的消化方式是把每个示例代码都不是复制而是自己敲一遍然后做这几件事把示例里的列表换成元组试试哪些方法会报错、哪些能用把字符串Python做一遍[::-1]手动算出结果再打印验证写一个程序输入一句英文输出每个单词的长度列表创建列表[5, 3, 8, 1, 9, 2]分别用sort()和sorted(reverseTrue)看差异。我自己当年学这章时最大的收获不是记住了每个方法而是理解了方法会返回None还是新序列这个分水岭。你只要看到就地修改的方法append、extend、sort、reverse都返回None看到生成新对象的方法sorted、reversed、切片都保留原序列不变80%的混淆都能消除。有序序列是Python数据操作的地基地基稳了后面学字典、集合、函数式编程都会轻松很多。下一篇我会接着聊字典和集合这类映射型数据结构看看它们如何和今天学的序列配合使用。先动手把今天的代码跑起来有问题评论区见。
返回列表