人生总得有目标,各位测试小伙伴的目标是什么呢 ( ̄ε ̄*)
我的目标是做测试经理,根据我的观察,测试经理想要工资高,代码是一定要懂的。
所以二狗利用休息时间,在这里挂一篇非常基础,基础到不能再基础的Python学习笔记。
Python安装啥的到处都有,步骤千篇一律,我就不写了。
我们直接从代码开始
注:Python里的所有代码相关的符号,皆为英文的符号,切忌用中文符号。
我们暂且将程序看成两个部分。
一个部分,是代码,也就是我们敲出来的那部分。
另一部分,是结果,也就是运行了我们的代码后,用户看到的部分。
举个栗子:
可以发现,图中绿色箭头指向的绿色字体“abcde”和下面的红框标记出来的字母“abcde”是一样的。
绿色那部分,就是我们敲的,是给我们自己看的。
而红框那部分,就是输出的结果,是给用户看的(当然你自己也要作为用户验证看看)。
敲代码的人,我们暂时称为程序员,也就是你。
程序员直接在框里输入“abcde”,用户那边是看不到的,必须要借助print()命令,电脑识别到你的这条命令,才知道你要做什么,然后根据程序员给出的命令,将输出的内容反给用户,即——(程序员敲代码——电脑识别并运行代码后给出结果——用户观看结果)这样的顺序。
好了,现在你知道了输出命令——print()是什么——一个用来输出给用户看的命令。
注意print()的结构,括号需要英文式的括号。至于引号的作用,我们往下看。
前面提到print()里的引号的作用时卖了个关子,因为这涉及到了一种叫做字符的数据类型(当然,字符不是python的数据类型,字符串才是。字符串就是一堆字符连接起来的串)(纯小白不好理解的话……你就暂时把一个字母当做英文吧——当然这种说法是错的,现在这样也是方便你理解)。
此次要讲的重点为int、float、string这三个数据类型。
int即整数类型(通常我们简称为整型,而不是整数),比如1、2、100这种。python3里的int理论上是没有范围的(大多数语言int都会有一个极限,例如c语言int最大只能到21474836473,即2的31次方-1);
float即浮点数类型(简称浮点型),即带有小数点的数,如1.2,2.8这些;
string即字符串,例如“abcde”就是一个字符串,一个字符串是由很多个字符,串在一起形成的,字符串必须要在引号里。
可以看到,引号里是什么,输出的结果就是什么,引号里的东西都是字符串,即便是数字,也不会拥有数字应该拥有的意义(当然会有特殊的情况,暂时先不讲)
而print()不加引号,那么这些数据该是什么,就是什么。例如3+6是int类型,那么他们就会拥有数字本身的意义,直接相加,得到9。
作为一名用户,我不能永远只看,而不自己操作吧?
用户如何操作呢?这里需要用到两个知识点,一个是变量,一个是输入。
这里我把变量与输入一起讲,方便理解。
输入的关键代码是input(),和输出print()刚好对应。
如图,绿1是用户自己输入的名字,绿2是计算机输出的结果,与上面的红1红2对应。
并且,必须要输入完成后,才会跳到下一步。
而红1的name,就是一个变量,这个name只是一个变量名(也就是说它只是个名字,非常的好理解),你可以取名为a,b,甚至hjsdfkhdsgh一串乱七八糟的东西都行。
红1这一行的意思就是,将你输入的数据(字符串、数字都行)赋值到name这个变量上,比如你输入的kunkun,那么name的值就是kunkun。
注意,上面“=”的意思是赋值,“==”才是等于的意思。
现在,我们对变量和输入有了一个大概的了解,接下来就再详细讲讲。
如上,我们将变量命名为name,那么我们可以将变量命名为1name,2name吗?或者命名为%%%吗?
这是不行的,变量名有属于他自己的基本规则:
答案是①②③⑤⑦
再来看看输入。
如果a输入字符串abc,b输入字符串efg,那么print(a+b)得到的结果是什么呢?
"+"号可以将字符串连接起来输出
那如果我们输入的是数字呢?
上面有讲到,print(3+6),输出的结果是9,那么如果将3和6赋值给a和b呢
Amazing!得到的结果居然是36?这是为什么?
原因是,使用input()时输入的值,只能是字符串,即便你在键盘上输入的是数字,但是通过input()输入后,还是会变成字符串。
而"+"号会将字符串连接起来,故得到的结果是两个字符串的连接,而非数字相加。
那么如果我们想要让输入的数字变为整型,而不是字符串,应该怎么做呢?
就是这么简单。用int()强行将字符串转换成整型就可以了。
我们还可以写的更简化一点
输入的很好看懂,引号里是什么,用户就会看到什么。
输出的时候,可以看到引号里的内容和实际输出内容不一样了。这里就是前面在讲print()时说到的特殊情况之一了。
引号里的%d,代表这里将会输出一个整型,而这个整型的值,由引号外面决定。
引号外面也有一个对应的%号,%后面的变量的值会传到前面的%d中。
输出这里,引号里用%d代表整型,%f代表浮点型,%s代表字符串。引号外,%号后接变量。
很好看懂。引号外的百分号后面如果有多个变量,则要用括号括起来,将这些变量全部放在括号里。并且每一个变量,用逗号隔开。
不过需要注意的是括号要打对(新手非常容易出错,如果报错,请反复检查)。
这种方法看起来就非常简单了,有手就行。
不过这里要注意,这样输出的时候,你输出的提示和变量直接会被空格隔开。那么如何取消空格呢?
简单,末尾加一个 sep=“” 就够了,如图
第三种方法:
利用函数.format()——函数可以理解为可以直接拿来实现某种功能的命令,例如前面的输入输出函数。
引号里给出输出的格式,并给出变量所在的位置。
引号外接.format(),括号里的变量要与前面引号里的{0},{1}对应上(记住,是从0开始,然后依次是1、2、3的顺序往后)。
非常的好用。
可以看到,每一个print()在输出结束的时候都会自动换行。
如果我们不希望他换行呢?我们只需要在末尾添加一个命令end=“”
在print()输出函数里,它的默认参数其实是end=“\n”,\n表示换行,如果你想连续换两行,你还可以这么写:
另外,末尾只写一个end="\n"是没有用的,因为你相当于把它的默认值重新写了一遍。要换两行,就要写两个\n。
python3有6个标准数据类型:
Number支持四种不同的数值类型,分别是int、float、bool、complex(复数)
int和float前面已经讲过了,分别是整型和浮点型。整型和浮点型放在一起计算的时候,整型会变成浮点型;
bool只有两个值,即True和False(注意大小写),代表真和假。在这里,所有的非空对象都是真,所有的空对象都是假——例如空字符串,空列表、空元组,None常量等都是假,这里先做了解;
complex新手先不看,看了也白看。
前面粗略的提到过,string就是字符串,
我们用双引号或者单引号来定义一个字符串,也可以直接定义一个空字符串
字符串需要被单引号或者双引号括起来,特殊字符需要用反斜杠转义,例如前面提到的\n。
字符串可以用+号连接,用*号重复,例如:
字符串的索引以 0 为开始值,-1 为从末尾的开始位置。
例如"abcde"这个字符串:
索引有点像下标,可以标出具体的位置;
另外,不止是字符串,之后涉及到的其它的内容,例如列表、元组等,索引都是从0开始;
而反过来就是-1开始(毕竟-0和0是一个意思);
两个字符串可以直接用"+"号连接组成一个新的字符串;
字符串可以使用切片操作——见下面第5大点;
可以使用len()来计算字符串的长度
当然,len()可以计算的不只是字符串,元组、列表等都可以。
定义就不讲了,新手不一定看得懂,感兴趣的可以百度。
我们直接开始举例:
我们用英文的中括号——[]——来定义列表,和字符串一样,也可以直接定义一个空列表。
如上图所示,列表的值可以是很多不同的类型,可以是字符串、数字、变量、甚至是其它的列表。
你可以将列表定义为一个有序列的格子,这个格子里可以放各种各样的数据,甚至可以放别的格子(别的格子甚至可能比你的这个格子大);
而每一个逗号,相当于一个隔板,将两个格子分隔开。
也就是说,对于列表,每一个逗号分隔开的就是两个不同的数据。而列表的长度,取决于有多少个数据——即逗号个数+1。
列表取值的方式和字符串一样,可以直接用索引取值,也可以用切片的方式。
当然,列表也可以使用 “ + ” “+” “+”号连接, “ ∗ ” “*” “∗” 号重复。
字符串的元素是不可改变的,但是列表可以
但是不能将整个列表强制转换成字符串,只能将列表里的值强制转换成字符串
图中的命令type(),用于查看括号内的对象类型。如图中查看a[0]的对象类型为int,b[0]的对象类型为str。
在日常练习中,type()经常使用,请记住它。
列表里面的变量、列表、字符串等会保留他们本来的特性,例如:
lista[5]的值便是stra变量,而lista[5][0]就相当于stra[0]
即输出的值为a
元组(tuple)与列表类似,不同之处在于元组的元素不能修改(其实,可以把字符串看作一种特殊的元组,毕竟它们都不能直接修改)。
定义列表用a=[]
定义元祖用a=()
列表里只添加一个值时,用a=[“a”]
元组里只添加一个值时,用a=(“a”,)——注意,这里并没有打错,如果只添加一个值,后面就要跟一个逗号。
能看到a、b、c三个变量的类型分别是tuple、int和str。只有第一个加了逗号的才是元组。
列表和元组都可以用多个变量来接收他们的值,值会按顺序赋给这些变量。
其它的地方(例如+、 ∗ * ∗)和列表一样,就不赘述了。
集合(set)是一个无序、不重复元素的序列。
基本功能是进行成员关系测试和删除重复元素。
可以使用大括号 { } 或者 set() 函数创建集合。
注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。
由于集合是无序且策不重复的,所以每次运行程序的时候,你看到的顺序可能都不一样(这里说的是视觉上的顺序,因为逻辑上它是无序的)。
set可以进行集合运算:
列表是有序的对象结合,字典是无序的对象集合。
两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取(这句话是复制粘贴的,我也不知道偏移是啥意思。我将之理解为下标)。
字典是一个无序的键值对集合,什么意思呢?
键:值。
键是唯一的,类似于学生的学号。
值可以不唯一,类似于学生的姓名。
一个学号对应一个学生,学号唯一,学生的姓名不唯一,毕竟允许重名。
不同的是,学号是有顺序的,这也是为了方便统计。
但是键是无序的,我们是直接根据键来找值,不需要顺序。
如果新的键和旧的键同名,那么旧的键会被移除。
如图所示。
在stu这个字典中,[2.5]不再代表下标,而是代表字典的键。而"坤坤"自然就是键2.5的值了。
每一个键值对都用“,”隔开,这没什么特殊的。
我们也可以只输出某一个键的值
keys就是键(我们以前英语学的是钥匙)
values就是值(以前学的也是值)
学这东西还得靠举例。
我们直接摆出例子:
例如字符串a=“abcdefg”,共有七个字符,那么它们的索引就是0-6。
我们如何获取字符串a中的一部分呢?例如我想要b,那么我可以写a[1],如果我想要g,我就写a[6]或a[-1]。
但是如果我想要bcdefg这样一个很长的字符串,而不是一个单独的字符,怎么办?
使用切片!
Look!
当我们使用a[1:]这个命令的时候,我们就得到了字符串a从索引1开始的剩余字符串,如果是a[0:]呢?那写了等于没写,因为索引本身就是从0开始
现在,对切片已经有了一个模糊的概念了。我们再来详细说说。
切片的表达式为[start : end : step],其中:
start表示起始索引,默认值为0;
end表示结束索引,默认到无值可取的时候结束;
step表示步长,步长不能为0,不写时默认值为1(负数则需要反着数)
我们再来看几个例子:
1.切片包含start的值,但是不包含end的值——相当于数学中的[start,end)
当然,不止是字符串可以切片,任何有序列的都可以进行切片,例如列表:
(根据之前学习内容的规律来看,
"函数()"数据类型写在括号里的叫函数
".方法()"数据类型写在.前面的叫方法
并且:
函数有返回值,不会改变当前数据类型的值
大多数方法无返回值,而是直接改变当前数据类型的值-仅仅是大多数,不是全部)
这里只记录我觉得初学者最常用的,想学完整点的老哥可以去隔壁百度( ̄▽ ̄)~*
del a之后,变量a不再有意义。
也可以用于删除某个元素。如下:
注意:
①max()只能比较同样的数据类型,例如列表里如果同时存在数字和字符串,那么使用max就会报错
②字典使用max()时,返回的是字典的键,而不是值
min()同理
类似的还有.sort()。但是.sort()要谨慎使用,因为它没有返回值,而是直接对原本的数据进行修改。
那么问题来了,c=a.copy()与c=a有什么区别呢?最后得到的都是同样的值,而且后者写起来还更简便,我们为什么要像前者一样写呢?那么请看下图:
如图所示,
a1=a.copy():[1,2,3,4]
b=a:[1,2,3,4]
此时a[0]=2,a1的数据未变,b的数据却变了,为什么呢?
因为a1是复制的a,虽然数据都是[1,2,3,4],但是这两个[1,2,3,4]却是不同的东西。
而b是直接=a,相当于在[1,2,3,4]这个列表上贴了a的标签,然后又在这个列表上贴了一张b的标签,a和b都是指向了同一个列表,所以a的数据发生改变时,b的数据也会发生改变,b会保持和a一样。
那么如果我像下图这样写,b的数据会跟随a一起改变吗?
答案是不会的,因为这个时候a已经指向了一个新的列表,但是b还是在原来的那个列表上
该元组里有两个"b",但是该方法只找第一个
如果忘记数据应该插到哪里,想想.insert(0)就是插入到最前面就好了。
该方法是唯一一个既能修改列表又能返回元素值的方法(除了None),pop 和 append 方法是 Python 中数据结构的出栈和入栈,如果追加(append)刚刚出栈(pop)的值,得到的还是原来的列表
(懒得截图了)
这个数据类型能用的,别的也能用。没有(指我在学习的过程中没发现)独属于它的函数、方法或语句。
如果元素已存在,则不进行任何操作
别忘了集合是不重复且无序的
.add()能添加一个简单元素,但是却不能添加复杂的类似于列表里的元素
但是.update()能将列表、元组和集合里的元素添加到当前集合里
注:集合s添加元素后,我刷新了很多次,排列出来的顺序依旧是1-6。但是请记住!这只是视觉上的顺序。集合连索引都没有,你并没有办法通过索引找到他,又怎么能叫有顺序呢?
这两个方法都是移除,但是有一定的区别。
.remove()移除的时候,如果括号中的元素在集合里不存在,则报错
.discard()移除的时候,如果括号在的元素在集合里不存在,不会报错
可以看到
只移除第一个集合中与括号里的集合重合的元素,括号里的集合不变
该方法无返回值
没有相同元素就返回 True,
有相同元素就返回 False。
包含则返回 True,
不包含则返回 False。
即集合A中的所有元素都能在集合B中找到
上面的A和B换一下位置就是这个方法的功能
移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。
和上一条方法的区别在于:
上一条方法是返回值为不重复元素的集合
这一条方法是直接将前面的集合变为不重复元素的集合
字典的键为seq序列里的值,每个键的值都是value。
不写value的话,每个键的值就是"None"
执行结果为返回一个新的字典,加"="就是赋值
该方法和直接用索引的办法有个最大的区别
直接用[键]来寻找值,如果该键不存在,那么会保存,然后程序停止运行
而用dict.get(键)来寻找值,如果该键不存在,则返回None,程序继续运行
正常执行的程序中,我们肯定不希望程序停止运行,所以建议多用这个方法
当然,这样也有个弊端,那就是遇到错误的时候(我们的键不见了),我们却不知道这里有错。
所以,我们还可以给命令里添加一些提示
可以看到,当字典里有这个键的时候,正常输出这个键的值
当字典里没有这个键的时候,就会由后面添加的提示来代替"None"
可遍历举例:
这里有一个小坑。
输出写成print(key,value)
或者print(key+"="+str(value))
都可以正常运行
但是写成print(key+"="+value)
就会报错
原因是返回的key和中间的"="都是str类型,而value是int类型。不同类型是不能相加的。
遍历前面还没讲过,下面第7点会讲
if,条件判断命令,方便理解,我们也可以直接翻译为“如果”。
python执行代码的顺序是从上往下
先执行a=1
,此时a的值就是1了。
再执行 if a==2:
——这里面“==
”的意思是等于。这句话可以理解为,当a的值为2的时候,执行缩进的命令——print("a的值为2")
如果没有满足a==2的条件,那么就会跳过print(“a的值为2”)这一步,直到满足if的条件。
除此之外,还有if-else命令
这段代码的意思是,如果没有满足if里的条件,就直接执行else里的代码。
我们还可以进行更详细的判断
elif,即else if的缩写,意为否则如果。
这段代码肯定能看得懂,太简单了,不多bb。
总结下来就是,满足哪个条件,就执行哪个条件下面缩进的代码。
随便找个代码举例
上面的if a==20:
这段代码,实际上可以拆成两部分看,“if :”、“a==20”这两部分。前面是判断条件,后面是需要判断的东西
a==20
就是我们需要判断的东西,如果成立,返回结果“True”(代表真),如果不成立,返回结果“False”(代表假)。
也就是说,if真正判断的是if True,if False这两个。
当if后面为True时,执行它的缩进代码
当if后面为False时,跳过本次代码执行
另外,在Python中,1也代表真,0也代表假
Python中有两种循环,一种是for…in循环,另一种是while循环
如果我们有一个列表a=[1,2,3,4,5],我们要如何将这5个值依次输出呢?
直接print?那显然是不行的,因为会一次性输出整个列表,而非依次输出这些值。
上面这种方法自然是可以的,但是这只适用于比较小的列表。如果列表里有成百上千个值呢,还要这样写吗?
这个时候,我们就能用到我们的循环了。
这里面,i是一个变量,每一次循环,i都会从a里按顺序取一个值。
第一次,i=a[0]
第二次,i=a[1]
……
这样就可以达到循环的效果。
这里要学一个很好用的函数,range()
我们根据代码来看看它的用法
可以看到,range(6)生成的是从0到5的整数序列,用上这个函数,我们就不需要一个数字一个数字的打了。
如果想将它变成列表,只需要加个list转换一下
联系上前面学的条件判断,我们能做出一个只显示偶数的输出
这里面continue
的意思,为跳过本次循环的所有代码,直接进行下一次循环,即跳过i==0
的循环,直接进入到i==1
的时候
除了continue,还有一个同样好用的语句break。
continue是跳过本次循环,而break是直接退出循环,即本次条件里的后续所有循环都不再进行。
在if i%2==0:
这段代码里,%是余的意思。例如5%2余1,即5除以2的结果为:等于2并余1(余0的便是偶数)。
在Python里,乘号为,除号为/,余数为%*
Python的第二种循环为while循环
一开始,sumn的值为0,满足while里的条件(sumn<10),进入循环
每一次循环,sumn+1并输出一次值,直到sumn=9时,进入循环,随后sumn=sumn+1,即sumn的值变为了10,然后输出。这次循环进行完了,下次循环时,不满足sumn<10的条件,跳出循环。
前面在学第6点的时候,有接触到一些内置函数。
内置函数,就是可以直接拿来使用的函数,例如用abs()求绝对值
如果传的参数不对,就会报错,并且会告诉你是哪里错了
但是其实我们也可以把函数名赋给一个变量,相当于给这个函数起了一个别名:
前面学了调用内置函数,但是除了调用Python本身就有的函数外,我们还可以自己定义函数。
定义函数的格式为:
def 函数名():
如此一来,就可以直接通过调用函数名,而直接使用函数里面的代码。
如果想先定义,但是暂时用不上这个函数,我们可以这样写
pass在其它地方也能使用
如果不写pass,而是空着,代码就会报错
另外注意:
函数体内部的语句在执行时,一旦执行到return,函数就会执行完毕,并返回结果。
如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。
在Python中,函数必须要调用,才能够使用函数里面的代码。
也就是说,上图代码的执行顺序为:
1.m=10
2.n=20
3.def sumn()这里只执行函数名,但是不调用函数里面的代码
4.print()
直到读到print()的时候,才会调用sumn()函数,然后将m和n传值进去,分别对应a和b。
然后return返回a+b的值,也就是30。返回后,print()输出返回的值30。
函数也可以一次性返回多个值:
但是实际上这只是一个假象。Python函数返回的仍然是单一值。
可以看到,实际上返回的是一个元组。前面在学第4节的元组时有讲过,“列表和元组都可以用多个变量来接收他们的值,值会按顺序赋给这些变量。”,所以我们可以采用多个变量来接收函数返回的元组。
在这段代码里,sumn(a,b)里的a和b就是位置参数
调用函数时,传入的两个值按照位置顺序依次赋给参数a和b
当我们计算a+b时,需要传入两个参数才能使用。如果我们只传入一个参数,就会报错。
如果避免这种报错呢?我们可以给b设置一个默认参数,例如0
这样,当我们只传递了一个值进去时,这个值会按顺序给a,而b有默认值0,最后返回的值是a+0,就不会报错了。
我们甚至可以一个值都不传,全部用默认值
但是,设置默认参数的时候,要记住必选参数必须在默认参数前面,否则就会报错。
我们还能传输列表、元组等
这里面,I+=i
就是I=I+i
的简写
可变参数就是指,传入的参数是可变的,可以是1个、2个,n个,或者0个。这些可变参数在函数调用时自动组装为一个tuple。
可变参数的符号为*
这样,我们就可以像传几个值进去,就传几个值进去。
如果这时候刚好有个列表a,我想把列表a的值传进去呢?
重新写一次sumn函数?
那太麻烦了,我们可以只在print()里的参数做点手脚:
在列表a前加一个*号,就能让该列表的值一个一个传输进去
这种写法很有用,也很常用。
前面说到,可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
关键词参数,用符号**表示
通过这种方式,可以传入任意个数的关键字参数(包括0个)
可以看到,后面传入的关键字参数都是以字典的形式传入的。
和可变参数利用“*”调用列表、元组一样。关键字参数也可以利用“**”调用字典:
在学关键字参数的时候,如果是用户输入,就代表他们想输入多少关键字,就输入多少关键字,甚至会输入很多无用的东西。
所以我们可以用到这个功能——命名关键字参数。
可以看到,“命名关键字参数”并不像“可变参数”和“关键字参数”那样,可以少传和多传。
它更像是位置参数,必须每个参数都传入值。
不同的是,位置参数只需要传入值,而命名关键字参数,要把参数名和值一起传入。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
总结下来就是……
只有参数时,为“位置参数”,必须传入值才能用;
参数有默认值时,为“默认参数”,可以传值,也可以不传。不传值就为默认值;
带 ∗ * ∗号的参数为可变参数,想传几个值传几个值;
带**号的为关键字参数,想传几个“参数名=值”就传几个“参数名=值”;
*,后面有参数名,或者可变参数后面还有其它的“位置参数”,则这些“位置参数”实际上为“命名关键字参数”。传递此参数时,必须使用“参数名=值”的格式。
前面已经讲过循环了,放在这里是用作对比,方便理解
例如有abc三个房间
循环就是指,我从a到b再到c,走完一次,然后再走一次abc,走完之后再走abc……,我翻来覆去的走abc,就是循环;
遍历就是指,共有abc三个房间,那么我就要把a走了,把b走了,把c走了。即每一个房间我都要走,就是遍历(即走遍每一个房间就是我的目的);
而迭代是指,我通过走路的方式走完a,然后通过走路的方式走完b,再通过走路的方式走完c,这个“走路”就是迭代(即“走”遍每一个房间的“走”,是我的手段)。
事实上,大部分的遍历、迭代、递归都是循环。
循环(loop) - 最基础的概念, 所有重复的行为
递归(recursion) - 在函数内调用自身, 将复杂情况逐步转化成基本情况
(数学)迭代(iterate) - 在多次循环中逐步接近结果
(编程)迭代(iterate) - 按顺序访问线性结构中的每一项
遍历(traversal) - 按规则访问非线性结构中的每一项
这些概念都表示“重复”的含义, 彼此互相交叉, 在上下文清晰的情况下, 不必做过于细致的区分。
这里面的递归,我也写不出更简单易懂的教程(主要是本来也不难,跟着代码走一遍就理解了),所以就懒得写了。各位可以自行百度~
但是除了这种,还有一种更简单的写法
print([i*i for i in range(1,6)])
前面i*i就是要生成的元素
后面接for循环
我们还能在生成式里写两层循环:
根据结果可以看出,前面的for循环在外,后面的for循环在里,相当于:
虽然可以写n层for循环在列表生成式里,但是建议不要这样做,因为很不利于阅读。
我们还能给列表生成式添加条件判断
但是for后面的if不能带else,否则就会报错。
但是如果if写在for前面,那就必须带else了,否则就会报错
这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。
如果只有i if i%2==0,他是无法算出结果的,因为他不知道i%2!=0的时候,i应该等于多少。所以必须加上else,这个表达式才算完整。
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
可以看到,列表和元组输出显示的结果并不一样。原因是用列表生成式生成的元组其实是一个生成器。
那么我们如何取到生成器里面的值呢?
使用next()函数获取生成器的下一个返回值:
前面讲过,生成器保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然,每次使用都用next()也很麻烦,所以我们其实也能使用for循环:
事实上,通过循环来迭代生成器更常用,而且不用担心StopIteration的报错问题。
generator函数和普通函数的执行流程不一样。
普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回并停止执行。
而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
为什么前面三次使用next()的时候,返回的值却都是5,而后面三次使用next()的时候,却能正常遍历呢?
原因是每次pt()都会创建一个新的对象(现在还没学到对象,暂时可以理解成,每次都会创建了一个新的从第一步开始执行的工具),上面的三个print()里面实际上是创建了三个完全独立的生成器,next()的时候都只执行了第一步yield n。
所以通过o=pt(5)的方式,创建了一个独立的生成器,每次next()的时候,都是在o这个对象里执行yield。
当然,我们依旧尽量不用next()的写法,很麻烦:
并且使用循环,还可以少写点步骤,也不会重复创建新的生成器:
这里面的print(i)都是在同一个生成器下执行的。
Iterable-前面是大写的i
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
用该函数查看列表[]是否是迭代器对象。
上面的from…import…在使用isinstance()前是必须要写的。这个是模块相关的知识,后面再讲。
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator(可以理解为生成器是一种特殊的迭代器,而list等虽然是迭代器,但却不是生成器)。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。