第五章 字符串及正则表达式
5.1 字符串
字符串是python中的不可变数据类型
5.1.1 字符串的常用方法
# 大小写转换
s1='HelloWorld'
new_s2=s1.lower()
print(new_s2)
new_s3=s1.upper()
print(new_s3)
# 字符串的分割
e_mail='iop99yeside@163.com'
lst=e_mail.split('@')
print('邮箱名:',lst[0],'邮件服务器域名:',lst[1])
print(s1.count('o')) # o在字符串s1中出现了2次
# 检索操作
print(s1.find('o')) # o在字符串s1中首次出现的位置
print(s1.find('p')) # -1 没有找到
print(s1.index('o'))
print(s1.index('p')) # 报错,没有找到
# 判断前后缀
print(s1.startswith('H'))
print(s1.startswith('P'))
print(s1.endswith('d'))
print('demo.py',endswith('.py')) # True
s='HelloWorld'
# 字符串的替换
new_s=s.replace('o','你好',1) # 最后一个参数是替换次数,默认是全部替换
print(new_s)
# 字符串在指定位置的宽度范围内居中
print(s.center(20))
print(s.center(20,'-')) # 指定的填充
# 去掉字符串左右的空格
s=' Hello World '
print(s.strip()) # 直接去掉左右的空格
print(s.lstrip()) # 只去掉左侧的空格
print(s.rstrip()) # 只去掉右侧的空格
# 去掉指定的字符
s='dl-HelloWorld'
print(s.strip('ld')) # -HelloWor 去除字符的时候与顺序无关
print(s.lstrip('ld')) # -HelloWorld
5.1.2 格式化字符串
5.1.2.1 占位符%、f-string、format()
# (1)使用占位符进行格式化
name='林夏'
age=18
score=99.3
print('姓名:%s,年龄:%d,成绩:%f' % (name,age,score)) #注意使用方式,%+元组
print('姓名:%s,年龄:%d,成绩:%.1f' % (name,age,score)) #对小数进行简化
# (2)f-string
print(f'姓名:{name},年龄:{age},成绩:{score}')
# (3)使用字符串的format方法
print('姓名:{0},年龄:{1},成绩:{2}'.format(name,age,score)) # 花括号里是format索引位置
print('姓名:{2},年龄:{0},成绩:{1}'.format(age,score,name))
5.1.2.2 格式化字符串的详细格式
s='helloworld'
print('{0:*<20}'.format(s)) # 字符串显示的宽度为20,左对齐,空白部分用*填充
print('{0:*^20}'.format(s)) # 字符串显示的宽度为20,居中对齐,空白部分用*填充
# 居中对齐
print(s.center(20,'*'))
# 千位分隔符(只适用于整数和浮点数)
print('{0:,}'.format(41653156))
print('{0:,}'.format(4165956.3156))
# 浮点数小数部分的精度
print('{0:.2f}'.format(3.1415926))
# 字符串类型,表示的是最大显示长度
print('{0:.5}'.format(s))
# 整数类型
a=425
print('二进制:{0:b},十进制:{0:d},八进制:{0:o},十六进制:{0:x},十六进制:{0:X}'.format(a))
# 1a9,1A9 十六进制用大写X转换后就是大写,用小写x转换后就是小写
# 浮点数类型
b=3.1415926
print('{0:.2f},{0:.2E},{0:.2e},{0:.2%}'.format(b)) # 保留两位小数,科学计数法,百分数
5.1.3 字符串的编码和解码
- str转换成bytes:编码
- bytes转换成str:解码
errors:出错方式
s='暑假来了'
# 编码 str->bytes
scode=s.encode(errors='replace') # 默认是utf-8,1个中文占3个字节
print(scode)
scode_gbk=s.encode('gbk',errors='replace') # gbk 1个中文占2个字节
print(scode_gbk)
# 编码中的出错问题
s2='☎☏'
scode=s2.encode('gbk',errors='ignore') # 忽略
print(scode) # b'' 无意义码
scode=s2.encode('gbk',errors='replace') # 严格
print(scode) # b'??' 出现问号?
# scode=s2.encode('gbk',errors='strict') # 严格
# print(scode) # 报错
# 解码 bytes->str
print(bytes.decode(scode_gbk,'gbk'))
5.1.4 数据验证的方法
数据的验证是指程序对用户输入的数据进行“合法”性验证
| 方法名 | 描述说明 |
|---|---|
| str.isdigit() | 所有字符都是数字(阿拉伯数字) |
| str.isnumeric() | 所有字符都是数字 |
| str.isalpha() | 所有字符都是字母(包括中文字符) |
| str.isalnum() | 所有字符都是数字或字母(包括中文字符) |
| str.islower() | 所有字符都是小写 |
| str.isupper() | 所有字符都是大写 |
| str.istitle() | 所有字符都是首字母大写 |
| str.isspace() | 所有字符都是空白字符(\n、\t等) |
# isdigit()只识别十进制的阿拉伯数字
print('123'.isdigit()) # True
print('一二三'.isdigit()) # False
print('0b1010'.isdigit()) # False
print('Ⅷ'.isdigit()) # False
print('-'*40)
# isnumeric()所有字符都是数字
print('123'.isnumeric()) # True
print('一二三'.isnumeric()) # True
print('0b1010'.isnumeric()) # False
print('Ⅷ'.isnumeric()) # True
print('壹贰叄肆'.isnumeric()) # True
print('-'*40)
# isalpha()所有字符都是字母(包括中文字符)
print('hello你好'.isalpha()) # True
print('hello你好123'.isalpha()) # False
print('hello你好一二三'.isalpha()) # True
print('hello你好Ⅷ'.isalpha()) # False
print('hello你好壹贰叄'.isalpha()) # True
print('-'*40)
# isalnum()所有字符都是数字或字母
print('hello你好'.isalnum()) # True
print('hello你好123'.isalnum()) # True
print('hello你好一二三'.isalnum()) # True
print('hello你好Ⅷ'.isalnum()) # True
print('hello你好壹贰叄'.isalnum()) # True
print('-'*40)
# 判断字符的大小写
print('HelloWorld'.islower()) # False
print('helloworld'.islower()) # True
print('hello你好'.islower()) # True
print('HelloWorld'.isupper()) # False
print('HELLOWORLD'.isupper()) # True
print('HELLO你好'.isupper()) # True
print('-'*40)
# istitle()首字母大写
print('Hello'.istitle()) # True
print('HelloWorld'.istitle()) # False W不该大写
print('Helloworld'.istitle()) # True
print('Hello World'.istitle()) # True
print('Hello world'.istitle()) # False w该大写
print('-'*40)
# isspace()判断是否都是空白字符
print('\t'.isspace()) # True
print(' '.isspace()) # True
print('\n'.isspace()) # True
5.1.5 字符串的处理
5.1.5.1 拼接
- 使用
str.join()方法进行拼接字符串 - 直接拼接
- 使用格式化字符串进行拼接
s1='hello'
s2='world'
# (1)使用+进行拼接
print(s1+s2)
# (2)使用字符串的join()方法
print(''.join([s1,s2])) # 使用空字符串进行拼接
print('*'.join([s1,s2])) # 将每个值中间用*连接
print('你好'.join([s1,s2])) # 将每个值中间用 你好 连接
# (3)直接拼接
print('hello''world') #直接写在一起
# (4)使用格式化字符串进行拼接
print('%s%s' % (s1,s2))
print(f'{s1}{s2}')
print('{0}{1}'.format(s1,s2))
5.1.5.2 去重
s='helloworldheloaqaqaq'
# (1)字符串拼接not in
new_s=''
for item in s:
if item not in new_s:
new_s+=item # 拼接操作
print(new_s) # helowrdaq
# (2)索引+not in
new_s2=''
for i in range(len(s)):
if s[i] not in new_s2:
new_s2+=s[i] # 拼接操作
print(new_s2) # helowrdaq
# (3)通过集合去重+列表的排序操作
new_s3=set(s)
lst=list(new_s3)
print(lst) # ['w', 'e', 'o', 'd', 'q', 'h', 'a', 'r', 'l']
lst.sort(key=s.index)
print(''.join(lst)) # helowrdaq
5.2 正则表达式
元字符:
- 具有特殊意义的字符
- 例如:“^”和“$”分别表示匹配的开始和结束
| 元字符 | 描述说明 | 举例 | 结果 |
|---|---|---|---|
| . | 匹配任意字符(除\n) | p\nytho\tn | p、y、t、h、o、\t、n |
| \w | 匹配字母、数字、下划线 | python\n123 | p、y、t、h、o、n、1、2、3 |
| \W | 匹配非字母、数字、下划线 | python\n123 | \n |
| \s | 匹配任意空白字符 | python\t123 | \t |
| \S | 匹配任意非空白字符 | python\t123 | p、y、t、h、o、n、1、2、3 |
| \d | 匹配任意十进制数 | python\t123 | 1、2、3 |
限定符:
- 用于限定匹配的次数
| 限定符 | 描述说明 | 举例 | 结果 |
|---|---|---|---|
| ? | 匹配前面的字符0次或1次 | colou?r | 可以匹配color或colour |
| + | 匹配前面的字符1次或多次 | colou+r | 可以匹配colour或colouu...r |
| * | 匹配前面的字符0次或多次 | colou*r | 可以匹配color或colouu...r |
| {n} | 匹配前面的字符n次 | colou{2}r | 可以匹配colouur |
| {n,} | 匹配前面的字符最少n次 | colou{2,}r | 可以匹配colouur或colouuu...r |
| {n,m} | 匹配前面的字符最小n次,最多m次 | colou{2,4}r | 可以匹配colouur或colouuur或colouuuur |
| 区间字符[] | 匹配[]中所指定的字符 | [.?!] [0-9] | 匹配标点符号点、问号、感叹号 匹配0、1、2、3、4、5、6、7、8、9 |
| 排除字符^ | 匹配不在[]中指定的字符 | [^0-9] | 匹配除0、1、2、3、4、5、6、7、8、9的字符 |
| 选择字符 I (竖线) | 用于匹配 I 左右的任意字符 | \d{18}I\d{15} | 匹配15位身份证或18位身份证 |
| 转义字符 | 同python中的转义字符 | \ . | 将.作为普通字符使用 |
| [\u4e00-\u9fa5] | 匹配任意一个汉字 | ||
| 分组 ( ) | 改变限定符的作用 | sixIfourth (sixIfour)th | 匹配six或foueth 匹配sixth或fourth |
5.3 re模块
- python中的内置模块
- 用于实现python中的正则表达式操作
| 函数 | 功能描述 |
|---|---|
| re.match(pattern,string,flags=0) | 用于从字符串的开始位置进行匹配,如果起始位置匹配成功,结果为Match对象,否则结果为None |
| re.search(pattern,string,flags=0) | 用于在整个字符串中搜索第一个匹配值,如果匹配成功,结果为Match对象,否则结果为None |
| re.findall(pattern,string,flags=0) | 用于在整个字符串搜索所有符合正则表达式的值,结果是一个列表类型 |
| re.sub(pattern,string,flags=0) | 用于实现字符串中对指定子串的替换 |
| re.split(pattern,string,flags=0) | 字符串中的split()方法功能相同,都是分隔字符串 |
5.3.1 re模块中的match函数
import re # 导入
pattern='\d.\d+' # 模式字符串 +限定符,\d 0-9数字出现1次或多次 含义是:数字.数字...
s='I study python 3.11 every day' # 待匹配字符串
match=re.match(pattern,s,re.I) # 用pattern规则在s中查找 re.I意思是忽略大小写
print(match) # None
s2='3.11python I study every day'
match2=re.match(pattern,s2)
print(match2) # <re.Match object; span=(0, 4), match='3.11'>
print('匹配值的起始位置:',match2.start())
print('匹配值的结束位置:',match2.end())
print('匹配区间的位置元素:',match2.span())
print('待匹配的字符串:',match2.string)
print('匹配的数据:',match2.group())
5.3.2 re模块中的search函数
import re # 导入
pattern='\d.\d+'
s='I study python 3.11 every day python2.7 i love u'
match=re.search(pattern,s)
print(match) # <re.Match object; span=(15, 19), match='3.11'> 3.11找到了,2.7没找
s2='4.10python I study every day'
match2=re.search(pattern,s2)
print(match2) # <re.Match object; span=(0, 4), match='4.10'>
s3='python I study every day'
match3=re.search(pattern,s3)
print(match3) # None
print(match.group()) # 3.11
print(match2.group()) # 4.10
# findall()在整个字符串中查找所有
lst=re.findall(pattern,s)
lst2=re.findall(pattern,s2)
lst3=re.findall(pattern,s3)
print(lst) # ['3.11', '2.7']
print(lst2) # ['4.10']
print(lst3) # []
5.3.4 re模块中的sub函数和split函数
import re # 导入
pattern='hei客|破解'
s='我想当hei客,学python是为了破解一些vip视频'
new_s=re.sub(pattern,'XXX',s)
print(new_s) # 我想当XXX,学python是为了XXX一些vip视频
s2='https://cn.bing.com/search?q=iop&qs=n&fo'
pattern2='[?|&]'
lst=re.split(pattern2,s2) # 用pattern2的规则去到s2进行拆分
print(lst) # ['https://cn.bing.com/search', 'q=iop', 'qs=n', 'fo']
章节习题
习题1:车牌归属地
需求:使用列表存储N个车牌号码,通过遍历列表及字符串的切片操作判断车牌的归属地
运行效果:
lst=['京A8888','京A1111','沪A6666']
for item in lst:
area=item[0:1]
print(item,'归属地为',area)
习题2:统计指定字符出现的次数
需求:声明一个字符串,内容为“HelloPython,HelloJava,hellophp”用户从键盘录入要查询的字符(部分大小写),要求统计出要查找的字符在字符串中出现的次数
s='HelloPython,HelloJava,hellophp'
word=input('请输入要统计的字符的大写形式:')
print('{0}在{1}一共出现了{2}次'.format(word,s,s.upper().count(word))) # upper()都转成大写
习题3:格式化输出商品信息
需求:使用列表存储一些商品数据,使用循环遍历输出商品信息,要求对商品的编号进行格式化为6位,单价保留2位小数,并在前面添加人民币符号输出
运行效果:
lst = [
['01', '电风扇', '美的', '100'],
['02', '洗衣机', 'TCL', '1000'],
['03', '微波炉', '小熊', '400']
]
print('编号\t\t名称\t\t\t品牌\t\t单价')
for item in lst:
for i in item:
print(i,end='\t\t')
print() # 换行
# 格式化
for item in lst:
item[0] = '0000' + item[0]
item[3] = '¥{0:.2}'.format(item[3]) # 连接上索引为3的字符且保留两位小数
# 遍历输出
print('编号\t\t\t名称\t\t\t品牌\t\t单价')
for item in lst:
for i in item:
print(i,end='\t\t')
print() # 换行
习题4:使用正则表达式提取图片网址
需求:从给定的文本中使用正则表达式提取出所有的图片链接地址
运行效果:
import re
s='"queryEnc":"%c3%C0%C5%AE","queryExt":"美女”,"listNum":1726,"displayNum":1102160,"gsm":"3c","bdFmtDispNum" :"丝1,100,000","bdsearchTime":"","isNeedAsyncRequest":8,"bdIsclustered":"1","data":[{ "adType" :"g""hasAspData":"0","thumbuRl":"https:/img1,baidu.com/it/u=272155668,1962283813&fm=26&fmt=auto","commoditvInfo":null"iscommodity":0,"middleURL":"https://img1,baidu.com/it/u=272155668,1962283813&fm=26&fmt=auto"shituToken":"aadb3a","largeTnImageUrl":","hasLarge":0,"hoverURL":"https://img1.baidu.com/it/u=272155668,1962283813&fm=26&fmt=auto", "pageNum":30, "objURL":"ipprf z2C$qAzdH3FAzdH3F2t42d z&e3Bkwt17z&e3Bv54AzdH3Ft4w2j fjw6viAzdH3Ff6v=ippr%nA%dF%dFetn z&e3Bxt78dn z&e3Bvg%dfstei%dFda81%dFam%dFd1%dF88%df8ande8cm80banenllalnba80 z&e3B3r2&6jui6=ippr%nA%dF%dFetn z&e3Bxt78dn z&e3Bvg&wrr=daad&ftzj=u1111,8aaaa&g=wba&g=a82=ag&u4p=3rj2?fjv=8m9abc98cd&p=an8vwcw9v9jl1nm88jdllww8dmuwnvvn""fromURL":"ippr z2C"fromJumpUrl":"ippr z2C$GAzdH3FAzdH3Fe z&e3Bm z&e3BvgAzdH3Fr65utsiAzdH3FowpviMtgt z&e3Brir?et1=m8adm8"$qAzdH3FAzdH3Fe z&e3Bm z&e3BvgAzdH3Fr65utsjAzdH3FowpviMtgt z&e3Brir?et1=m8adm8","fromURLHost":"v.6.cn","width":800,"height":600,"type":"jpg""currentIndex"."""is gif":0,"iscopyright":0,"resourceInfo":null,"is":"@ 日""strategyAssessment":"3141544242 1243 0 0""filesize":"""bdSrcType":"@"."di":"157630","pi":“""imgcollectionword":"","hasThumbData" :"""bdSetImgNum":0,"partnerId":0,"spn":0,"bdImgnewsDate":"2020-06-0302:31","fromPageTitle":"美女</strong>热舞","fromPageTitleEnc":"美女热舞","bdSourceName":"""bdFromPageTitlePrefix":"""isAspDianiing":0,"token":"""272155668,1962283813","os""imgType""CS"。"1570395708,812629700","simid":"272155668,1962283813""personalized":"0","simid info":null,"face info":null,"xiangshi info":null,"adpicId":"0","source type":""},{ "adType":"0", "hasAspData":"0","thumbuRL":"https://img0.baidu.com/it/u=1934854801,2871685401&fm=253&fmt=auto&app=138&f=JPEG?W=508&h=313","commodityInfo":nul1,"iscommodity":0,"middleURl":"httos://img8.baidu.com/it/u=1934854801,2871685401&fm=253&fmt=auto&app=138&f=JPEG?W=500&h=313","shituToken":"9bb791","largeTnImageurl"."""hasLarge" :0, "hoverURl":"https://img,baidu,com/it/u=1934854801,28716854018fm=253&fmt=uto&apD=138&f=JPEG?W=50B&h=313", "pageNum":31,"obiURL":"ipprf z2C$9AZdH3FAzdH3F2t42d z&e3Bkwt17 2&e3Bv54AzdH3Ft4w2i fiw6viAzdH3Ff6v=ippr%nA%dF%dFt42 2&e3B33da z&e3Bv54%df7r%dfwsst42%dF8889%dfac8dd8as1n1%dFd8ac8dasIn1-d-8daa z&e3B3r2&6juj6=ippr%nA%dF%dft42 &e3833da &e3Bv54&wrr=daad&ftzi=ul111,8aaaa&g=wba&g=a&2=ag&u4p=3ri2?fiv='
pattern='https://img\d{1}.baidu.com/it/u=\d*,\d*&fm=\d*&fmt=auto' # 模式字符串
lst = re.findall(pattern,s) # 使用pattern模式到s中查找,赋值给lst列表
for item in lst:
print(item) # 输出
6.1 python中的异常处理
6.1.1 try-except
try: # 把可能出现异常的代码放在try中
num1 = int(input('请输入一个整数:'))
num2 = int(input('请输入另一个整数:'))
result = num1 / num2
print('结果:',result)
except ZeroDivisionError: # 一旦出现异常,需要执行的代码
print('除数为0')
try:
num1 = int(input('请输入一个整数:'))
num2 = int(input('请输入另一个整数:'))
result = num1 / num2
print('结果:',result)
except ZeroDivisionError:
print('除数为0')
except ValueError:
print('不能将字符串转换为数字')
except BaseException:
print('未知异常')
6.1.2 try-except-else-finally
try:
num1 = int(input('请输入一个整数:'))
num2 = int(input('请输入另一个整数:'))
result = num1 / num2
except ZeroDivisionError:
print('除数为0')
except ValueError:
print('不能将字符串转换为数字')
except BaseException:
print('未知异常')
else: # else是程序正常执行结束所执行的代码
print('结果:',result) # 当整个计算结果都没有问题时会执行else
try:
num1 = int(input('请输入一个整数:'))
num2 = int(input('请输入另一个整数:'))
result = num1 / num2
except ZeroDivisionError:
print('除数为0')
except ValueError:
print('不能将字符串转换为数字')
except BaseException:
print('未知异常')
else: # else是程序正常执行结束所执行的代码
print('结果:',result) # 当整个计算结果都没有问题时会执行else
finally:
print('程序执行结束!')
6.2 raise关键字的使用
- 抛出一个异常,从而提醒程序出现了异常情况,程序能够正确地处理这些异常情况
raise关键字的语法结构为:
raise [Exception类型(异常描述信息)]
try:
gender=input('请输入您的性别:')
if gender!='女'and gender!='男': # 当用户输入错误时判断为True,执行第4行
raise Exception('性别只能是女或男!') # 抛出这个异常对象,这个异常对象会被第7行的Exception捕获
else:
print('您的性别是:',gender)
except Exception as e: # 抛出的这个异常对象取个名字叫做e
print(e) # 然后把第4行的异常描述信息“性别只能是女或男!”在第8行输出
# 第4行的“性别只能是女或男!”是描述信息,描述信息只会在出现异常时输出
6.3 python中常见的异常类型
| 异常类型 | 描述说明 |
|---|---|
| ZeroDivisionError | 当除数为0时,引发的异常 |
| IndexError | 索引超出范围所引发的异常 |
| KeyError | 字典取值时key不存在的异常 |
| NameError | 使用一个没有声明的变量时引发的异常 |
| SyntaxError | python中的语法错误 |
| ValueError | 传入的值错误 |
| AttributeError | 属性或方法不存在引发的异常 |
| TypeError | 类型不合适引发的异常 |
| IndentationError | 不正确的缩进引发的异常 |
6.4 PyCharm的程序调试
使用PyCharm进行代码调试的操作步骤:
- 设置断点
断点通常设置在循环处 - 进入调试视图
-
进入调试视图的3种方式:
- 单击工具栏上的调试按钮
- 右键单击编辑区:点击“Debug模块名”
- 快捷键:Shift + F9
-
- 开始调试
章节习题
习题1:输入成绩如果不正确手动抛出异常
需求:如果分数在0-100之间,输出成绩。如果成绩不在该范围内,抛出异常信息,提示分数必须在0-100之间
try:
score=eval(input('请输入分数:'))
if 0<=score<=100:
print('分数为:',score)
else:
raise Exception('分数不正确!')
except Exception as e:
print(e)
习题2:判断是否构成三角形
需求:编写程序实现组成三角形的判断,如果不能则抛出异常Exception,显示异常信息“a,b,c不能构成三角形”,如果可以构成则显示三角形三个边长
try:
a = int(input('请输入第一条边长:'))
b = int(input('请输入第二条边长:'))
c = int(input('请输入第三条边长:'))
if a+b>c and a+c>b and b+c>a:
print('三角形的边长为:',a,b,c)
else:
raise Exception(f'{a},{b},{c},无法构成三角形!')
except Exception as e:
print(e)
第七章 函数及常用的内置函数
7.1 函数的定义及调用
函数是将一段实现功能的完整代码,使用函数名称进行封装,通过函数名称进行调用。以此达到一次编写,多次调用的目的
内置函数:
- 输出函数
print() - 输入函数
input()
- 列表定义函数
list()
自定义函数:
def 函数名称(参数列表):
函数体
[return返回值列表] # 非必须部分
函数调用:函数名(参数列表)
def get_sum(num): # 函数定义处的参数是形式参数
s=0
for i in range(1,num+1):
s+=i
print(f'1到{num}之间的累加和为:{s}')
# 调用函数
get_sum(10) # 函数调用处的参数是实际参数
get_sum(100)
get_sum(1000)
函数返回结果:
- 函数执行结束后,如果使用
return进行返回结果,则结果被返回到函数调用处
7.2 函数的参数传递
7.2.1 位置传参和关键字传参
位置参数:是指调用时的参数个数和顺序必须与定义的参数个数和顺序相同
def happy_birthday(name,age):
print('祝'+name+'生日快乐!')
print(str(age)+'岁生日快乐')
happy_birthday('iop',20)
happy_birthday('iop') # 报错 happy_birthday() missing 1 required positional argument: 'age'
happy_birthday(20,'iop') # 报错 can only concatenate str (not "int") to str
关键字参数:是在函数调用时,使用“形参名称=值”的方法进行传参,传递参数顺序可以与定义时参数的顺序不同
happy_birthday(age=20,name='iop')
# 位置参数和关键字参数混合使用时,必须位置参数在前,关键字参数在后
happy_birthday('iop',age=20)
happy_birthday(name='iop',20) # 报错
7.2.2 默认值参数
默认值参数:是在函数定义时,直接对形式参数进行赋值,在调用时如果该参数不传值,将使用默认值,如果该参数传值,则使用传递的值
def happy_birthday(name='iop',age=20): # 设置了默认值
print('祝'+name+'生日快乐!')
print(str(age)+'岁生日快乐')
happy_birthday() # 没有传参就用默认值
happy_birthday('qwe') # 只传一个,其余采用默认值
happy_birthday(19) # 报错 19会被传给name
注意:当位置参数和默认值参数混合使用时,必须位置参数在前,默认值参数在后
7.2.3 可变参数
又分为个数可变的位置参数和个数可变的关键字参数两种,其中个数可变的位置参数是在参数前加一颗星(*para),para形式参数的名称,函数调用时可接收任意个数的实际参数,并放到一个元组中。个数可变的关键字参数是在参数前加两颗星(**para)在函数调用时可接收任意多个“参数=值”形式的参数,并放到一个字典中。
# 个数可变的位置参数
def fun(*para):
print(type(para)) # <class 'tuple'>
for item in para:
print(item)
# 调用
fun(10,20,30,40)
fun(10)
fun(10,20)
fun([10,20,30]) # 实际上传的是一个参数
# 在调用时,参数前面加一颗星*,就可以将列表解包
fun(*[10,20,30])
# 个数可变的关键字参数
def fun(**para):
print(type(para)) # <class 'dict'>
for key,value in para.items():
print(key,'-->',value)
# 调用,要用赋值的形式
fun(name='iop',age=20)
d={'name':'iop','age':20} # 参数是字典
# fun(d) # 报错 fun() takes 0 positional arguments but 1 was given
fun(**d) # 当参数是字典时,必须加上**
7.3 函数的返回值
- 如果函数的运行结果需要在其它函数中使用,那么这个函数就应该被定义为带返回值的函数。
- 函数的运行结果使用
return关键字进行返回 return可以出现在函数中的任意一个位置,用于结束函数- 返回值可以是一个值,或多个值,如果返回的值是多个,结果是一个元组类型。
# 函数的返回值
def calc(a,b):
print(a+b)
calc(10,10) # 正常
print(calc(1,2)) # None 因为这个函数定义时没有使用return返回任何结果
# 带返回值
def calc2(a,b):
s=a+b
return s # 将s返回给函数的调用处去处理
get_s=calc2(10,10)
print(get_s)
get_s2=calc2(calc2(10,10),10)
print(get_s2)
# 返回值多个
def get_sum3(num):
s=0 # 累加和
odd_sum=0 # 奇数和
even_sum=0 # 偶数和
for i in range(1,num+1):
if i%2!=0: # 说明是奇数
odd_sum+=i
else:
even_sum+=i
s+=i
return odd_sum,even_sum,s
result=get_sum3(10)
print(type(result)) # <class 'tuple'>
print(result) # (25, 30, 55)
# 解包
a,b,c=get_sum3(10) # 返回3个值
print(a) # 25
print(b) # 30
print(c) # 55
7.4 变量的作用域
变量的作用域是指变量起作用的范围,根据范围作用的大小可分为局部变量和全局变量
7.4.1 局部变量
- 定义:在函数定义处的参数和函数内部定义的变量
- 作用范围:仅在函数内部,函数执行结束,局部变量的生命周期也结束
def calc(a,b):
s=a+b
return s
print(a,b,s) # 报错 因为a、b、s是局部变量
result=calc(10,20)
print(result)
7.4.2 全局变量
- 定义:在函数外定义的变量或函数内部使用
global关键字修饰的变量 - 作用范围:整个程序,程序运行结束全局变量的生命周期才结束
a=100 # 全局变量
def calc(x,y):
return a+x+y
print(a) # 100
print(calc(10,20)) # 130
def calc2(x,y):
a=200 # 局部变量,局部变量名称和全局变量名称是相同的
return a+x+y
print(a) # 100
print(calc2(10,20)) # 230 此时a使用的是函数calc2中的局部变量
# 当全局变量和局部变量名称相同时,局部变量的优先级更高
def calc3(x,y):
global s # s是在函数中定义的变量,但是使用了global关键字声明,变成了全局变量
s=300 # 注意:声明和赋值必须分开执行,不能放在一起
return s+x+y
print(s) # 报错,s必须放在函数执行后才被声明了,否则找不到
print(calc3(10,20))
print(s) # 300
7.5 匿名函数 lambda
lambda是指没有名字的函数,这种函数只能使用一次,一般是在函数的函数体只有一句代码且只有一个返回值时,可以使用匿名函数来简化
语法结构:
result=lambda 参数列表:表达式
def calc(a,b):
return a+b # 这就是只有一个函数体的函数
print(calc(10,20))
# 用lambda表达式进行简化
s=lambda a,b:a+b # s表示一个匿名函数
# 调用匿名函数
print(s(10,20))
lst=[10,20,30,40]
# 列表的正常取值操作:遍历
for i in range(len(lst)):
print(lst[i])
print()
# 使用匿名函数进行取值
for i in range(len(lst)):
result=lambda x:x[i] # 根据索引取值,result的类型是函数,x是形式参数
print(result(lst)) # lst是实际参数
student_scores=[
{'name': '韩梅梅', 'score': '90'},
{'name': '王一一', 'score': '95'},
{'name': '林笑笑', 'score': '97'},
{'name': '张乐乐', 'score': '86'},
]
# 对列表进行排序,排序的规则是字典的成绩
student_scores.sort(key=lambda x:x.get('score'),reverse=True) # 降序
print(student_scores)
7.6 函数的递归操作
- 在一个函数的函数体内调用该函数本身,该函数就是递归函数
- 一个完整的递归操作由两部分组成,一部分是递归调用,一部分是递归终止条件,一般可使用
if-else结构来判断递归的调用和递归的终止。
def fac(n): # n的阶乘 N!=N*(N-1)......1!=1
if n==1:
return 1
else:
return n*fac(n-1) # 自己调用自己
print(fac(5)) # 120
7.7 斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割线,是因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、…从第三项开始,每项都等于前两项之和
公式为:f(n) = f(n-1) + f(n-2)
def fac(n):
if n==1 or n==2:
return 1
else:
return fac(n-1)+fac(n-2)
print(fac(9)) # 第9位上的数字
# 把所有数字打印输出
for i in range(1,10):
print(fac(i),end=' ')
print()
7.8 常用的内置函数
7.8.1 类型转换函数
| 函数名称 | 描述说明 |
|---|---|
| bool(obj) | 获取指定对象obj的布尔值 |
| str(obj) | 将指定对象obj转成字符串类型 |
| int(x) | 将x转成int类型 |
| float(x) | 将x转成float类型 |
| list(sequence) | 将序列转成列表类型 |
| tuple(sequence) | 将序列转成元组类型 |
| set(sequence) | 将序列转成集合类型 |
7.8.2 数学函数
| 函数名称 | 描述说明 |
|---|---|
| abs(x) | 获取x的绝对值 |
| divmod(x,y) | 获取x与y的商和余数 |
| max(sequence) | 获取sequence的最大值 |
| min(sequence) | 获取sequence的最小值 |
| sum(iter) | 对可迭代对象进行求和运算 |
| pow(x,y) | 获取x的y次幂 |
| round(x,d) | 对x进行保留d位小数,结果四舍五入 |
print(round(3.1415926)) # 3 round函数只有一个参数,保留整数
print(round(3.9415926)) # 4 四舍五入
print(round(3.1415926,2)) # 3.14
print(round(314.15926,-1)) # 310.0 -1位会对个位进行四舍五入
print(round(314.15926,-2)) # 300.0 -2位会对十位进行四舍五入
7.8.3 迭代器操作函数
| 函数名称 | 描述说明 |
|---|---|
| sorted(iter) | 对可迭代对象进行排序 |
| reversed(sequence) | 反转序列生成新的迭代器对象 |
| zip(iter1,iter2) | 将iter1与iter2打包成元组并返回一个可迭代的zip对象 |
| enumerate(iter) | 根据iter对象创建一个enumerate对象 |
| all(iter) | 判断可迭代对象iter中所有元素的布尔值是否都为True |
| any(iter) | 判断可迭代对象iter中所有元素的布尔值是否都为False |
| next(iter) | 获取迭代器的下一个元素 |
| filter(function,iter) | 通过指定条件过滤序列并返回一个迭代器对象 |
| map(function,iter) | 通过函数function对可迭代对象iter的操作返回一个迭代器对象 |
lst=[54,56,12,85,19]
# (1)排序操作 sorted()
asc_lst=sorted(lst) # 默认升序操作
dasc_lst=sorted(lst,reverse=True) # 修改为降序
print('原列表:',lst) # 原列表: [54, 56, 12, 85, 19]
print('升序:',asc_lst) # 升序: [12, 19, 54, 56, 85]
print('降序:',dasc_lst) # 降序: [85, 56, 54, 19, 12]
# (2)反向 reversed()
new_lst=reversed(lst)
print(type(new_lst)) # <class 'list_reverseiterator'> 结果是迭代器对象,不是列表
print(new_lst) # <list_reverseiterator object at 0x0000019D2753CD00>
print(list(new_lst)) # 需要使用list()转成列表才能看得到 [19, 85, 12, 56, 54]
# (3)zip()
x=['a','b','c','d']
y=[10,20,30,40,50] # 注意:y比x多一个,在进行打包时会以短的那个为主
zipobj=zip(x,y)
print(type(zipobj)) # <class 'zip'>
print(zipobj) # <zip object at 0x0000026C3F8D7FC0>
print(list(zipobj)) # 需要使用list()转成列表才能看得到 [('a', 10), ('b', 20), ('c', 30), ('d', 40)],是一个元组
# (4)enumerate()
enum=enumerate(y,start=1)
print(type(enum)) # <class 'enumerate'>
print(tuple(enum)) # 转成元组才能看到(列表也行)
# (5)all()
lst2=[10,20,'',30]
print(all(lst2)) # False 因为列表中有一个空字符串,空字符串的bool值为False,当列表中有一个bool为False,结果就是False
print(all(lst)) # True 当列表中所有的bool为True,结果才是True
# (6)any()
print(any(lst2)) # True 列表中只要有一个bool为True,结果就是True
# (7)next()
x2=['a','b','c','d']
y2=[10,20,30,40,50]
zipobj2=zip(x2,y2)
print(next(zipobj2)) # ('a', 10)
print(next(zipobj2)) # ('b', 20)
print(next(zipobj2)) # ('c', 30) 每运行一次就会在zipobj2中获取一个元素
# (8)filter()
def fun(num):
return num%2==1 # 可能是True也可能是False,只有奇数的返回结果才是True
obj=filter(fun,range(10)) # 函数作为参数,不是调用,函数作为参数是不需要小括号的;将range(10)即0-9都执行一次fun操作
print(list(obj)) # [1, 3, 5, 7, 9]
# (9)map()
def upper(x):
return x.upper()
new_lst2=['hello','world','python']
obj2=map(upper,new_lst2)
print(list(obj2)) # ['HELLO', 'WORLD', 'PYTHON']
7.8.4 其他函数
| 函数名称 | 描述说明 |
|---|---|
| format(value,format_spec) | 将value以format_spec格式进行显示 |
| len(s) | 获取s的长度或s元素的个数 |
| id(obj) | 获取对象的内存地址 |
| type(x) | 获取x的数据类型 |
| eval(s) | 执行s这个字符串所表示的Python代码(去掉字符串左右的引号进行计算) |
print(format(3.14,'20')) # 数值型默认右对齐
print(format('hello','20')) # 字符串型默认左对齐
print(format('hello','*<20')) # <左对齐,*为填充符
print(format('hello','*^20'))
print(format(3.14159,'.2f')) # 保留两位小数
print(format(20,'b')) # 二进制
print(format(20,'o')) # 八进制
print(format(20,'x')) # 十六进制
章节习题
习题1:计算列表元素的最大值
需求:随机产生10个元素,存储到列表中,编写函数获取这个列表中元素的最大值(不能使用内置函数
max())
import random
def get_max(lst):
x=lst[0] # x用于存储元素中的最大值
# 遍历
for i in range(1,len(lst)): # 因为最大值的初始赋值i=0,所以跳过索引0
if lst[i]>x:
x=lst[i] # 对最大值进行重新赋值
return x
# 调用
lst=[random.randint(1,100) for item in range(10)]
print(lst)
# 计算列表中元素的最大值
max=get_max(lst)
print(max)
习题2:提取字符串中所有的数字并求和
需求:使用
input()获取一个字符串,编写并传参,使用isdigit()方法提取字符串中所有的数字,并对提取的数字进行求和计算,最后将存储数字的列表和累加和返回
def get_digit(x):
s=0 # 存储累加和
lst=[] # 存储提取出来的数字
for item in x:
if item.isdigit(): # 如果是数字
lst.append(int(item))
# 求和
s=sum(lst)
return lst,s
# 准备函数的调用
s=input('请输入一个字符串:')
# 调用
lst,x=get_digit(s)
print('提取的数字列表为:',lst)
print('累加和为:',x)
习题3:字符串中字母大小写转换
需求:使用
input()获取一个字符串,编写并传参,将字符串中所有的小写字母转成大写字母,将大写字母转成小写字母
def lower_upper(x):
lst=[]
for item in x:
if 'A'<=item<='Z':
lst.append(chr(ord(item) + 32)) # ord()将字母转成Unicode码整数,加上32,chr()将码转成字符
elif 'a'<=item<='z':
lst.append(chr(ord(item) - 32)) # 小写转大写
else:
lst.append(item) # 其它的直接添加到列表当中
return ''.join(lst) # 将列表中每个元素进行拼接
# 函数调用
s=input('请输入一个字符串:')
new_s=lower_upper(s) # 函数的调用
print(new_s)
习题4:实现操作符in的判断功能
需求:使用
input()从键盘获取一个字符串,判断这个字符串在列表中是否存在(函数体不能使用in),返回结果为True 或 False
def get_find(s,lst):
for item in lst:
if s==item:
return True
return False
lst=['hello','world','python']
s=input('请输入需要判断的字符串:')
result=get_find(s,lst)
print('存在' if result else '不存在') # if...else的简写,被称为“三元运算符”;if result==True,if result是利用对象的bool值
第八章 面向对象程序设计
8.1 类和对象
8.1.1 自定义类和创建自定义类的对象
类是怎么来的?
- 是由N多个对象抽取出“像”的属性和行为从而归纳总结出来的一种类别
自定义数据类型的语法结构为:
class 类名():
pass
# 编写一个Person类
class Person():
pass
# 编写一个Student类
class Student:
pass
类相当于图纸,是一个抽象的模板
创建对象的语法格式为: 对象名=类名()
per=Person() # per是Person类型的对象
stu=Student() # stu是Student类型的对象
对象是一个具体的事物
8.1.2 类的组成
| 类的组成 | 描述 |
|---|---|
| 类属性 | 直接定义在类中,方法外的变量 |
| 实例属性 | 定义在__int__方法中,使用self打点的变量 |
| 实例方法 | 定义在类中的函数,而且自带参数self |
| 静态方法 | 使用装饰器@staticmethod修饰的方法 |
| 类方法 | 使用装饰器@classmethod修饰的方法 |
class Student:
# 类属性:定义在类中,方法外的变量
school='北京xx教育'
# 初始化方法
def __init__(self,xm,age): # xm、age是方法的参数,是局部变量,作用域是整个__init__方法
self.name=xm # 等号左边self.name是实例属性,右边xm是局部变量;将局部变量xm赋值给实例属性self.name
self.age=age # 实例的名称和局部变量的名称可以相同
# 定义在类中的函数,称为方法,自带一个参数self
def show(self):
print(f'我叫{self.name},我今年{self.age}岁了') # 不能用xm,因为那是局部变量,但可以用实例属性self.name,其作用范围在整个类中
# 静态方法
@staticmethod
def sm():
print('这是一个静态方法')
# 静态方法不能调用实例属性,也不能调用实例方法
# 类方法
@classmethod
def cm(cls): # 自带一个cls,cls是class的简写
print('这是一个类方法')
# 类方法不能调用实例属性,也不能调用实例方法
# 创建类的对象
stu=Student('iop',20) # 传入两个参数是因为__init__中有两个形参;self是自带的参数,无需手动传入;传入参数后赋值给stu
# 实例属性,是使用对象名进行打点调用的
print(stu.name,stu.age) # 对象名stu
# 类属性,直接使用类名打点调用
print(Student.school) # 类名Student
# 实例方法,使用对象名打点调用
stu.show() # 直接调用即可
# 静态方法,@staticmethod进行修饰的方法,直接使用类名打点调用
Student.sm()
# 类方法,@classmethod进行修饰的方法,直接使用类名打点调用
Student.cm()
8.1.3 使用类模板创建N多个对象
class Student:
school='北京xx教育'
def __init__(self,xm,age):
self.name=xm
self.age=age
def show(self):
print(f'我叫{self.name},我今年{self.age}岁了')
# 根据“图纸”可以创建N多个对象
stu=Student('iop',20)
stu2=Student('qwe',18)
stu3=Student('wer',19)
stu4=Student('ert',21)
Student.school='北京xx教育' # 给类的属性赋值
# 将上面四个学生对象存储到列表中
lst=[stu,stu2,stu3,stu4] # 列表中的元素是Student类型的对象
for item in lst: # item是列表中的元素,是Student类型的对象
item.show() # 对象名打点调用实例方法
8.2 动态绑定属性和方法
- 每个对象的属性名称相同,但属性值不同
- 可以为某个对象绑定独有的属性或方法
class Student:
school='北京xx教育'
def __init__(self,xm,age):
self.name=xm
self.age=age
def show(self):
print(f'我叫{self.name},我今年{self.age}岁了')
# 创建两个Student类型的对象
stu=Student('iop',20)
stu2=Student('qwe',18)
print(stu.name,stu.age)
print(stu2.name,stu2.age)
# 为stu2动态绑定一个实例属性
stu2.gender='女'
print(stu2.name,stu2.age,stu2.gender)
print(stu.gender) # 报错,gender只给stu2绑了,没有给stu绑
# 动态绑定方法
def introduce():
print('我是一个普通的函数,我被动态绑定成了stu2对象的方法')
stu2.fun=introduce # 函数的赋值,注意不要加小括号,加小括号就是调用了
# 调用
stu2.fun()
8.3 python中的权限控制
面向对象的三大特征:
- 封装:隐藏内部细节,对外提供操作方式
- 继承:是在函数调用时,使用“
形参名称=值”的方式进行传参,传递参数顺序可以与定义时参数的顺序不同 - 多态:是在函数定义时,直接对形式参数进行赋值在调用时如果该参数不传值,将使用默认值如果该参数传值,则使用传递的值
8.3.1 封装
8.3.1.1 权限控制
权限控制:是通过对属性或方法添加单下划线、双下划线以及首尾双下划线来实现
- 单下划线开头:以单下划线开头的属性或方法表示
protected受保护的成员,这类成员被视为仅供内部使用,允许类本身和子类进行访问,但实际上它可以被外部代码访问。 - 双下划线开头:表示
private私有的成员,这类成员只允许定义该属性或方法的类本身进行访问 - 首尾双下划线:一般表示特殊的方法
class Student:
# 首尾双下划线
def __init__(self,name,age,gender):
self._name=name # 单下划线开头的,受保护的,只能本类和子类访问
self.__age=age # 双下划线开头的,表示私有的,只能类本身去访问
self.gender=gender # 普通的实例属性,类的内部、外部、子类都可以访问
def show(self):
print(f'我叫{self.name},我今年{self.age}岁了')
def _fun1(self): # 单下划线开头,受保护的
print('子类及本身可以访问')
def __fun2(self): # 双下划线开头,私有的
print('只有定义的类可以访问')
def fun(self): # 普通的实例方法
self._fun1() # 类本身访问受保护的方法
self.__fun2() # 类本身访问私有方法
print(self._name) # 受保护的实例属性
print(self.__age) # 私有的实例属性
# 创建一个Student类对象
stu=Student('iop',20,'女')
# 类的外部
print(stu._name)
print(stu.__age) # 报错,出了class的定义范围,__age就不能用了
# 调用受保护的实例方法
stu._fun1() # 子类及本身可以访问
# 私有方法
stu.__fun2() # 报错,无法访问
# 私有的实例属性和方法是真的无法访问吗?
print(stu._Student__age) # 可以访问了
print(stu._Student__fun2)
8.3.1.2 属性的设置
class Student:
# 首尾双下划线
def __init__(self,name,gender):
self.name=name
self.__gender=gender # 私有的实例属性
# 使用@property修改方法,将方法转成属性使用
@property
def gender(self):
return self.__gender
# 将我们的gender这个属性设置为可写属性
@gender.setter
def gender(self,value):
if value!='女' and value!='男':
print('性别有误,已将性别默认为女')
self.__gender='女'
else:
self.__gender=value
stu=Student('iop','女')
print(stu.name,'的性别是:',stu.gender) # stu.gender就会去执行stu.gender()这个方法;此时状态是只读,不能修改
# 尝试修改属性值
stu.gender='男' # 报错,此时需要在类中将该属性设置为可写属性,具体为第12-19行(该部分为报错后加上的)
print(stu.name,'的性别是:',stu.gender) # 增加第12-19行后,修改成功
stu.gender='其他'
print(stu.name,'的性别是:',stu.gender)
8.3.2 继承
8.3.2.1 继承概念
- 在Python中一个子类可以继承N多个父类
- 一个父类也可以拥有N多个子类
- 如果一个类没有继承任何类,那么这个类默认继承的是object类
继承的语法结构:
class 类名(父类1,父类2.....父类N):
pass
class Person: # 默认继承了object类
def __init__(self,name,age):
self.name=name
self.age=age
def show(self):
print(f'大家好,我叫{self.name},我今年{self.age}岁了')
# Student继承Person类
class Student(Person):
# 编写初始化的方法
def __init__(self,name,age,stuno): # stuno是该方法独有的属性,name和age是父类的
super().__init__(name,age) # super()就是调用父类的属性或方法;因为name和age是父类有的,所以只需要调用,不用再赋值
self.stuno=stuno
# Doctor继承Person类
class Doctor(Person):
# 编写初始化方法
def __init__(self,name,age,department):
super().__init__(name,age)
self.department = department
# 创建两个子类对象
stu=Student('iop',20,1001)
doctor=Doctor('iop',20,'脑科')
stu.show()
doctor.show()
8.3.2.2 多继承
class FatherA():
def __init__(self,name):
self.name=name
def showA(self):
print('父类A中的方法')
class FatherB():
def __init__(self,age):
self.age=age
def showB(self):
print('父类B中的方法')
# 多继承
class Son(FatherA,FatherB):
def __init__(self,name,age,gender):
# 需要调用两个父类的初始化方法
FatherA.__init__(self,name) # 多继承调用父类的参数不能再用super()
FatherB.__init__(self,age)
self.gender=gender
son=Son('iop',20,'女') # 调用Son类中的__init__执行
son.showA()
son.showB()
8.3.2.3 方法重写
- 子类继承了父类就拥有了父类中公有成员和受保护的成员
- 父类的方法法并不能完全适合子类的需要求这个时候子类就可以重写父类的方法
- 子类在重新父类的方法时,要求方法的名称必须与父类方法的名称相同,在子类重写后的方法中可以通过
super().xxx()调用父类中的方法
class Person: # 默认继承了object类
def __init__(self,name,age):
self.name=name
self.age=age
def show(self):
print(f'大家好,我叫{self.name},我今年{self.age}岁了')
# Student继承Person类
class Student(Person):
# 编写初始化的方法
def __init__(self,name,age,stuno):
super().__init__(name,age)
self.stuno=stuno
def show(self): # 需要与父类中的方法同名
# 调用父类中的方法
super().show()
print(f'我的学号是{self.stuno}') # 调用父类后再增加
# Doctor继承Person类
class Doctor(Person):
def __init__(self,name,age,department):
super().__init__(name,age)
self.department = department
def show(self): # 需要与父类中的方法同名
# 不调用父类中的方法,完全重写
print(f'大家好,我叫{self.name},今年{self.age}岁了,我所在的科室是{self.department}') # 调用父类后再增加
# 创建两个子类对象
stu=Student('iop',20,1001)
doctor=Doctor('qwe',18,'脑科')
stu.show() # 调用子类自己的show方法
doctor.show() # 调用子类自己的show方法
8.3.3 多态
- 指的就是“多种形态”,即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用对象的方法
- 在程序运行过程中根据变量所引用对象的数据类型,动态决定调用哪个对象中的方法
- Python语言中的多态,根本不关心对象的数据类型,也不关心类之间是否存在继承关系,只关心对象的行为(方法)。只要不同的类中有同名的方法,即可实现多态
class Person():
def eat(self):
print('人,吃五谷杂粮。')
class Cat():
def eat(self):
print('猫,吃鱼。')
class Dog():
def eat(self):
print('狗,吃骨头。')
# 这三个类都有一个同名的方法,eat
# 编写函数
def fun(obj): # obj形参,此时无法得知其数据类型
obj.eat() # 通过变量obj调用eat方法
# 创建三个类的对象
per=Person()
cat=Cat()
dog=Dog()
# 调用fun函数
fun(per) # Python中的多态,不关心对象的数据类型,只关心对象是否具有同名方法
fun(cat)
fun(dog)
8.4 object对象
- 所有类的直接或间接父类
- 所有类都拥有object类的属性和方法
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def show(self):
print(f'大家好,我叫{self.name},我今年{self.age}岁了')
# 创建Person类的对象
per=Person('iop',20) # 创建对象时会自动调用__init__()方法
print(dir(per))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'show']
# 除了后面三个:age、name、show是自定义外其余全是从父类object调用的
# 其中__init__相当于重写了
print(per) # <__main__.Person object at 0x00000210A7C7CD70> 直接输出对象名实际上是自动调用了__str__方法,为描述信息
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
# 重写__str__方法
def __str__(self):
return '这是一个Person,具有name和age两个实例属性'
# 创建Person类的对象
per=Person('iop',20)
print(per) # 此时输出的已经不是地址了,而是上面重写的__str__方法中的内容
print(per.__str__()) # 这是一个Person,具有name和age两个实例属性 与第12行结果一致
8.5 python中的特殊方法与特殊属性
8.5.1 特殊方法
a=10
b=20
print(a.__add__(b)) # 等价于a+b
8.5.2 特殊属性
class A:
pass
class B:
pass
class C(A,B):
def __init__(self,name,age):
self.name=name
self.age=age
# 创建类的对象
a=A()
b=B()
c=C('iop',20)
print('对象a的属性字典:',a.__dict__) # 对象a的属性字典: {}
print('对象b的属性字典:',b.__dict__) # 对象b的属性字典: {}
print('对象c的属性字典:',c.__dict__) # 对象c的属性字典: {'name': 'iop', 'age': 20}
print('对象a的所属的类:',a.__class__) # 对象a的所属的类: <class '__main__.A'>
print('对象b的所属的类:',b.__class__) # 对象b的所属的类: <class '__main__.B'>
print('对象c的所属的类:',c.__class__) # 对象c的所属的类: <class '__main__.C'>
print('A类的父类元组:',A.__bases__) # A类的父类元组: (<class 'object'>,)
print('B类的父类元组:',B.__bases__) # B类的父类元组: (<class 'object'>,)
print('C类的父类元组:',C.__bases__) # C类的父类元组: (<class '__main__.A'>, <class '__main__.B'>)
print('A类的父类:',A.__base__) # A类的父类: <class 'object'>
print('B类的父类:',B.__base__) # B类的父类: <class 'object'>
print('C类的父类:',C.__base__) # C类的父类: <class '__main__.A'> 如果继承了多个父类,那结果只显示第一个父类
print('A类的层次结构:',A.__mro__) # A类的层次结构: (<class '__main__.A'>, <class 'object'>)
print('B类的层次结构:',B.__mro__) # B类的层次结构: (<class '__main__.B'>, <class 'object'>)
print('C类的层次结构:',C.__mro__) # C类的层次结构: (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
# C类首先继承了A类和B类,又间接继承了object类
# 子类列表
print('A类的子类列表:',A.__subclasses__()) # A类的子类列表: [<class '__main__.C'>]
print('B类的子类列表:',B.__subclasses__()) # B类的子类列表: [<class '__main__.C'>]
print('C类的子类列表:',C.__subclasses__()) # C类的子类列表: []
8.6 类的深拷贝与浅拷贝
8.6.1 变量的赋值
变量的赋值:只是形成两个变量实际上还是指向同一个对象
class CPU:
pass
class Disk:
pass
class Computer:
# 计算机由CPU和硬盘组成
def __init__(self,cpu,disk):
self.cpu=cpu
self.disk=disk
# 创建类的对象
cpu=CPU()
disk=Disk()
com=Computer(cpu,disk)
# 变量/对象的赋值
com1=com
print(com) # <__main__.Computer object at 0x000001D87441CD70>
print(com1) # <__main__.Computer object at 0x000001D87441CD70> 内存地址相同
print('com子对象的内存地址',com.cpu,com.disk) # <__main__.CPU object at 0x000001CC1950CFB0> <__main__.Disk object at 0x000001CC1950CF20>
print('com1子对象的内存地址',com1.cpu,com1.disk) # 也相同
8.6.2 浅拷贝
浅拷贝:拷贝时,对象包含的子对象内容不拷贝,因此源对象与拷贝对象会引用同一个子对象
class CPU:
pass
class Disk:
pass
class Computer:
# 计算机由CPU和硬盘组成
def __init__(self,cpu,disk):
self.cpu=cpu
self.disk=disk
# 创建类的对象
cpu=CPU()
disk=Disk()
com=Computer(cpu,disk)
# 类对象的浅拷贝
import copy
com2=copy.copy(com) # com2是新产生的对象,com2的子对象cpu、disk不变
print(com) # <__main__.Computer object at 0x000001626D39D010>
print(com2) # <__main__.Computer object at 0x000001626D39D160> 不相同了
print('com子对象的内存地址:',com.cpu,com.disk) # <__main__.CPU object at 0x000001626D39D070> <__main__.Disk object at 0x000001626D39CFE0>
print('com2子对象的内存地址:',com2.cpu,com2.disk) # <__main__.CPU object at 0x000001626D39D070> <__main__.Disk object at 0x000001626D39CFE0> 子对象还是相同的
8.6.3 深拷贝
深拷贝:使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同
class CPU:
pass
class Disk:
pass
class Computer:
# 计算机由CPU和硬盘组成
def __init__(self,cpu,disk):
self.cpu=cpu
self.disk=disk
# 创建类的对象
cpu=CPU()
disk=Disk()
com=Computer(cpu,disk)
# 类对象的深拷贝
import copy
com3=copy.deepcopy(com) # com3是新产生的对象,com3的子对象cpu、disk也会重新产生
print(com) # <__main__.Computer object at 0x000002688373D010>
print(com3) # <__main__.Computer object at 0x000002688373D490> 不同了
print('com子对象的内存地址:',com.cpu,com.disk) # <__main__.CPU object at 0x000002688373D070> <__main__.Disk object at 0x000002688373CFE0>
print('com2子对象的内存地址:',com3.cpu,com3.disk) # <__main__.CPU object at 0x000002688373FB00> <__main__.Disk object at 0x000002688373FB60> 不同了