九、 字符串
9.1 字符串的创建与驻留机制
字符串:基本数据类型,不可变的字符序列
字符串定义:单引号’’,双引号"",三引号""" “”"或’’’ ‘’’
用不同的定义方式定义一个value相同的字符串,内存地址是相同的。(说明内存中只有一份)
a = 'python'
b = "python"
c = '''python'''
print(a,id(a))
print(b,id(b))
print(c,id(c))
这就是字符串的驻留机制:仅保存一份相同且不可变的字符串的方法。不同的值被存放在字符串的驻留池中,Python驻留机制对相同字符串仅保留一份拷贝,后续创建相同的字符串时,不会开辟新空间,而是把该字符串的地址赋给新的变量。
驻留机制的几种情况(使用交互模式cmd)
1.字符串的长度为0或1时
s1=''
s2=''
s1 is s2
s1='&'
s2='&'
s1 is s2
2.符合标识符的字符串(只含有字母、数字、下划线)
s1='123%'
s2='123%'
s1==s2
s1 is s2
s1='abcx'
s2='abcx'
s1==s2
s1 is s2
3.字符串只在编译时进行驻留,而非运行时
a='abc'
b='ab'+'c'
c=''.join(['ab','c'])
a is b
a is c
4.[-5,256]之间的整数数字
a=-6
b=-6
a is b
a=-5
b=-5
a is b
当然,也可以强制两个字符串指向同一个对象(sys库)
import sys
a='abc%'
b='abc%'
a=sys.intern(b)
a is b
pycharm对字符串进行了优化处理,原本不驻留的字符串强制进行驻留
优点:当需要值相同的字符串时可以直接从字符串池中拿来使用,避免频繁创建和销毁,提升效率和节约内存。也正是因此,拼接字符串和修改字符串会比较影响性能(产生新的字符串对象)
所以字符串拼接时建议用join方法,而非+,因为join方法先计算出所有字符中的长度再拷贝,只新创建一次对象,效率更高。
9.2 字符串的常用操作-字符串的查询操作
1.s.index():查找子串第一次出现的位置,如果不存在会异常:ValueError
2.s.rindex():查找子串第最后一次出现的位置,如果不存在会异常:ValueError
3.s.find():查找子串第一次出现的位置,如果不存在会返回-1
4.s.rfind():查找子串最后一次出现的位置,如果不存在会返回-1
9.3 字符串的常用操作-字符串的大小写转换操作
1.s.upper():将所有字符转为大写字母
2.s.lower():将所有字符转为小写字母
3.s.swapcase():将所有大写字母转为小写字母,所有小写字母转为大写字母
4.s.capitalize():首字母转为大写,其余字母转为小写
5.s.title():每个单词的第一个字符转换为大写,每个单词的剩余字符转为小写
转换将会产生一个新的字符串对象。
9.4 字符串的常用操作-字符串内容对齐操作
1.s.center():居中对齐,第1个参数指定宽度,第2个参数可选,指定填充符,默认为空格。如果设置宽度小于实际宽度则返回原字符串。
s = 'hello,python'
print(s.center(20,'*'))
2.s.ljust():左对齐,第1个参数指定宽度,第2个参数可选,指定填充符,默认为空格。如果设置宽度小于实际宽度则返回原字符串。
s = 'hello,python'
print(s.ljust(20,'*'))
print(s.ljust(10))
3.s.rjust():右对齐,第1个参数指定宽度,第2个参数可选,指定填充符,默认为空格。如果设置宽度小于实际宽度则返回原字符串。
4.s.zfill():右对齐,左边用0填充。只有一个参数用于指定字符串宽度。如果设置宽度小于实际宽度则返回原字符串。
s = 'hello,python'
print(s.zfill(20))
a = '-8910'
print(a.zfill(8))
9.5 字符串的常用操作-字符串的劈分
1.s.split():从字符串左边开始分割,默认分隔符为空格,返回值是一个列表。
s = 'hello world python'
print(s.split())
可以指定参数sep指定分隔符
s2 = 'hello,world,python'
print(s2.split(sep=','))
可以指定参数maxsplit指定分割字符串时最大的分割次数,在经过最大次数后,剩余的子串会单独作为一部分。
s2 = 'hello,world,python'
print(s2.split(sep=',',maxsplit=1))
2.s.rsplit():从右侧开始劈分,其他与split()方法相同。(其实除了maxsplit以外,其他运行结果与split相同)
s2 = 'hello,world,python'
print(s2.rsplit(sep=',',maxsplit=1))
9.6 字符串的常用操作-字符串判断的相关方法
1.s.isidentifier():判断指定字符串是否是合法标识符(只有字母、数字、下划线)
2.s.isspace():判断指定字符串是否全部由空白字符组成(包括回车、换行、水平制表符)
3.s.isalpha():判断指定字符串是否全部由字母组成(注意:此处认为,汉字也是字母)
print('张三'.isalpha())
4.s.isdecimal():判断指定字符串是否全部由十进制数字组成(罗马数字、汉字中的数字等不认为是十进制数字)
print('ⅡⅡ'.isdecimal())
print('123四'.isdecimal())
5.s.isnumeric():判断指定字符串是否全部由数字组成(罗马数字、汉字的数字等也是数字)
print('123四'.isnumeric())
print('ⅡⅡ'.isnumeric())
6.isalnum():判断指定字符串是否全部由字母和数字组成(汉字认为是字母)
以上返回值均为True/False
9.7 字符串的常用操作-替换与合并
字符串替换:s.replace():第1个参数是指定被替换的子串,第2个参数指定替换子串的字符串。返回替换后的字符串而替换前的字符串不变。
s='hello python'
print(s.replace('python','java'))
可以使用第3个参数指定最大替换次数。
s='hello python python python'
print(s.replace('python','java',2))
字符串合并:s.join():将列表或元组或字符串中的字符串合并成一个字符串
一般用法是:‘分隔符’.join(list)
ls = ['hello','java','python']
print('|'.join(ls))
print(''.join(ls))
print('*'.join('python'))
9.8 字符串的比较操作
运算符:>,<,>=,<=,==,!=
比较规则:首先比较第一个字符,如果相等则比较下一个字符,依次比较下去,直到有字符不相等时,比较结果就是两个字符串的比较结果,后续字符不再比较。
print('apple'>'app')
print('apple'>'banana')
比较原理:比较的是每个字符的原始值(Unicode编码,即ordinary value)
通过ord()函数可以得到指定字符的ordinary value
与ord()对应的是chr()函数,指定ordinary value时可以得到对应字符
Unicode的前128项就是ASCII码
==比较的是值,is比较的是内存地址id
9.9 字符串的切片操作
字符串是不可变类型,不具备增删改的操作,因此切片操作将产生新的对象。
正向递增和反向递减两个索引体系,可混用
其他可以参见前边列表操作中的切片,语法规则一致。
字符串名[start:stop(:step)]
1.切片的结果:原字符串片段的拷贝(结果是一个新的字符串对象,id与原字符串不同)
2.切片的范围:[start,stop)
3.step可省,默认为1,省略后简写为:字符串名[start,stop]或者:字符串名[start,stop:]
4.step为正数时:start省略时默认从0开始;stop省略时默认切片到最后
5.step为负数时:start省略时默认从末尾开始;stop省略时默认切片到开头元素。step为负数时是逆序输出。
字符串连接或者分隔以后,产生的新字符串要重新索引编号。
s[::-1]就是倒序。
9.10 格式化字符串
为什么需要格式化字符串?字符串拼接会产生空间的浪费,格式化字符串会有效避免这一点
格式化字符串:按一定格式输出的字符串
两种方式:
1.%作为占位符:
%d/%i:整数
%s:字符串
%f:浮点数
是按顺序的
name = '张三'
age = 20
print('我的名字叫%s,今年%d岁' % (name,age))
2.{ }作为占位符:(format方法)
name = '张三'
age = 20
print('我的名字叫{0},今年{1}岁,我真的叫{0}'.format(name,age))
0,1是索引–所有0都用format第一个参数替换,所有1都用format第二个参数替换,以此类推。
Python3.x中的另一种{ }格式化方法:
name = '张三'
age = 20
print(f'我的名字叫{name},今年{age}岁')
字符串前加字母f,然后花括号内填参数。
表示精度和宽度等参数:
%xd:x为宽度
%.xf:x为小数精度(x位小数)
%x.yf:x为宽度,y为小数精度
{:.xf}:x为小数精度
{:.x}:x表示总共是几位数
{:x.yf}:x为宽度,y为小数精度
print('{:.3}'.format(3.1415926))
print('{:.3f}'.format(3.1415926))
print('{:10.3f}'.format(3.1415926))
9.11 字符串的编码与解码
为什么要对字符串进行编码转换?
str在A计算机内存中以Unicode表示,通过编码进行byte字节(二进制)传输,最终解码并且在B计算机显示。
编码与解码的方式:
编码:将字符串转换为二进制数据(bytes)
用encode()方法,参数为编码格式。
s = '天涯共此时'
print(s.encode(encoding='GBK'))
中文用GBK格式或者UTF-8格式。GBK格式中,一个中文占两个字节;UTF-8格式中,一个中文占三个字节
不同的编码格式决定了一个字符占用的字节数
解码:将bytes类型的数据转换成字符串类型
用byte.code()方法,参数为解码格式 byte代表的就是一个二进制数据(字节类型的数据)
byte=s.encode(encoding='GBK')
print(byte.decode(encoding='GBK'))
编码格式与解码格式应当相同,否则报错:UnicodeDecodeError
十、 函数
10.1 函数的定义与调用
什么是函数?执行特定任务以完成特定功能的一段代码
为什么需要函数?
1.复用代码
2.隐藏实现细节
3.提高可维护性
4.提高可读性便于调试
函数的创建:
def 函数名(输入参数):
? 函数体
? [return xxx]
函数命名也要遵循标识符的规则
def calc(a,b):
c=a+b
return c
函数的调用:
result = calc(20,10)
print(result)
10.2 函数调用的参数传递-位置实参和关键字实参
形式参数(形参):如上例中的a,b–起占位符的作用,位置在函数定义处
实际参数(实参):如上例中20,10–实际值,位置在函数调用处
位置实参:根据形参对应的位置进行实参传递,如上例
关键字实参:根据形参名称进行实参传递,等号左侧变量名称成为关键字参数。如下:
calc(b=10,a=20)
10.3 函数参数传递的内存分析
示例函数代码:
def fun(arg1,arg2):
print(arg1)
print(arg2)
arg1=100
arg2.append(!0)
print(arg1)
print(arg2)
n1=11
n2=[22,33,44]
print(n1)
print(n2)
print('-------------')
fun(n1,n2)
print(n1)
print(n2)
在调用函数时,arg1指向n1的值也就是11(不可变对象),arg2指向n2指向的列表(可变对象)
然后arg1指向100,arg2指向的列表n2增加10
函数调用结束后,arg1,arg2不存在了,就不存在指向谁的问题。n1仍然指向11,n2指向列表,但列表增加了10这个元素。
如果是不可变对象,在函数体内的修改不会影响实参的值;如果是可变对象,在函数体内的修改会影响实参的值。
10.4 函数的返回值
1.如果函数没有返回值,(函数执行完毕后不需要给调用后提供数据),return可省略不写,或者只写return
def fun1():
print('hello')
fun1()
2.函数的返回值,如果是1个,直接返回类型
def fun2():
return 'hello'
print(fun2())
3.函数返回多个值时,return语句中间要用逗号隔开,其返回结果为元组。
def fun3():
return 'hello','world'
print(fun3())
一个示例:
def fun(num):
odd=[]
even=[]
for i in num:
if i%2:
odd.append(i)
else:
even.append(i)
return odd,even
ls = [10,29,34,23,44,53,55]
print(fun(ls))
函数在定义时,是否需要返回值,视情况而定。
10.5 函数参数定义-默认值参数
函数定义时,给形参设置默认值,只有与默认值不符时才传递实参
def fun(a,b=10):
print(a,b)
fun(100)
fun(20,30)
默认值参数的实例:print()的end参数,默认’\n’
10.6 函数参数定义-个数可变的位置形参和个数可变的关键字形参
个数可变的位置形参:定义函数时无法确定传递的位置实参个数时可用。使用’*'定义,结果为一个元组
def fun(*args):
print(args)
fun(10)
fun(20,30)
个数可变的关键字形参:定义函数时无法确定传递的关键字实参个数时可用。使用’**'定义,结果为一个字典
def fun1(**args):
print(args)
fun1(a=10)
fun1(a=20,b=30,c=40)
实例:print的定义:
print(self, *args, sep=’ ‘, end=’\n’, file=None),所以可以写在一个print里写多个输出。
个数可变的位置形参和关键字形参只能定义一个。
当既有个数可变的位置形参,又有个数可变的关键字形参时,要求个数可变的位置形参放在个数可变的关键字形参之间
10.7 函数的参数总结
序号 | 参数的类型 | 函数的定义 | 函数的调用 | 备注 |
---|
1 | 位置实参 | | √ | | | 将序列中每个元素都转换为位置实参 | | √ | 使用* | 2 | 关键字实参 | | √ | | | 将字典中每个键值对都转换为关键字实参 | | √ | 使用** | 3 | 默认值形参 | √ | | | 4 | 关键字形参 | √ | | 使用* | 5 | 个数可变的位置形参 | √ | | 使用* | 6 | 个数可变的关键字形参 | √ | | 使用** |
补充:
1.将序列中每个元素都转换为位置实参:
def fun(a,b,c):
print(a)
print(b)
print(c)
ls = [10,20,30]
fun(ls)
fun(*ls)
2.将字典中每个键值对都转换为关键字实参:
def fun(a,b,c):
print(a)
print(b)
print(c)
dic={'a':111,'b':222,'c':333}
fun(dic)
fun(**dic)
3.位置实参和关键字实参传递可混用,必须是前边采用位置实参传递,后边采用关键字实参传递,不能反,否则报错:SyntaxError
如果想让某几个参数只能用关键字实参传递,需要在前边加*,其后边的参数都需要用关键字实参传递,如:
def fun(a,b,*,c,d):
print(a)
print(b)
print(c)
print(d)
fun(a10,30,20,30)
fun(a=10,b=30,c=20,d=30)
fun(10,20,c=30,d=40)
4.函数定义时形参的顺序问题:
从前向后:位置形参、默认值形参、个数可变的位置形参、个数可变的关键字形参
10.8 变量的作用域
变量的作用域:程序代码能访问该变量的区域
根据变量有效范围,可分为:
1.局部变量:在函数体内定义并使用的变量,只在函数内有效;如果局部变量使用global声明,就会成为全局变量。
2.全局变量:在函数体外定义的变量,可作用于函数体内外
def fun(a,b):
c=a+b
print(c)
print(c)
print(a,b)
name = '张三'
print(name)
def fun2():
print(name)
fun2()
def fun3():
global age
age = 20
print(age)
fun3()
print(age)
10.9 递归函数
递归函数:在一个函数的函数体内调用了该函数本身
递归的组成部分:递归调用与递归终止条件
递归的调用过程:每递归调用一次函数,都会在栈内存分配一个栈帧;每执行完一次函数,都会释放相应的空间
递归的优点:思路和代码简单
递归的缺点:占用内存多,效率低下
例题:用递归计算阶乘的函数:
def fac(n):
if n==1:
return 1
else:
return n*fac(n-1)
10.10 斐波那契数列
def fib(n):
if n==1:
return 1
elif n==2:
return 1
else:
return fib(n-1)+fib(n-2)
十一、 Bug、异常处理与调试
11.1 bug的由来
bug:故障
debug:排除程序故障
11.2 bug的常见类型-粗心产生的错误
粗心导致的语法错误SyntaxError
1.if、else、循环漏掉冒号
2.缩进错误
3.把英文符号写成中文符号
4.把字符串和数字拼接在一起
5.没有定义变量
6.’==‘和’='的误用混用
11.3 bug的常见类型-错误点不熟悉导致错误
1.索引越界问题
2.append()方法使用不熟练–每次只能添加一个元素,诸如此类的问题。
解决方法:熟能生巧
11.4 bug的常见类型-思路不清导致问题
解决方案:
1.使用print()函数找断点
2.使用#暂时注释部分代码
11.5 bug的常见类型-被动掉坑 及try-except
被动掉坑:程序代码逻辑没错,但因为用户错误操作或者一些例外情况导致程序崩溃
如用户在除数位置输入了0……
解决方案:异常处理机制——在异常出现时即时捕获,并且内部消化,让程序继续运行
try:
? 可能会出现异常的代码
except [异常类型](可省略):
? 异常处理代码
try:
n1=int(input('请输入被除数:''))
n2=int(input('请输入除数:'))
result=n1/n2
print(result)
except ZeroDivisionError:
print('对不起,除数不允许为0')
print('程序结束')
多个except结构:捕获异常的顺序按照先子类后父类的顺序。为了避免遗漏可能出现的情况,可以在最后加BaseException
try:
? 可能会出现异常的代码
except [异常类型1]:
? 异常处理代码
except [异常类型2]:
? 异常处理代码
except BaseException:
? 异常处理代码
try:
n1=int(input('请输入被除数:''))
n2=int(input('请输入除数:'))
result=n1/n2
print(result)
except ZeroDivisionError:
print('对不起,除数不允许为0')
except ValueError:
print('只能输入数字')
except BaseException as e:
print('出错了',e)
print('程序结束')
11.6 try-except-else结构与try-except-else-finally结构
try-except-else结构:如果try中没有抛出异常,则执行else;如有异常,则执行except并不再执行else
try:
n1=int(input('请输入被除数:''))
n2=int(input('请输入除数:'))
result=n1/n2
except BaseException as e:
print('出错了',e)
else:
print(result)
try-except-else-finally结构:无论程序是否异常,finally都会被执行。
finally常用来释放try块中申请的资源。
11.7 Python中常见的异常类型
1.ZeroDivisionError:除或取模0(各种数据类型)
2.IndexError:序列中没有此索引(字符串、列表、元组)
3.KeyError:映射中没有这个键(字典)
4.NameError:未声明/初始化对象(没有属性)
5.SyntaxError:Python语法错误
6.ValueError:传入无效参数
11.8 traceback模块的使用
Python异常处理机制:
traceback模块:打印异常信息
import traceback
try:
print(10/0)
except:
traceback.print_exc()
11.9 PyCharm的程序调试
断点:程序运行到此处,暂时挂起,停止执行。此时可以详细观察程序运行情况,方便进行下一步判断
设置断点:界面左侧,行序号右侧,单机设置断点
进入调试视图的三种方式:
1.工具栏上的小虫子按钮
2.右键单机编辑区,点击:debug’程序名’
3.快捷键shift+F9
|