开始学习图形化程序设计
>>>import turtle #导入turtle模块
>>>turtle.showturtle() #显示箭头
>>>turtle.write("高琪") #写字符串
>>>turtle.forward(300) #前进300像素
>>>turtle.color("red") #画笔颜色改为red
>>>turtle.left(90) #箭头左转90度
>>>turtle.forward(300)
>>>turtle.goto(0,50) #去坐标(0,50)
>>>turtle.goto(0,0)
>>>turtle.penup() #抬笔。这样,路径向上
>>>turtle.goto(0,300)
>>>turtle.pendown() #下笔。这样,路径向下
>>>turtle.circle(100) #画圆
自己练手做了一个奥运五环的图标
#绘制奥运五环
import turtle
turtle.width(10)
turtle.color("blue")
turtle.circle(50)
turtle.penup()
turtle.goto(120,0)
turtle.pendown()
turtle.color("black")
turtle.circle(50)
turtle.penup()
turtle.goto(240,0)
turtle.pendown()
turtle.color("red")
turtle.circle(50)
turtle.penup()
turtle.goto(60,-50)
turtle.pendown()
turtle.color("yellow")
turtle.circle(50)
turtle.penup()
turtle.goto(180,-50)
turtle.pendown()
turtle.color("green")
turtle.circle(50)
可以用IDLE运行出来一个图片,感觉很有意思,有兴趣的可以试一试
引用
在Python中,变量也成为:对象的引用。因为,变量存储的就是对象的地址。
变量通过地址引用了“对象”。
变量位于:栈内存。
对象位于:堆内存。
Python是动态类型语言
变量不需要显示声明类型。根据变量引用的对象,Python解释器自动确定数据类型。
Python是强类型语言
每个对象都有数据类型,只支持该类型支持的操作。
变量和简单赋值语句
变量的声明和赋值
变量的声明和赋值用于将一个变量绑定到一个对象上,格式如下:
变量名 = 表达式
最简单的表达式就是字面量。比如: a = 123。运行过程中,解释器先运行右边的表达式。
生成一个代表表达式运算结果的对象:然后,将这个对象地址赋值给左边的变量。
删除变量和垃圾回收机制
可以通过del语句删除不在使用的变量。
链式赋值
链式赋值用于同一个对象赋值给多个变量。
x=y=123 相当于:x=123;y=123
系列解包赋值
系列数据赋值给对应相同个数的变量(个数必须保持一致)
>>>a,b,c=4,5,6 相当于:a=4;b=5;c=6
[操作]使用系列解包赋值实现变量交换
>>> a,b=1,2
>>> a,b=b,a
>>> print(a,b)
2 1
常量
Python不支持常量,即没有语法规则限制改变一个常量的值,我们只能约定常量的命名规则,以及在程序的逻辑上不对常量的值作出修改。
>>> MAX_SPEED = 120
>>> print(MAX_SPEED)
120
>>> MAX_SPEED=140 #实际是可以改变的,只能逻辑上不做修改
>>> print(MAX_SPEED)
140
最基本内置数据类型介绍
每个对象都有类型,python中最基本的内置数据类型:
1.整型
整数,2345,,10,50
2.浮点数
小数,3.14或者科学计数法314e-2
3.布尔型
表示真假,仅包含:True、False
4.字符串型
由字符组成的序列。“abc”,"sxt","尚学堂",“百战程序员”
数字
Python支持整数(如:50,520)和浮点数(如:3.14,10.0,1.23e2),我们可以对数字做如下运算。
运算符 | 说明 | 示例 | 结果 |
+ | 加法 | 3+2 | 5 |
- | 减法 | 30-5 | 25 |
* | 乘法 | 3*6 | 18 |
/ | 浮点数除法 | 8/2 | 4.0 |
// | 整数除法 | 7//2 | 3 |
% | 模(取余) | 7%4 | 3 |
** | 幂 | 2**3 | 8 |
使用divmod函数同时得到商和余数:
>>> divmod(13,3)
(4, 1)
整数
Python中,除10进制,还有其他三种进制:
0b或0B,二进制 0 1
0o或0O,八进制 0 1 2 3 4 5 6 7
0x或0X,十六进制0 1 2 3 4 5 6 7 8 9 a b c d e f
这三种进制可以非常方便的进行“位运算”操作。
【操作】测试不同进制
>>> 12 12 >>> 0b101 5 >>> 0o10 8 >>> 0xff 255 >>> 0xf 15 >>> 0x10 16 |
使用int()实现类型转换:
1.浮点数直接舍去小数部分,如:int(9.9)结果为9
2.布尔值True转为1,False转为0. 如:int(True)结果为1
3.字符串符合整数格式(浮点数格式不行)则直接转成对应整数,否则报错。
>>> int("456") 456 >>> int("456abc") Traceback (most recent call last): File "<pyshell#20>", line 1, in <module> int("456abc") ValueError: invalid literal for int() with base 10: '456abc' |
自动转型:
整数和浮点数混合运算时,表达式结果自动转型为浮点数,比如:2+8.0的结果是10.0
整数可以有多大?
Python2中,int是32位,可以存储从-2147483648到2147483647的整数(约±21亿),Long类型是64位,可以存储:-2^63---2^63-1之间的数值。
Python3中,int可以存储任意大小的整数,long被取消。我们甚至可以存储下面的值:
>>> googol=10**100 >>> googol 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
Googol也是Google最初的名字,这也是Google最初的含义。
Python3中可以做超大数的计算,而不会造成“整数溢出”,这也是Python特别适合科学运算的特点。
浮点数
浮点数,称为float.
浮点数用a*b的十次方形式的科学计数法表示。比如:3.14,表示成:314E-2或者314e-2.
这些数字在内存中也是按照科学计数法存储。
类型转换和四舍五入
1.类似于int(),我们也可以使用float()将其他类型转化成浮点数。
2.整数和浮点数混合运算时,表达式结果自动转型成浮点数。比如:2+8.0的结果是10.0
3.round(value)可以返回四舍五入的值
注:但不会改变原有值,而是产生新的值
增强型赋值运算符
运算符+、-、*、/、//、**和%和赋值符=结合可以构成“增强型赋值运算符”。
a=a+1 等价于:a+=1
增强型赋值运算符
运算符 | 例子 | 等价 |
+= | a+=2 | a=a+2 |
-= | a-=2 | a=a-2 |
*= | a*=2 | a=a*2 |
/= | a/=2 | a=a/2 |
//= | a//=2 | a=a//2 |
**= | a**=2 | a=a**2 |
%= | a%=2 | a=a%2 |
注意:“+=”中间不能加空格!
【操作】定义多点坐标_绘出折线_并计算起始点和终点距离
import turtle #定义多个点的坐标 x1,y1=100,100
distance = math.sqrt((x1-x4)**2+(y1-y4)**2) |
布尔值
Python2中没有布尔值,直接用数字0表示False,用数字1表示True.
Python3中,把True和False定义成了关键字,但他们的本质还是1和0,甚至可以和数字相加。
>>> a=True >>> b=3 >>> a+b 4 |
比较运算符
所有比较运算符返回1表示真,返回0表示假。这分别与特殊的变量True和False等价。
一下假设变量a为15,变量b为30:
逻辑运算符
同一运算符
同一运算符用于比较两个对象的存储单元,实际比较的是对象的地址。
is 与 ==区别:
is用于判断两个变量引用对象是否为同一个,既比较对象的地址。
==用于判断引用变量引用对象的值是否相等,默认调用对象的__eq__()方法。
>>> a=1000 >>> b=1000 >>> a==b True >>> a is b False >>> id(a) 1860714118064 >>> id(b) 1860719150256 >>> |
·整数缓存问题
Python仅仅对比较小的整数对象进行缓存(范围[-5,256])缓存起来,而并非是所有整数对象。需要注意的是,这仅仅是在命令行中执行,而在Pycharm或者保存为文件执行,结果是不一样的,这是因为解释器做了一部分优化(范围[-5,任意正整数])。
·总结
1.is 比较两个对象的id值是否相等,是否指向同一个内存地址;
2.==比较的是两个对象的内容是否相等,值是否相等;
3.小整数对象[-5,256]在全局解释器范围内被放入缓存供重复使用;
4.is运算符比==效率高,在变量和None进行比较时,应该使用is.
字符串
字符串基本特点
字符串的本质是:字符序列。Python的字符串是不可变的,我们无法对原字符串做任何修改,但,可以将字符串的一部分复制到新创建的字符串,达到“看起来修改”的效果。
Python不支持单字符类型,单字符也是作为一个字符串使用的。
字符串的编码
Python3直接支持Unicode,可以表示世界上任何书面语言的字符。Python3的字符默认就是16位Unicode编码,ASCII码是Unicode编码的子集。
使用内置函数ord()可以把字符转换成对应的Unicode码。
使用内置函数chr()可以把十进制数字转换成对应的字符。
>>> ord('A') 65 >>> ord('高') 39640 >>> chr(66) 'B' |
引号创建字符串
我们可以通过单引号或者双引号创建字符串。例如:a='abc';b="sxt".
使用两种引号的好处是可以创建本身就包含引导的字符串,而不用使用转义字符。例如:
>>> a="I'm a teacher!" >>> print(a) I'm a teacher! >>> b='my_name is "TOM"' >>> print(b) my_name is "TOM" |
连续三个单引号或三个双引号,可以帮助我们创建多行字符串。例如:
>>> resume='''name="gaoqi" company="sxt" age=18 lover="TOM"''' >>> print(resume) name="gaoqi" company="sxt" age=18 lover="TOM" |
空字符串和len()和函数
Python允许空字符串的存在,不包括任何字符且长度为0.例如:
>>> c='' >>> len(c) 0 |
len()用于计算字符串含有多少字符。例如:
>>> d='abc尚学堂' >>> len(d) 6 |
转义字符
我们可以使用“\+特殊字符”,实现某些难以用字符表示的效果。比如:换行等。常见的转义字符有这些:
【操作】测试转义字符的使用
>>> a='I\nlove\nU' >>> print('aaabb\
|
字符串拼接
1.可以使用+将多个字符串拼接起来。例如:‘aa'+'bb'==>'aabb'
(1)如果+两边都是字符串。则拼接。
(2)如果+两边都是数字,则加法运算。
(3)如果+两边类型不同,则抛出异常。
2.可以将多个字面字符串直接放到一起实现拼接。例如:'aa' 'bb' ==> 'aabb'
【操作】字符串拼接操作
>>> a='sxt'+'gaoqi' >>> a 'sxtgaoqi' >>> b='sxt''gaoqi' >>> b 'sxtgaoqi' |
字符串复制
使用*可以实现字符串复制。
【操作】字符串复制操作
>>> a='Sxt'*3 >>> a 'SxtSxtSxt' |
不换行打印
我们前面调用print时,会自动打印一个换行符。有时,我们不想换行,不想自动添加换行符。我们可以自己通过参数end=“任意字符串”。实现末尾添加任何内容:
建立源文件:
print("sxt",end=' ') print("sxt",end='##') print("sxt") |
运行结果:
sxt sxt##sxt
从控制台读取字符串
我们可以使用input()从控制台读取键盘输入的内容。
>>> myname=input("请输入名字:") 请输入名字:邹 >>> myname '邹' |
str()实现数字转型字符串
str()可以帮助我们将其他数据类型转换为字符串。例如:
str(5.20) == > '5.20' str(3.14e2) == >'314.0' str(True) == > 'True'
当我们调用print()函数时,解释器自动调用了str()将非字符串的对象转成了字符串。
使用[]提取字符
字符串的本质就是字符序列,我们可以通过在字符串后面添加[],在[]里面指定偏移量,可以提取该位置的单个字符。
正向搜索:
最左侧第一个字符,偏移量是0,第二个偏移量是1,以此类推。直到len(str)-1为止。
反向搜索:
最右侧第一个字符,偏移量是-1,倒数第二个偏移量是-2,以此类推,直到-len(str)为止。
[操作]使用[]提取字符串中的字符
>>> a='abcdefghijklmnopqrstuvwxyz' >>> a 'abcdefghijklmnopqrstuvwxyz' >>> a[0] 'a' >>> a[3] 'd' >>> a[26-1] 'z' >>> a[-1] 'z' >>> a[-26] 'a' |
replace()实现字符串替换
字符串是“不可改变”的,我们通过[]可以获取字符串指定位置的字符,但是我们不能改变字符串,我们尝试改变字符串中的某个字符,发现报错了:
>>> a='abcdefghijklmnopqrstuvwxyz' >>> a 'abcdefghijklmnopqrstuvwxyz' >>> a[3]='搞' Traceback (most recent call last): File "<pyshell#51>", line 1, in <module> a[3]='搞' TypeError: 'str' object does not support item assignment |
字符串不可改变,但是,我们确实有时候需要替换某些字符,这是,只能通过创建新的字符串来实现。
>>> a='abcdefghijklmnopqrstuvwxyz' >>> a 'abcdefghijklmnopqrstuvwxyz' >>> a.replace('c','高') 'ab高defghijklmnopqrstuvwxyz' |
整个过程中,实际上我们是创建了新的字符串对象,并指向了变量a,而不是修改了以前的字符串,内存图如下
字符串切片slice操作
切片slice操作可以让我们快速的提取字符串,标准格式为:
[起始偏移量 start : 终止偏移量 end : 步长 step]
典型操作(三个量为正数的情况)如下:
其他操作(三个量为负数)的情况:
切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始偏移量小于0则会当做0,终止偏移量大于“长度-1”会被当成-1.例如:
>>>"abcdefg"[3:50]
'defg'
我们发现正常输出了结果,没有报错。
split()分割和join()合并
split()可以基于指定分隔符将字符串分隔成多个子字符串(存储到列表中)。如果不指定分隔符,则默认使用空白符(换行符/空格/制表符)。示例代码如下:
>>> a="to be or not to be" >>> a.split() ['to', 'be', 'or', 'not', 'to', 'be'] >>> a.split('be') ['to ', ' or not to ', ''] |
join()的作用和split()作用刚好相反,用于将一系列子字符串连接起来。示例代码如下:
>>> a=['sxt','sxt100','sxt200'] >>> '*'.join(a) 'sxt*sxt100*sxt200' |
拼接字符串要点:
使用字符串拼接符+,会生成新的字符串对象,因此不推荐使用+来拼接字符串。推荐使用join函数,因此join函数在拼接字符串之前会计算所有字符串的长度,然后逐一拷贝。仅新建一次对象。
字符串驻留机制和字符串比较
字符串驻留:仅保存一份相同且不可变字符串的方法,不同的值被存放在字符串驻留池中。
Python支持字符串驻留机制,对于符合标识符规则的字符串(仅包含下划线(_)、字母和数字)会启用字符串驻留机制驻留机制。
>>> a="abd_33" >>> b="abd_33" >>> a is b True >>> c="dd#" >>> d="dd#" >>> c is d False >>> str1="aa" >>> str2="bb" >>> str1+str2 is "aabb" False |
字符串比较和同一性
我们可以直接使用==,!=对字符串进行比较,是否含有相同的字符。
我们使用is /not is, 判断两个对象是否同一对象。比较的是对象的地址,即id(obj1)是否和id(obj2)相等。
成员操作符
in / not in 关键字,判断某个字符(子字符串)是否存在于字符串中。
字符串常用方法汇总
字符串有很多常用的方法,我们需要熟悉。我们通过表格将这些方法汇总起来,方便大家查阅,希望大家针对每个方法都做一次测试。
常用查找方法
去除首尾信息
我们可以通过strip()去除字符串首尾指定信息。通过lstrip()去除字符串左边指定信息,rstrip()去除字符串右边指定信息。
>>> "*s*x*t*".strip("*") 's*x*t' >>> "*s*x*t*".lstrip("*") 's*x*t*' >>> "*s*x*t*".rstrip("*") '*s*x*t' >>> " sxt ".strip() 'sxt' |
大小写转换
格式排版
其他方法
1. isalnum() 是否为字母或数字
2. isalpha() 检测字符串是否只由字母组成(含汉字)。
3. isdigit() 检测字符串是否只由数字组成。
4. isspace() 检测是否为空白符
5. isupper() 是否为大写字母
6. islower() 是否为小写字母
>>> "sxt100".isalnum() True >>> "sxt尚学堂".isalpha() True >>> "234.3".isdigit() False >>> "23423".isdigit() True >>> "aB".isupper() False >>> "A".isupper() True >>> "\t\n".isspace() True |
字符串的格式化
format()基本用法
Python2.6开始,新增了一种格式化字符串格式化的功能。
基本语法是通过{}和:来代替以前的%。
format函数可以接受不限个参数,位置可以不按顺序。
我们可以通过{索引}/{参数名},直接映射参数值,实现对字符串的格式化,非常方便。
填充和对齐
填充常跟对齐一起使用
^ 、<、>分别是居中,左对齐,右对齐,后面带宽度
:后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充
数字格式化
可变字符串
Python中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,智能创建新的字符串对象,但是,经常我们确实需要原地修改字符串,可以使用。io.StringIO对象或array模块。
>>> import io >>> s="hello,sxt" >>> sio=io.StringIO(s) >>> sio <_io.StringIO object at 0x000001AA7E43B558> >>> sio.getvalue() 'hello,sxt' >>> sio.seek(7) 7 >>> sio.write("9") 1 >>> sio.getvalue() 'hello,s9t' |
基本运算符
我们在前面讲解了“+”、“-”、“*”、“/”、“//”、“%”等运算符,这里我们继续讲解一些其他运算符,并进行学习和测试。
1.比较运算符可以连用,并且含义和我们日常使用完全一致。
>>>a=4
>>>3<a<10 #关系运算符可以连用
True
2.位操作
>>>a=0b11001
>>>b=0b01000
>>>c=a|b
>>>bin(c) #bin()可以将数字转成二进制表示
'0b11001'
>>>bin(c&b)
'0b1000'
>>>bin(c^b)
'0b10001'
>>> a=3
>>> a<<2 #左移1位相当于乘以2.左移2位,相当于乘以4
12
>>> a=8
>>> a>>1 #右移1位相当于除以2
4
3.加法操作
(1)数字相加 3+2 ==>5
(2)字符串拼接 “3”+“2”==>“32”
(3)列表、元组等合并 [10,20,30]+[5,10,100] ==>[10,20,30,5,10,100]
4.乘法操作
(1)数字相乘 3*2==>6
(2)字符串复制 “sxt”*3 ==>"sxtsxtsxt"
(3)列表、元组等复制 [10,20,30]*3 ==>[10,20,30,10,20,30,10,20,30]
复合赋值运算符
符合赋值可以让程序更加精炼,提高效率。
注:与C和JAVA不一样,Python不支持自增(++)和自减(--)
运算符优先级问题
如下优先级,从高到低。
实际使用中,记住如下简单的规则即可,复杂的表达式一定要使用小括号组织。
1.乘除优先加减
2.位运算和算术运算>比较运算符>赋值运算符
序列
序列是一种数据存储方式,用来存储一系列的数据。在内存中,序列就是一块用来存放多个值的连续的内存空间。比如一个整数序列[10,20,30,40].可以这样示意表示:
10 | 20 | 30 | 40 |
由于Python3中一切皆对象,在内存中实际是按照如下方式存储的:
a = [10,20,30,40]
从图示中,我们可以看出序列中存储的整数对象的地址,而不是整数对象的值。python中常用的序列结构有:
字符串、列表、元组、字典、集合
我们上一章学习的字符串就是一种序列。关于字符串里面很多操作,在这一章中仍然会用到,大家一定会感觉非常熟悉。
列表简介
列表:用于存储任意数目、任意类型的数据集合。
列表是内置可变序列,是包含多个元素的有序连续的内存空间。列表定义的标准语法格式:
a = [10,20,30,40]
其中,10,20,30,40这些称为:列表a的元素。
列表中元素可以各不相同,可以是任意类型。比如:
a = [10,20,'abc',True]
列表对象的常用方法汇总如下,
方法 | 要点 | 描述 |
list.append(x) | 增加元素 | 将元素x增加到列表list尾部 |
list.extend(aList) | 增加元素 | 将列表alist所有元素加到列表list尾部 |
list.insert(index,x) | 增加元素 | 在列表list指定位置index处插入元素x |
list.remove(x) | 删除元素 | 在列表list中删除首次出现的指定元素x |
list.pop([index]) | 删除元素 | 删除并返回列表list指定为止index处的元素,默认是最后一个元素 |
list.clear() | 删除所有元素 | 删除列表所有元素,并不是删除列表对象 |
list.index(x) | 访问元素 | 返回第一个x的索引位置,若不存在x元素抛出异常 |
list.count(x) | 计数 | 返回指定元素x在列表list中出现的次数 |
len(list) | 列表长度 | 返回列表中包含元素的个数 |
list.reverse() | 翻转列表 | 所有元素原地翻转 |
list.sort() | 排序 | 所有元素原地排序 |
list.copy() | 浅拷贝 | 返回列表对象的浅拷贝 |
Python的列表大小可变,根据需要随时增加或缩小。
字符串和列表都是序列类型,一个字符串是一个字符序列,一个列表是任何元素的序列。
列表的创建
基本语法[]创建
>>> a=[10,20,'gaoqi','sxt']
>>> a=[] #创建一个空的列表对象
list()创建
使用list()可以将任何可迭代的数据转化成列表。
>>> a=list()
>>> a=list(range(10))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a=list("gaoqi,sxt")
>>> a
['g', 'a', 'o', 'q', 'i', ',', 's', 'x', 't']
range()创建整数列表
range()可以帮助我们非常方便的创建整数列表,这在开发中及其有用。语法格式为:
range([start],end,[step])
start参数:可选,表示起始数字。默认是0
end参数:必选,表示结尾数字。
step参数:可选,表示步长,默认为1
python3中range()返回的是一个range对象,而不是列表。我们需要通过list()方法将其转换成列表对象。
典型示例如下:
>>> list(range(3,15,2))
[3, 5, 7, 9, 11, 13]
>>> list(range(15,3,-1))
[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4]
>>> list(range(3,-10,-1))
[3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
推导式生成列表
使用列表推导式可以非常方便的创建列表,在开发中经常使用。但是,由于涉及到for循环和if语句。
>>> a=[x*2 for x in range(5)] #循环创建多个元素
>>> a
[0, 2, 4, 6, 8]
>>> a=[x*2 for x in range(100) if x%9==0] #通过if过滤元素
>>> a
[0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198]
列表元素的增加和删除
当列表增加和删除元素时,列表会自动进行内存管理,大大减少了程序员的负担。但这个特点涉及列表元素的大量移动,效率较低。除非必要,我们一般只在列表的尾部添加元素或删除元素,这会大大提高列表的操作效率。
append()方法
原地修改列表对象,是真正的列表尾部添加新的元素,速度最快,推荐使用。
>>> a=[20,40]
>>> a.append(80)
>>> a
[20, 40, 80]
+运算符操作
并不是真正的尾部添加元素,而是创建新的列表对象;将原列表的元素和新列表的元素依次复制到新的列表对象中。这样,会涉及大量的复制操作,对于操作大量元素不建议使用。
>>> a=[20,40]
>>> id(a)
1831775170504
>>> a=a+[50]
>>> id(a)
1831775169800
通过如上测试,我们发现变量a的地址发生了变化。也就是创建了新的列表对象。
extend()方法
将目标列表的所有元素添加到本列表的尾部,属于原地操作,不创建新的列表对象。
>>> a=[20,40]
>>> id(a)
1831775170056
>>> a.extend([50,60])
>>> id(a)
1831775170056
insert()插入元素
使用insert()方法可以将指定元素插入到列表对象的任意指定位置。这样会让插入位置后面所有的元素进行移动,会影响处理速度。涉及大量元素时,尽量避免使用。类似发生这种移动的函数还有:remove()、pop()、del(),它们在删除非尾部元素时也会发生操作位置后面元素的移动。
>>> a=[10,20,30]
>>> a.insert(2,100)
>>> a
[10, 20, 100, 30]
乘法扩展
使用乘法扩展列表,生成一个新列表,新列表元素是原列表元素的多次重复。
>>> a=['sxt',100]
>>> b=a*3
>>> a
['sxt', 100]
>>> b
['sxt', 100, 'sxt', 100, 'sxt', 100]
适用于乘法操作的,还有:字符串、元组,例如:
>>> c='sxt'
>>> d=c*3
>>> c
'sxt'
>>> d
'sxtsxtsxt'
列表元素的删除
del删除
删除列表指定位置的元素。
>>> a=[10,20,30]
>>> del a[1]
>>> a
[10, 30]
pop()方法
pop()删除并返回指定位置元素,如果未指定位置则默认操作列表最后一个元素。
>>> a=[10,20,30,40,50]
>>> a.pop()
50
>>> a
[10, 20, 30, 40]
>>> a.pop(1)
20
>>> a
[10, 30, 40]
remove()方法
删除首次出现的指定元素,若不存在该元素抛出异常。
>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.remove(20)
>>> a
[10, 30, 40, 50, 20, 30, 20, 30]
>>> a.remove(60)
Traceback (most recent call last):
File "<pyshell#144>", line 1, in <module>
a.remove(60)
ValueError: list.remove(x): x not in list
列表元素访问和计数
通过索引直接访问元素
我们可以通过索引直接访问元素。索引的区间在[0,列表长度-1]这个范围。超过这个范围则会抛出异常。
>>> a=[10,20,30,40,50,20,30,20,30]
>>> a[2]
30
>>> a[10]
Traceback (most recent call last):
File "<pyshell#147>", line 1, in <module>
a[10]
IndexError: list index out of range
index()获得指定元素在列表中首次出现的索引
index()可以获得指定元素首次出现的索引位置。语法:index(value,[start,[end]])。其中,
start和end指定了搜索的范围。
>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.index(20)
1
>>> a.index(20,3) #从索引位置3开始往后搜索的第一个20
5
>>> a.index(30,5,7) #从索引位置5到7这个区间,第一次出现30元素的位置
6
count()获得指定元素在列表中出现的次数
count()可以返回指定元素在列表中出现的次数。
>>> a=[10,20,30,40,50,20,30,20,30]
>>> a.count(20)
3
len()返回列表长度
>>> a=[10,20,30]
>>> len(a)
3
成员资格判断
判断列表中是否存在指定的元素,我们可以使用count()方法,返回0则表示不存在,返回大于0则表示存在。但是,一般我们会使用更加简洁的in关键字来判断,直接返回True或False。
>>> a=[10,20,30,40,50,20,30,20,30]
>>> 20 in a
True
>>> 100 not in a
True
切片操作
切片是Python序列及其重要的操作,适用于列表、元组、字符串等等。切片的格式如下
切片slice操作可以让我们快速提取子列表或修改。标准格式为:
[起始偏移量 start:终止偏移量 end[:步长 step]]
注:当步长省略时顺便可以省略第二个冒号
典型操作(三个量为正数的情况)如下:
其他操作(三个量为负数)的情况:
切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始偏移量小于0则会当做0,终止偏移量大于“长度-1”会被当成-1。例如:
>>> [10,20,30,40][1:30]
[20, 30, 40]
列表的遍历
for obj in listObj:
print(obj)
列表排序
修改原列表,不建新列表的排序
>>> a=[20,10,30,40]
>>> id(a)
1831774893256
>>> a.sort() #默认是升序排列
>>> a
[10, 20, 30, 40]
>>> a=[10,20,30,40]
>>> a.sort(reverse=True) #降序排列
>>> a
[40, 30, 20, 10]
>>> import random
>>> random.shuffle(a) #打乱顺序
>>> a
[10, 40, 30, 20]
建新列表的排序
我们可以通过内置函数sorted()进行排序,这个方法返回新列表,不对原列表做修改。
>>> a=[20,10,30,40]
>>> id(a)
1831775170504
>>> a=sorted(a) #默认升序
>>> a
[10, 20, 30, 40]
>>> id(a)
1831775179080
>>> b=sorted(a)
>>> b
[10, 20, 30, 40]
>>> id(a)
1831775179080
>>> id(b)
1831774893896
>>> c=sorted(a,reverse=True) #降序
>>> c
[40, 30, 20, 10]
reversed()返回迭代器
内置函数reversed()也支持进行逆序排列,与列表对象reverse()方法不同的是,内置函数reversed()不对原列表做任何修改,只是返回一个逆序排列的迭代器对象。
>>> a=[20,10,30,40]
>>> c=reversed(a)
>>> c
<list_reverseiterator object at 0x000001AA7E4BA7F0>
>>> list(c)
[40, 30, 10, 20]
>>> list(c)
[]
我们打印输出c发现提示是:list_reverseiterator.也就是一个迭代对象。同时,我们使用list(c)进行输出,发现只能使用一次。第一次输出了元素,第二次为空。那是因为迭代对象在第一次时已经遍历结束了,第二次不能再使用了。
列表相关的其他内置函数汇总
max和min
用于返回列表中最大和最小值。
>>> a=[3,10,20,15,9]
>>> max(a)
20
>>> min(a)
3
sum
对数值型列表的所有元素进行求和操作,对非数值型列表运算则会报错。
>>> a=[3,10,20,15,9]
>>> sum(a)
57
多维列表
二维列表
一维列表可以帮助我们存储一维、线性的数据。
二维列表可以帮助我们存储二维、表格的数据。例如下表的数据:
姓名 | 年龄 | 薪资 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
a=[ ["高小一",18,30000,“北京”], ["高小二",19,20000,“上海”], ["高小五",20,10000,“深圳”], ] for m in range(3): for n in range(4): print(a[m][n],endl="\t") print() #打印完一行,换行 |
元组tuple
列表属于可变序列,可以任意修改列表中的元素。元组属于不可变序列,不能修改元组中的元素。因此,元组没有增加元素、修改元素、删除元素相关的方法。
因此,我们只需要学习元组的创建和删除,元组中元素的访问和计数即可。元组支持如下操作:
1.索引访问
2.切片操作
3.连接操作
4.成员关系操作
5.比较运算操作
6.计数:元组长度len()、最大值max()、最小值min()、求和sum()等。
元组的创建
1.通过()创建元组。小括号可以省略。
a=(10,20,30) 或者 a=10,20,30
如果元组只有一个元素,则必须后面加逗号。这是因为解释器会把(1)解释为整数1,(1,)解释为元组。
>>> a=(1) >>> type(a) <class 'int'> >>> a=(1,) #或者 a=1, >>> type(a) <class 'tuple'> |
2.通过tuple()创建元组
tuple(可迭代的对象)
例如:
b=tuple() #创建一个空元组对象
b=tuple("abc")
b=tuple(range(3))
b=tuple([2,3,4])
总结:
tuple()可以接收列表、字符串、其他序列类型。迭代器等生成元组。
list()可以接收元组、字符串、其他序列类型、迭代器等生成列表。
元组的元素访问和计数
1.元组的元素不能修改
>>> a=(20,10,30,9,8) >>> a[3]=33 Traceback (most recent call last): File "<pyshell#200>", line 1, in <module> a[3]=33 TypeError: 'tuple' object does not support item assignment |
2.元组的元素访问和列表一样,只不过返回的仍然是元组对象。
>>> a=(20,10,30,9,8) >>> a[1] 10 >>> a[1:3] (10, 30) >>> a[:4] (20, 10, 30, 9) |
3.列表关于排序的方法list.sorted()是修改原列表对象,元组没有该方法。如果要对元组排序,只能使用内置函数sorted(tupleObj),
并生成新的列表对象。
>>> a=(20,10,30,9,8) >>> sorted(a) [8, 9, 10, 20, 30] |
zip
zip(列表1,列表2,……)将多个列表对应位置的元素组合成为元组,并返回这个zip对象。
>>> a=[10,20,30] >>> b=[40,50,60] >>> c=[70,80,90] >>> d=zip(a,b,c) >>> list(d) [(10, 40, 70), (20, 50, 80), (30, 60, 90)] |
生成器推导式创建元组
从形式上看,生成器推导式与列表推导式类似,只是生成器推导式使用小括号。列表推导式直接生成列表对象,生成器推导式生成的不是列表也不是元组,而是一个生成器对象。
我们可以通过生成器对象,转化成列表或者元组。也可以使用生成器对象的_next_()方法进行遍历,或者直接作为迭代器对象来使用。不管什么方式使用,元素访问结束后,如果需要重新访问其中的元素,必须重新创建该生成器对象。
【操作】生成器的使用测试
>>> s=(x*2 for x in range(5)) >>> s=(x*2 for x in range(5)) 0 >>> s._next_() 2 >>> s._next_() 4 |
元组总结
1.元组的核心特点是:不可变序列。
2.元组的访问和处理速度比列表快。
3.与整数和字符串一样,元组可以作为字典的键,列表则永远不能作为字典的键使用。
字典
字典是“键对值”的无序可变序列,字典中的每个元素都是一个“键对值”,包含:“键对象”和“值对象”。可以通过“键对象”实现快速获取、删除、更新对应的“值对象”,
列表中我们通过“下标数字”找到对应的对象。字典中通过“键对象”找到对应的“值对象”。“键”是任意的不可变数据,比如:整数、浮点数、字符串、元组。但是:列表、字典、集合这些可变对象,不能作为“键”,而且“键”不可重复。
“值”是任意的数据,并且课重复。
一个典型的字典的定义方式:
a={'name':'gaoqi','age':'18','job':'programmer'}
字典的创建
1.我们可以通过{}、dict()来创建字典对象。
>>> a={'name':'gaoqi','age':'18','job':'programmer'} >>> b=dict(name='gaoqi',age=18,job='programmer') >>> a=dict([("name","gaoqi"),("age",18)]) >>> c={} #空的字典对象 >>> d=dict() #空的字典对象 |
2.通过zip()创建字典对象
>>> k=['name','age','job'] >>> v=['gaoqi','18','programmer'] >>> d=dict(zip(k,v)) >>> d {'name': 'gaoqi', 'age': '18', 'job': 'programmer'} |
3.通过fromkeys创建值为空的字典
>>> a=dict.fromkeys(['name','age','job']) >>> a {'name': None, 'age': None, 'job': None} |
字典元素的访问
为了测试各种访问方法,我们这里设定一个字典对象:
a={'name':'gaoqi','age':'18','job':'programmer'}
1.通过[键]获得“值”。若键不存在,则抛出异常。
>>> a={'name':'gaoqi','age':'18','job':'programmer'} >>> a['name'] 'gaoqi' >>> a['age'] '18' >>> a['sex'] Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> a['sex'] KeyError: 'sex' |
2.通过get()方法获得“值”。推荐使用。优点是:指定键不存在,返回None;也可以设定指定键不存在时默认返回的对象。推荐使用get()获取“值对象”。
>>> a.get('name') 'gaoqi' >>> a.get('sex') >>> a.get('sex','一个人') '一个人' |
3.列出所有的键值对
>>> a.items() dict_items([('name', 'gaoqi'), ('age', '18'), ('job', 'programmer')]) |
4.列出所有的键,列出所有的值
>>> a.keys() dict_keys(['name', 'age', 'job']) >>> a.values() dict_values(['gaoqi', '18', 'programmer']) |
5.len() 键值对的个数
6.检测一个“键”是否在字典中
>>> a={"name":"gaoqi","age":18} >>> "name" in a True |
字典元素添加、修改、删除
1.给字典新增“键对值”,如果“键”已经存在,则覆盖就得键对值,如果“键”不存在,
则新增“键值对”.
>>> a={'name':'gaoqi','age':'18','job':'programmer'} >>> a['address']='西三旗1号院' >>> a['age']=16 >>> a {'name': 'gaoqi', 'age': 16, 'job': 'programmer', 'address': '西三旗1号院'} |
2.使用update()将新字典中所有键值对全部添加到旧字典对象上,如果key有重复,则直接覆盖。
>>> a={'name':'gaoqi','age':18,'job':'programmer'} >>> b={'name':'gaoxixi','money':1000,'sex':'男'} >>> a.update(b) >>> a {'name': 'gaoxixi', 'age': 18, 'job': 'programmer', 'money': 1000, 'sex': '男'} |
3.字典中元素的删除,可以使用del()方法;或者clear()删除所有键值对;pop()删除指定键值对,并返回对应的“值对象”;
>>> a={'name':'gaoqi','age':18,'job':'programmer'} >>> del(a['name']) >>> a {'age': 18, 'job': 'programmer'} >>> a.pop('age') 18 >>> a {'job': 'programmer'} |
4.popitem():随机删除和返回该键值对,字典是“无序可变序列”,因此没有第一个元素、最后一个元素的概念;popitem()弹出随机的项,因为字典并没有“最后的元素”或者其他有关顺序的概念。若想一个接一个地移除并处理项,这个方法就非常有效(因为不用首先获取键的列表)。
>>> a={'name':'gaoqi','age':18,'job':'programmer'} >>> a.popitem() ('job', 'programmer') >>> a {'name': 'gaoqi', 'age': 18} >>> a.popitem() ('age', 18) >>> a {'name': 'gaoqi'} |
序列解包
序列解包可以用于元组、列表、字典。序列解包可以让我们方便的对多个变量赋值。
>>> x,y,z=(20,30,10) >>> x 20 >>> y 30 >>> z 10 >>> (a,b,c)=[9,8,10] >>> a 9 >>> b 8 >>> [a,b,c]=[10,20,30] >>> a 10 >>> b 20 |
序列解包用于字典时,默认是对“键”进行操作;如果需要对键值对操作,则需要使用items();如果需要对“值”进行操作,则需要使用values();
>>> s={'name':'gaoqi','age':18,'job':'programmer'} >>> name,age,job=s >>> name 'name' >>> name,age,job=s.items() >>> name ('name', 'gaoqi') >>> name,age,job=s.values() >>> name 'gaoqi' |
表格数据使用字典和列表存储,并实现访问
姓名 | 年龄 | 薪资 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
{"name":"高小一","age":18,"salary":3000,"city":"北京"}
#获得第二行的人的薪资
#打印表的所有数据 |
字典核心底层原理
字典对象的核心是散列表。散列表是一个稀疏数组(总是有空白元素的数组),数组的每个单元叫做bucket,每个bucket有两个部分:一个是键对象的引用,一个是值对象的引用。
由于,所有bucket结构和大小一致,我们可以通过偏移量来读取指定bucket.
将一个键值对放进字典的底层过程
>>>a={} >>> a["name"="gaoqi"] |
假设字典a对象创建完后,数组长度为8
我们要把“name”="gaoqi"这个键值对放到字典对象a中,首先第一步需要计算键“name”的散列值。Python中可以通过hash()来计算。
>>> bin(hash("name")) '0b110011011100010010000011001010100010110010101011000100000000010' |
由于数组长度为8,我们可以拿计算出的散列值的最右边3位数字作为偏移量,即“101”,十进制是数字5.我们查看偏移量5,对应的ducket是否为空。如果为空,则将键值对放进去。如果不为空,则依次取右边3位作为偏移量,即“100”,十进制是数字4。在查看偏移量为4的bucket是否为空。直到找到为空的bucket将键值对放进去。流程图如下:
扩容
python会根据散列表的拥挤程度扩容。“扩容”指的是:创造更大的数组,将原来内容拷贝到新数组中。
接近2/3时,数组就会扩容。
根据键查找“键值对”的底层过程
我们明白了,一个键值对是如何存储到数组中的,根据键对象取到值对象,理解起来就简单了。
>>> a={"name":"gaoqi","age":21,"job":"student"} >>> a.get("name") 'gaoqi' |
当我们调用a.get("name"),就是根据键“name”查找到“键值对”,从而找到值对象“gaoqi”。
第一步,我们仍然要计算“name”对象的散列值:
>>> bin(hash("name")) '0b110011011100010010000011001010100010110010101011000100000000010' |
和存储的底层流程算法一致,也是依次取散列值的不同位置的数字。假设数组长度为8,我们可以拿计算出的散列值的最右边3位数字作为偏移量,即“101”,十进制是数字5.我们查看偏移量5,对应的bucket是否为空。如果为空,则返回None.如果不为空,则将这个bucket的键对象计算对应散列值,和我们的散列值进行比较,如果相等,这将对应“值对象”返回。如果不相等,则再依次取其他几位数字,重新计算偏移量。依次取完后仍然没有找到。则返回None。流程图如下:
用法总结:
1.键必须可散列
(1)数字、字符串、元组,都是可散列的。
(2)自定义对象需要支持下面三点:
①支持hash()函数
②支持通过_eq_()方法检测相等性。
③若a==b为真,则hash(a)==hash(b)也为真。
2.字典在内存中开销巨大,典型的空间换时间。
3.键查询速度很快
4.网字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字典的同时进行字典的修改。
集合
集合是无序可变,元素不能重复。实际上,集合底层是字典实现,集合的所有元素都是字典中的“键对象”,因此是不能重复的且唯一的。
集合创建和删除
1.使用{}创建集合对象,并使用add()方法添加元素
>>> a={3,5,7} >>> a {3, 5, 7} >>> a.add(9) >>> a {9, 3, 5, 7} |
2.使用set(),将列表、元组等可迭代对象转成集合。如果原来数据存在重复数据,则只保留一个。
>>> a={'a','b','c'} >>> b=set(a) >>> b {'b', 'a', 'c'} |
3.remove()删除指定元素;clear()清空整个集合
>>> a={10,20,30,40,50} >>> a.remove(20) >>> a {40, 10, 50, 30} |
集合相关操作
像数学中概念一样,Python对集合也提供了并集、交集、差集等运算。我们给出示例:
>>> a={1,3,'sxt'} |
控制语句
选择结构
选择结构通过判断条件是否成立,来决定执行那个分支。选择结构多种形式,分为:单分支、双分支、多分支。流程图如下:
单分支选择结构
if语句单分支结构的语法形式如下:
if条件表达式
语句/语句块
其中:
①. 条件表达式:可以是逻辑表达式,关系表达式,算术表达式等等。
②. 语句/语句块:可以是一条语句,也可以是多条语句,缩进必须对齐一致。
【操作】输入一个数字,小于10,则打印这个数字
|
条件表达式详解
在选择和循环结构中,条件表达式的值为False的情况如下:
False、0、0.0、空值None、空序列对象(空列表、空元组、空集合、空字典、空字符串)、空range对象、空迭代对象。
其他情况,均为True.这么看来,Python所有的合法表达式都可以看做条件表达式,甚至包括函数调用的表达式。
【操作】测试各种条件表达式
if 3: #整数作为条件表达式 print("ok") a=[] #列表作为条件表达式,由于空列表,是False if a: print("空列表,False") s='False' #非空字符串,是True if s: print("非空字符串,是True") c=9 if 3<c<20: print("3<c<20") if 3<c and c<20: print("3<c and c<20") if True: print("True") |
条件表达式中,不能有赋值操作符“=”
在Python中,条件表达式不能出现赋值操作符“=”,避免了其他语言中经常误将关系运算符“==”写作赋值运算符“=”带来的困扰。如下代码将会报语法错误:
if 3<c and (c=20):
print("赋值符不能出现在条件表达式中")
双分支选择结构
双分支结构的语法格式如下:
多分支选择结构
多分支结构,几个分支之间是有逻辑关系的,不能随意颠倒顺序。
【操作】输入一个学生的成绩,将其转化成简单描述:不及格(小于60)、及格(60-79)、良好(80-89)、优秀(90-100)。
方法一(使用完整的条件表达)
运行结果: 请输入分数:98 |
每个分支都使用了独立的、完整的判断,顺序可以随意挪动,而不影响程序运行。
方法二(利用多分支结构)
运行结果: 请输入分数:87 |
【操作】已知点的坐标(x,y),判断其所在的象限
运行结果: 请输入x的坐标1 |
选择结构嵌套
循环结构
循环结构用来重复执行一条或多条语句。表达这样的逻辑:如果符合条件,则反复执行循环体里的语句。在每次执行完后都会判断一次条件是否为True,如果为True则重复执行循环体里的语句。图示如下:
循环体里面的语句至少应该包含改变条件表达式的语句,以使循环趋于结束;否则,就会变成一个死循环。
while循环
while循环的语法格式如下:
while 条件表达式:
循环体语句
【操作】利用while循环打印从0-10的数字。
运行结果: 0 |
for循环和可迭代对象遍历
for循环通常用于可迭代对象的遍历。for循环的语法格式如下:
for 变量 in 可迭代对象:
循环语句
【操作】遍历一个元组或列表
运行结果: 60 |
可迭代对象
Python包含以下几种可迭代对象:
1.序列。包含:字符串,列表、元组
2.字典
3.迭代器对象(iterator)
4.生成器函数(generator)
5.文件对象
【操作】遍历字符串中的字符
运行结果: s |
【操作】遍历字典
运行结果: name |
range对象
range对象是一个迭代器对象,用来产生指定范围的数字序列。格式为:
range(start,end,[step])
生成的数值序列从start开始到end结束(不包含end)。若没有填写start,则默认从0开始。step是可选的步长,默认为1.如下是几种典型示例:
for i in range(10) 产生序列:0,1,2,3,4,5,6,7,8,9
for i in range(3,10) 产生序列:3,4,5,6,7,8,9
for i in range(3,10,2) 产生序列:3,5,7,9
【操作】利用for循环,计算1-100之间数字的累加和;计算1-100之间偶数的累加和,计算1-100之间奇数的累加和。
运行结果: 5050 |
嵌套循环
一个循环内可以嵌入另一个循环,一般称为“嵌套循环”,或者“多重循环”。
【操作】打印如下图案
|
【操作】利用嵌套循环打印九九乘法表
|
break语句
break语句可用于while和for循环,用来结束整个循环。当有嵌套循环时,break语句只能跳出最近一层的循环。
【操作】使用break语句结束循环
|
continue语句
continue语句用于结束本次循环,继续下一次。多个循环嵌套时,continue也是应用于最近的一层循环。
【操作】要求输入员工的薪资,若薪资小于0则重新输入。最后打印出录入员工的数量和薪资明细,以及平均薪资。
运行结果: 请输入员工的薪资(按Q或q结束)20000 |
else语句
循环代码优化
|
其他优化手段:
1.连续多个字符串,使用join()而不使用+
2.列表进行元素插入和删除,尽量在列表尾部操作
使用zip()并行迭代
我们可以通过zip()函数对多个序列进行并行迭代,zip()函数在最短序列“用亮”时就会停止。
【操作】测试zip()并行迭代
执行结果: 高琪--18--老师 |
推导式创建序列
推导式是从一个或多个迭代器快速创建序列的一种方法。它可以将循环和条件判断结合,从而避免冗长的代码。推导式是典型的Python初学者的水平。
列表推导式
列表推导式生成列表对象,语法如下:
[表达式 for item in 可迭代对象]
或者 :[表达式 for item in 可迭代对象 if 条件判断]
>>> [x for x in range(1,5)] |
字典推导式
字典的推导式生成字典对象,格式如下:
{key_expression : value_expression for 表达式 in 可迭代对象}
类似于列表推导式,字典推导也可以增加if条件判断、多个for循环。
统计文本字符出现的次数:
>>> my_text='i love you,i love sxt,i love gaoqi' >>> char_count={c:my_text.count(c) for c in my_text} >>> char_count {'i': 4, ' ': 6, 'l': 3, 'o': 5, 'v': 3, 'e': 3, 'y': 1, 'u': 1, ',': 2, 's': 1, 'x': 1, 't': 1, 'g': 1, 'a': 1, 'q': 1} |
集合推导式
集合推导式生成集合,和列表推导式的语法格式类似:
{表达式 for item in 可迭代对象 }
或者:{表达式 for item in 可迭代对象 if 条件判断}
>>> {x for x in range(1,100) if x%9==0} {99, 36, 72, 9, 45, 81, 18, 54, 90, 27, 63} |
生成器推导式(生成元组)
>>> (x for x in range(1,100) if x%9==0) <generator object <genexpr> at 0x000001F1ADDA96D8> |
我们发现提示的是“一个生成器对象”。显然,元组是没有推导式的。
一个生成器只能运行一次。第一次迭代可以得到数据,第二次迭代发现数据已经没有了。
>>> gnt=(x for x in range(1,100) if x%9==0)
|
函数用法和底层分析
函数的基本概念
1.一个程序由一个个任务组成:函数就是代表一个任务或者一个功能。
2.函数是代码复用的通用机制。
Python函数的分类
1.内置函数
我们前面使用的str(),list(),len()等这些都是内置函数,我们可以拿来直接使用。
2.标准库函数
我们可以通过import语句导入库,然后使用其中定义的函数。
3.第三方库函数
Python社区也提供了很多高质量的库。下载安装这些库后,也通过import语句导入,然后可以使用这些第三方库的函数
4.用户自定义函数
用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。
函数定义和调用
核心要点
Python中,定义函数的语法如下:
def 函数名([参数列表]):
'''文档字符串'''
函数体/若干语句
要点:
1.我们使用def来定义函数,然后就是一个空格和函数名称;
(1)Python执行def时,会创建一个函数对象,并绑定到函数名变量上。
2.参数列表
(1)圆括号内形式参数列表,有多个参数则使用逗号隔开
(2)形式参数不需要声明类型,也不需要指定函数返回值类型
(3)无参数,也必须保留空的圆括号
(4)实参列表必须与形参列表一一对应
3.return返回值
(1)如果函数体中包含return语句,则结束函数执行并返回值;
(2)如果函数体中不包含return语句,则返回None值。
4.调用函数之前,必须要先定义函数,即先调用def创建函数对象
(1)内置函数对象会自动创建
(2)标准库和第三方库函数,通过import 导入模块时,会执行模块中的def语句
形参和实参
【操作】定义一个函数,实现两个数的比较,并返回较大的值。
执行结果: 20 较大值 |
上面的printMax函数中,在定义时写的printMax(a,b).a和b称为“形式参数”,简称“形参”。也就是说,形式参数是在定义函数使用的。形式参数的命名只要符合“标识符”命名规则即可。
在调用函数时,传递的参数称为“实际参数”,简称“实参”。上面代码中,printMax(10,20).10,和20就是实际参数。
文档字符串(函数的注释)
程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符串”,也有人成为“函数的注释”。我们通过三个单引号或三对双引号来实现,中间可以加入多行文字进行说明。
【操作】测试文档字符串的使用
测试结果: print_star() |
返回值
return 返回值要点:
1.如果函数体包含return语句,则结束函数执行并返回值;
2.如果函数体中不包含reutrn语句,则返回None值。
3.要返回多个返回值,使用列表、元组、字典、集合将多个值“存起来”即可。
【操作】定义一个打印n个星号的无返回值的函数
运行结果: ***** |
【操作】定义一个返回两个数平均值的函数
运行结果: 25.0 |
函数也是对象,内存底层分析
Python中,“一切都是对象”。实际上,执行def定义函数后,系统就创建了相应的函数对象。我们执行如下程序,然后进行解释:
测试结果: <function print_star at 0x0000022EB8FAC1E0> |
上面代码执行def时,系统中会创建函数对象,并通过print_star这个变量进行引用。
变量的作用域(全局变量和局部变量)
变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全局变量、局部变量。
全局变量:
1.在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
2.全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。
3.全局变量一般做常量使用。
4.函数内要改变全局变量的值,使用global声明一下。
局部变量:
1.在函数体中(包含形式参数)声明的变量。
2.局部变量的引用比全局变量快,优先考虑使用。
3.如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量。
【操作】全局变量的作用域测试
测试结果: 100 |
局部变量和全局变量效率测试
局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。
在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运行速度。
测试结果: 耗时2.029999256134033 |
参数的传递
函数声明时的形式参数,使用时,等同于函数体内的局部变量。由于Python中一切皆对象。
因此,参数传递时直接传递对象的地址,但具体使用分两种类型:
1.传递不可变对象的引用(起到其他语言值传递的效果)
2.传递可变对象的引用(起到其他语言引用传递的效果)
函数的参数传递本质上就是:从实参到形参的赋值操作。Python中“一切皆对象”,
所有的赋值操作都是“引用的赋值”。所以,Python中参数的传递都是“引用传递”,不是“值传递”。
具体操作时分两类:
1.对“可变对象”进行“写操作”,直接作用于原对象本身。
2.对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间。(起到其他语言的“值传递”效果,但不是“值传递”)
可变对象有:
字典、列表、集合、自定义的对象等
不可变对象有:
数字、字符串、元组、function等
传递可变对象的引用
传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。
【操作】参数传递:传递可变对象的引用
测试结果: m: 2121923846792 |
传递不可变对象的引用
传递参数是不可变对象(例如:int、float、字符串、元组、布尔值),实际传递的还是对象的引用。在“赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。
【操作】参数传递:传递不可变对象的引用
运行结果: n: 140723893923568 |
显然通过id值我们可以看到n和a一开始是同一个对象。给n赋值后,n是新的对象。
浅拷贝和深拷贝
内置函数:
copy(浅拷贝):不拷贝子对象的内容,只拷贝子对象的引用。
deepcopy(深拷贝):会连子对象的内存也全部拷贝一份,对子对象的修改不会影响原对象
测试结果: a: [10, 20, [5, 6]]
|
参数的类型---不可变对象含可变子对象
测试结果: a: 1599817736904 |
参数的几种类型
位置参数
函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为:
“位置参数”。
【操作】测试位置参数
测试结果: 2 3 4 |
默认值参数
我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。
默认值参数放在位置参数后面。
【操作】测试默认值参数
执行结果: 8 9 10 20 |
命名参数
我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。
【操作】测试命名参数
执行结果: 7 2 4 |
可变参数
可变参数指的是“可变数量的参数”。分两种情况:
1.*param(一个星号),将多个参数收集到一个“元组”对象中。
2.**param(两个星号),将多个参数收集到一个“字典”对象中。
【操作】测试可变参数处理(元组、字典两种方式)
执行结果: 8 9 (19, 20) |
强制命名参数
再带星号的“可变参数”后面增加新的参数,必须是“强制命名参数”。
【操作】强制命名参数的使用
执行结果: (2,) 3 4 |
lambda表达式和匿名函数
lambda表达式可以用来声明匿名函数。lambda函数是一种简单的、在同一行中定义函数的方法。lambda函数实际生成了一个函数对象。
lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。
lambda表达式的基本语法如下:
lambda arg1,arg2,arg3... :<表达式>
arg1,arg2,arg3为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。
【操作】lambda表达式使用
测试结果: <function <lambda> at 0x000002C387A9D1E0> |
eval()函数
功能:将字符串str当成有效的表达式来求值并返回计算结果。
语法:eval(source[,globals[,locals]])->value
参数:
source:一个Python表达式或函数compile()返回的代码对象
globals:可选。必须是dictionary
locals:可选。任意映射对象
执行结果: abcde |
递归函数
递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类似于大家
中学数学学过的“数学归纳法”。每个递归函数必须包含两个部分:
1.终止条件
表示递归什么时候结束。一般用于返回值,不再调用自己。
2.递归步骤
把第n步的值和第n-1步相关联。
递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨慎使用。
【操作】用递归函数计算阶乘
测试结果: 120 |
嵌套函数(内部函数)
嵌套函数:
在函数内部定义的函数!
【操作】嵌套函数定义
测试结果: f1 running... |
上面程序中,f2()就是定义在f1函数内部的函数。f2()的定义和调用都在f1()函数内部。
使用嵌套函数的情况:
1.封装 -----数据隐藏
外部无法访问“嵌套函数”
2.贯彻DRY(Don't Repeat Yourself)原则
嵌套函数,可以让我们在函数内部避免重复代码。
3.闭包
nonlocal关键字
nonlocal 用来声明外层的局部变量
global 用来声明全局变量
【操作】使用nonlocal声明外层局部变量
测试结果: inner b:10 outer b:20 a: 1000 |
LEGB规则
Python在查找“名称”时,是按照LEGB规则查找的:
Local-->Enclosed-->Global-->Built in.
Local 指的就是函数或者类的方法内部
Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包)
Global 指的是模块中的全局变量
Built in 指的是Python为自己保留的特殊名称。
如果某个name映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域(enclosed)进行搜索,如果闭包作用域也没有找到,Python就会在全局(global)命名空间进行查找,最后会在内建(built in)命名空间搜索(如果一个名称在所有命名空间中都没有找到,就会产生一个NameError).
执行结果: inner |
第六章 面向对象编程
面向对象和面向过程区别
面向过程(Procedure Oriented)思维
面向过程编程更加关注的是“程序的逻辑流程”,是一种“执行者”思维,适合编写小规模的程序。
面向对象(Object Oriented)思维
面向对象更加关注的是“软件中对象之间的关系”,是一种“设计者”思维,适合编写大规模的程序。
面向对象思考方式
遇到复杂问题,先从问题中找名词(面向过程更多的是找动词),然后确立这些名词那些可以作为类,再根据问题需求确定类的属性和方法,确定类之间的关系。
面向对象和面向过程总结
·都是解决问题的思维方式,都是代码组织的方式。
·解决简单问题可以使用面向过程。
·解决复杂问题:宏观上使用面向对象把握,微观处理上仍然是面向过程。
对象的进化
1.简单数据
像30,40,50,4等这些数字,可以看做是简单数据。最初的计算机编程,都是像这样的数字。
2.数组
将同类型的数据放到一起。比如:整数数组[20,30,40],浮点数数组[10.2,11.3,12.4],字符串数组:["aa","bb","ccc"]
3.结构体
将不同类型的数据放到一起,是C语言中的数据结构。比如:
struct resume{
int age;
char name[10];
double salary;
};
4.对象
将不同类型的数据、方法(即函数)放到一起,就是对象。比如:
|
我们前面学习的数字也是对象。比如:整数9,就是一个包含了加法、乘法等方法的对象。
类的定义
从一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态)。更具体一点:“方法代码是共享的,属性数据不共享”。
Python中,“一切皆对象”。类也称为“类对象”,类的实例也称为“实例对象”。
定义类的语法格式如下:
class 类名:
类体
【操作】一个典型的类的定义
执行结果: 张三 的分数是: 80 |
构造函数_init_()
类是抽象的,也称为“对象的模板”。我们需要通过类这个模板,创建类的实例对象,然后才能使用类定义的功能。
Python对象包含三个部分:id(identity识别码)、type(对象类型)、value(对象的值)。
1.id(identity识别码)
2.type(对象类型)
3.value(对象的值)
(1)属性(attribute)
(2) 方法(method)
创建对象,我们需要定义构造函数_init_()方法。构造方法用于执行“实例对象的初始化工作”,即对象创建后,初始化当前对象的相关属性,无返回值。
_init_()的要点如下:
1.名称固定,必须为:_init_()
2.第一个参数固定,必须为:self。self指的是刚刚创建好的实例对象。
3.构造函数通常用来初始化实例对象的实例属性,如下代码就是初始化实例属性:name和score。
4.通过“类名(参数列表)”来调用构造函数。调用后,将创建好的对象返回给相应的变量。
比如:s1=Student('张三',80)
5._init_()方法:初始化创建好的对象,初始化指的是:“给实例属性赋值”
6._new_()方法:用于创建对象,但我们一般无需重定义该方法。
其他操作:
1.dir(obj) 可以获得对象的所有属性、方法
2.obj._dict_ 对象的属性字典
3.pass 空语句
4.isinstance(对象、类型) 判断“对象”是不是“指定类型”
类对象
我们在前面讲的类定义格式中,“class 类名:”。实际上,当解释器执行class语句时,就会创建一个类对象。
【操作】测试类对象的生成
执行结果: <class 'type'> |
我们可以看到实际上生成了一个变量名就是类名“Student”的对象。我们通过赋值给新变量Stu2,也能实现相关的调用。说明,确实创建了“类对象”。
【注】pass为空语句。就是表示什么都不做,只是作为一个占位符存在。当你写代码时,遇到暂时不知道往方法或者类中加入什么时,可以先用pass站位,后期再补上。
类属性和类方法
类属性
类属性是从属于“类对象”的属性,也称为“类变量”。由于,类属性从属于类对象,可以被所有实例对象共享。
类属性的定义方式:
class 类名:
类变量名 = 初始值
在类中或者类的外面,我们可以通过:“类名.类变量名”来读写。
【操作】类属性的使用测试
执行结果: 我的公司是: SXT |
类方法
执行结果: SXT |
静态方法
【操作】类方法测试
执行结果: 20+30=50 |
_del_方法(析构函数)和垃圾回收机制
_del_方法称为:“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。
Python实现自动的垃圾回收,当对象没有被引用时(引用计数为0),由垃圾回收器调用_del_方法。
我们可以通过del语句删除对象,从而保证调用_del_方法。
系统会自动提供_del_方法,一般不需要自定义析构方法。
执行结果: 销毁对象:<__main__.Person object at 0x00000240E787AC18> |
_call_方法和可调用对象
定义了_call_方法的对象,称为“可调用对象”,即该对象可以像函数一样被调用。
执行结果: 算工资啦。。。 |
方法没有重载
在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含3个部分:方法名、参数数量、参数类型。
Python中,方法的参数没有生命类型(调用时确定参数的类型),参数的数量也可以由可变参数控制,因此,Python中是没有办法的重载的。定义一个方法即可有多种调用方法,相当于实现了其他语言中的方法的重载。
如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。
建议:不要使用重名的方法!Python中方法没有重载。
执行结果: gaoqi,hello |
方法的动态性
Python是动态语言,
执行结果: 好好学习 |
执行结果: 高琪 |
@property装饰器
@property可以将一个方法的调用方式变成“属性调用”。
|
面向对象三大特征介绍
Python是面向对象的语言.也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。
●封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只
对外暴露“相关调用方法”。
通过前面学习的“私有属性、私有方法” 的方式,实现“封装”。Python追求简洁的
语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现。
●继承
继承可以让子类具有父类的特性,提高了代码的重用性。
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进
已有的算法。
●多态
多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆
是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员
休息是“敲几行代码”。
继承
继承是面向对象程序设计的重要特征,也是实现“代码复用”的重要手段。
如果一个新类继承自一个设计好的类, 就直接具备了已有类的特征,就大大降低了工作
难度。已有的类,我们称为“父类或者基类”,新的类,我们称为“子类或者派生类”。
语法格式
Python支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
class 子类类名(父类1[,父类2,...]):
类体
如果在类定义中没有指定父类,则默认父类是object类。也就是说,object是所有类的父类,里面定义了一些所有的默认实现,比如:_new_()。
定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:
父类名._init_(self,参数列表)
测试结果: [<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>] |
类成员的继承和重写
1.成员继承:子类继承了父类除构造方法之外的所有成员。
2.方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称重写
【操作】继承和重写的案例
执行结果: 高琪 的年龄是: 18 |
查看类的继承层次结构
通过类的方法mro()或者类的属性_mor_可以输出这个类的继承层次结构。
【操作】查看类的继承层次结构
执行结果: [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>] |
object根类
object类是所有类的父类,因此所有的类都有object类的属性和方法。
dir()查看对象属性
为了深入学习对象,我们先学习内置函数dir(),它可以让我们方便的看到指定对象所有的属性。
【操作】查看对象所有属性以及和object进行比对
执行结果: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] |
从上面我们可以发现这样几个要点:
1.Person对象增加了六个属性:
_dict_ _module_ _weakref_ age name say_age
2.object的所有属性,Person类作为object的子类,显然包含了所有的属性。
3.我们打印age、name、say_age,发现say_age虽然是方法,实际上也是属性,只不过这个属性的类型是“method”而已。
age<class 'int'>
name<class 'str'>
say_age<class 'method'>
重写_str_()方法
object有一个_str_()方法,用于返回一个对于“对象的描述”,对应于内置函数str()经常用于print()方法,帮助我们查看对象的信息,_str_()可以重写。
执行结果: 名字是:高琪 |
多重继承
Python支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞得异常复杂,尽量避免使用。
执行结果: aa |
MRO()
Python支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将
“从左向右”按顺序搜索。
MRO ( Method Resolution Order )方法解析顺序,我们可以通过mro()方法获得
“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找的。
执行结果: bb |
super()获得父类定义
在子类中,如果获得父类的方法时,我们可以通过super()来做。
super()代表父类的定义,不是父类对象。
执行结果: say AAA: <__main__.B object at 0x0000020ED248A240> |
多态
1.多态是方法的多态,属性没有多态。
2.多态的存在有2个必要条件:继承、方法重写。
中国人用筷子吃饭 |
特殊方法和运算重载
Python的运算符实际上是通过对象的特殊方法实现的。
比如:
执行结果: 50 50 |
我们可以重写上面的特殊方法,即实现了“运算符的重载”。
执行结果: 高琪---高希希 |
特殊属性
对象的浅拷贝和深拷贝
变量的赋值操作
只是形成了两个变量,实际还是指向同一个对象。
浅拷贝
Python拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝,因此,源对象和拷贝对象会引用同一个子对象。
深拷贝
使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不同。
执行结果: <__main__.CPU object at 0x0000019A7984B278> |
组合
“is-a”关系,我们可以使用“继承”。从而实现了子类拥有的父类的方法和属性。“is-a”关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物类。
“has-a”关系指的是这样的关系:手机拥有CPU。MobilePhone has a CPU.
执行结果: a1 a1 a1 |