此文章参考廖雪峰大神的官网,地址:高级特性 - 廖雪峰的官方网站 (liaoxuefeng.com)
一、切片
- 在python的使用中,对于列表、元组的元素取值是非常常见的,例如:
注意:切片是顾头不顾
尾的
>>> L = ["aaa","bbb","ccc","ddd"] >>> print(L) ['aaa', 'bbb', 'ccc', 'ddd'] #如果想取前三个元素,可以这样: >>> print(L[0],L[1],L[2]) aaa bbb ccc #也可以使用for循环: >>> for i in range(3): ... a.append(L[i]) ... >>> print(a) ['aaa', 'bbb', 'ccc'] #但是明显前两种方法都比较繁琐,所以,python提供了切片(Slice)操作符,这样在取指定元素时可以变得更加简单,例如: >>> print(L[0:3]) #表示从第0个元素取到第3个元素,由于切片是顾头不顾尾的,所以是不包含第3个元素的,只取出了前三个元素 ['aaa', 'bbb', 'ccc'] >>> print(L[:3]) #默认:前面从0开始 ['aaa', 'bbb', 'ccc'] #有了切片这个方法后,就可以轻松取出一段元素,例如: >>> L = list(range(100)) #python3需要把range转换成列表,python2可以直接使用range赋值,range(100)表示0-99,同样顾头不顾尾 >>> print(L) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] >>> print(L[:]) #切片没有指定条件的话,会输出全部 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] >>> print(L[:10]) #取前10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> print(L[10:20]) #取10-19 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>> print(L[:-1]) #使用负数时,是从末尾开始算的,":-1"表示从0到末尾-1,所以是0-98 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98] >>> print(L[-10:-1]) #取倒数10到倒数1 [90, 91, 92, 93, 94, 95, 96, 97, 98] >>> print(L[-20:-10]) [80, 81, 82, 83, 84, 85, 86, 87, 88, 89] >>> print(L[-10:]) [90, 91, 92, 93, 94, 95, 96, 97, 98, 99] #如果想每个5个取一个值的话,可以这样做: >>> print(L[::5]) #表示每隔5个值取一个元素 [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
- 上面的是列表的取值案例,列表的切片最终输出的结果还是列表
- python的切片还可以引用在其他数据类型上,如元组、字符串等,例如:
#元组 >>> T = (1,2,3,4,5) >>> print(T) (1, 2, 3, 4, 5) >>> print(T[1:3]) (2, 3) >>> print(T[-3:]) (3, 4, 5) #字符串 >>> S = "ABCDEFG" >>> print(S) ABCDEFG >>> print(S[4:]) EFG >>> print(S[::3]) ADG
- 在自己实践过之后可以发现,元组切片后的输出还是元组,同样字符串切片后还是字符串
- 这里引用一个小案例,不使用strip函数,利用切片来去除首位的空格:
# -*- coding: utf-8 -*- def trim(s): if s[:1] != ' ' and s[-1:] != ' ': return s elif s[:1] == ' ': return trim(s[1:]) else: return trim(s[:-1]) if trim('hello ') != 'hello': print('测试失败!') elif trim(' hello') != 'hello': print('测试失败!') elif trim(' hello ') != 'hello': print('测试失败!') elif trim(' hello world ') != 'hello world': print('测试失败!') elif trim('') != '': print('测试失败!') elif trim(' ') != '': print('测试失败!') else: print('测试成功!')
这段代码通过切片去判断首位是否有空格,有的话会再次调用函数,并且引用第1元素或第-1元素来去除空格,然后继续做判断,直至首尾没有空格
二、迭代
首先要知道的是,通过for循环来遍历列表、元组、字符串等,这种遍历就称为迭代
需要注意的是,python中的迭代是直接通过for in的这种方式来完成的,而其他语言,比如C语言中的迭代是通过下标完成的,即i++这种方式。并且python的for循环还可以使用在其他的可迭代的数据类型上
除了列表、元组这种有下标的数据类型,python中的for循环还可以使用在其他没有下标的可迭代数据类型上,如字典:
>>> L = {"aaa":1,"bbb":2,"ccc":3} {'aaa': 1, 'bbb': 2, 'ccc': 3} >>> for i in L: ... print(i) ... aaa bbb ccc #但是直接这样循环的话,可以看到,只能取到字典的key,取不到value,取value的话可以这样: >>> for i in L.values(): #利用values函数来取到value的值 ... print(i) ... 1 2 3 #想要同时取key和value的话可以使用items函数: >>> for x,y in L.items(): ... print(x,y) ... aaa 1 bbb 2 ccc 3
要注意的是,因为字典的存储不是按照列表的方式顺序排列的,所以迭代出的结果有可能会不同
- 其他类型的一些循环:
#字符串 >>> S = "ABCDEFG" >>> print(S) ABCDEFG >>> for i in S: ... print(i) ... A B C D E F G #元组 >>> T = (1,2,3,4) >>> for i in T: ... print(i) ... 1 2 3 4 #列表 >>> L = [1,2,3,4] >>> for i in L: ... print(i) ... 1 2 3 4
- 在上面的案例中,可以看出,for循环只要是使用于可迭代的数据类型,都可以正常运行,那怎么看是否是可迭代的数据类型呢,python提供了相应的方法:
>>> from collections.abc import Iterable #导入collections.abc模块的Iterable方法 >>> isinstance('aaaa',Iterable) True #True表示为可迭代的数据类型 >>> isinstance([1,2,3],Iterable) True >>> isinstance((1,2,3),Iterable) True >>> isinstance(S,Iterable) True >>> isinstance(L,Iterable) True >>> isinstance(T,Iterable) True >>> isinstance({"aaa":1,"bbb":2},Iterable) True
- 引用其他的一些小案例:
>>> L = [1,2,3,4,5] >>> for x,y in enumerate(L): #使用enumerate函数可以输出“下标 + 元素”的格式 ... print(x,y) ... 0 1 1 2 2 3 3 4 4 5 #利用for循环来达到这种效果 >>> L = [(1,2),(3,4),(5,6)] >>> for x,y in L: ... print(x,y) ... 1 2 3 4 5 6 #取列表中的最大值和最小值: # -*- coding: utf-8 -*- def findMinAndMax(L): if L == []: return (None,None) else: min=L[0] max=L[0] for i in L: if i > max: max = i if i < min: min = i return (min,max) if findMinAndMax([]) != (None, None): print('测试失败!') elif findMinAndMax([7]) != (7, 7): print('测试失败!') elif findMinAndMax([7, 1]) != (1, 7): print('测试失败!') elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9): print('测试失败!') else: print('测试成功!') 先创建一个参照数,然后循环列表依次对比参照数,从而取出最大值和最小值
三、列表推导式
- 列表推导式,也叫列表生成式,(List Comprehensions),是python内置的一个简单、强大的可以用来创建列表的生成式
- 下面举一些简单的例子进行对比:
#使用普通的方式创建列表 >>> L = list(range(1,11)) >>> print(L) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #如果要使用列表生成[1*1,2*2,3*3,...,10*10]这样的可以使用for循环 >>> N = [] >>> for i in L: ... N.append(i*i) ... >>> print(N) [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] #感觉还是有些繁琐,那使用列表推导式该怎么写呢: >>> [i * i for i in L] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] #可以看出推导式明显比上面的普通方法更加简单、便捷,下面来解析一下这个推导式 [i * i for i in L] 在我的理解中,其实就是直接操作列表的值,使用for循环去指定要操作的列表,把里面的值拿出来,然后直接在开头对值进行操作,从而达到想要的效果 #如果这个不是很好理解那么还可以换一个: >>> L = ["Aaa","Bbb","Ccc"] >>> [i.lower() for i in L] ['aaa', 'bbb', 'ccc'] 使用lower函数把字符变成小写
注意:列表推导式是创建一个新列表,不会修改原列表的元素
- 在熟悉基本的列表推导式后,我们还可以使用if语句、多层for循环来创建列表推导式,例如:
#只想输出1-10的偶数 >>> L = list(range(11)) >>> print(L) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> [i for i in L if i % 2 == 0] [0, 2, 4, 6, 8, 10] 可以发现if语句是写在末尾的,如果想要加else呢?就需要换一下位置了 >>> [i if i % 2 == 0 else -i for i in L] [0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 这个推导式的意思是除以二余零的直接输出,否则变成负数输出 #还可以使用多层for循环来进行交叉输出 >>> [x + y for x in "abc" for y in "efg" ] ['ae', 'af', 'ag', 'be', 'bf', 'bg', 'ce', 'cf', 'cg'] >>> [x + y + i for x in "abc" for y in "efg" for i in "hij"] ['aeh', 'aei', 'aej', 'afh', 'afi', 'afj', 'agh', 'agi', 'agj', 'beh', 'bei', 'bej', 'bfh', 'bfi', 'bfj', 'bgh', 'bgi', 'bgj', 'ceh', 'cei', 'cej', 'cfh', 'cfi', 'cfj', 'cgh', 'cgi', 'cgj'] 但是一般只会用到两层循环,两层以上的就很少用了
注意:在列表推导式中,for后面跟的if是过滤条件,不能跟else否则会报错,而if..else是表达式,所以需要写在前面
- 之前说过for循环只要是可迭代的数据类型都可以使用,那么同样的列表推导式也可以进行引用
#引用字典 >>> d = {"a":1,"b":2,"c":3} >>> [x + '=' + y for x,y in d.items()] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp> TypeError: can only concatenate str (not "int") to str 在引用字典时,发现报错了,原来是因为int类型导致的报错 >>> d = {"a":"1","b":"2","c":"3"} #提前转变为字符串类型后就可以正常输出了 >>> [x + '=' + y for x,y in d.items()] ['a=1', 'b=2', 'c=3']
注意:在使用列表推导式时,要注意列表、元组、字典元素的数据类型,比如要使用lower函数转换大小写时,如果列表中元素有int类型的,那么就会报错,可以使用内建函数isinstance来判断一个变量是不是字符串,例如:
>>> L = ["Aaa","Bbb",111,222,"Ccc",None] >>> [ i.lower() for i in L] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp> AttributeError: 'int' object has no attribute 'lower' 这样会报错,所以可以加一个isinstance的过滤条件 >>> [ i.lower() for i in L if isinstance(i,str) == True] ['aaa', 'bbb', 'ccc']
三、生成器(generator)
通过上面的列表推导式,我们可以直接创建一个列表,但是列表的容量肯定是有限的,如果要创建一个100万个元素的列表,不仅会占用很大的存储空间,而且,如果说里面的元素有百分之90都用不到的话,那么就会浪费很大的空间,而生成器就可以解决这个问题
生成器可以根据某个算法在循环过程中不断的推算后续的元素,这样就不用创建完整的列表,从而节省大量的空间,而这种一边循环一边生成的机制,就叫生成器==(generator)==
下面是创建生成器的案例:
#创建生成器只需要把列表推导式的[]换成()即可: >>> L = list(range(11)) >>> [ i * i for i in L] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] >>> ( i * i for i in L) <generator object <genexpr> at 0x0000022979D4C0B0> 可以发现生成器不会直接输出结果 #输出生成器的结果,使用next() >>> g = ( i * i for i in L) #先赋值 >>> g <generator object <genexpr> at 0x0000022979D4DA80> >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) 36 >>> next(g) 49 >>> next(g) 64 >>> next(g) 81 >>> next(g) 100 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration #可以看到每次使用next就会输出循环后续的结果,直至循环完成,但是这样一个一个敲太麻烦了,而生成器也是可迭代的数据,所以我们可以使用for循环 >>> g = ( i * i for i in L) >>> for x in g: ... print(x) ... 0 1 4 9 16 25 36 49 64 81 100
- 从上面的例子可以看出,生成器是可以直接使用for循环调用的,所以说在实际使用生成器时,几乎不会使用next去依次调用
- 下面引用一个案例,实现斐波拉契数列
# -*- coding: utf-8 -*- def fbl(n): x,y,i = 0,0,1 while x < n: print(i) y,i = i,y + i x = x + 1 return 'done' fbl(6) #运行: 1 1 2 3 5 8 #解析 定义x为循环的次数,每次循环会加一,当x等于函数传入的参数n时,while循环就会结束 而y和i在每次循环时,y会赋予i的值,而i会赋予y+i的值,这样实现的效果就是: 1:y=1,x=0+1=1 2:y=1,x=1+1=2 3:y=2,x=1+2=3 4:y=3,x=2+3=5 5:y=5,x=3+5=8 更好理解的就是x2=y1+x1,x3=y2+x2,x4=y3+y3这种规律
- 从上面的案例中可以看出,斐波拉契数列就是根据上一个运算结果推算出下一个运算结果,这就比较像生成器的特性了
- 而上面定义的函数只需要把print(i)改为yield i就可以变成一个生成器:
# -*- coding: utf-8 -*- def fbl(n): x,y,i = 0,0,1 while x < n: yield i y,i = i,y + i x = x + 1 return 'done' f = fbl(6) print(f) for a in f: print(a) #运行: <generator object fbl at 0x000001F1B9BED8C0> 1 1 2 3 5 8 #解析: 这里的函数和上面的普通函数只改变了一个关键字,但是因为这个关键字,使函数执行的流程发生了改变,上面的普通函数就是依次的顺序执行,遇到return语句或是到了最后一行语句就会返回,而这里的函数变成了只有在遇到next函数或for循环时才会在yield关键字的语句位置进行返回,再次使用next或继续for循环时,会从上次yield语句的位置继续执行,直到再次遇到yield语句然后再次进行返回,这里的函数其实就变成了生成器函数 #如果这里的解析比较难理解的话,可以看一下这个简单的示例: # -*- coding: utf-8 -*- def test(): print('aaa') yield print('bbb') yield print('ccc') yield a = test() next(a) print("1") next(a) print("2") next(a) print("3") next(a) #运行: aaa 1 bbb 2 ccc 3 Traceback (most recent call last): File "c:\Users\12488\Desktop\python\pachong.py", line 17, in <module> next(a) StopIteration 这里其实就更直观了,在第一次调用时,在第一个yield停下了,从而输出了aaa,第二次从第一个yield位置继续执行在第二个yield的位置停下,从而输出的bbb,第三次从第二个yield的位置继续执行,在第三个的yield位置停下,从而输出了ccc,第四次的时候,因为已经没有yield关键字了,所以就报错了