python学习笔记(老男孩s21)
——————————-by:发飙的登山包——————————————
gitee项目地址:https://gitee.com/python_road/old_boy_python_self_study 附带尹成python、老男孩s1 python学习笔记、学习期间写的各种py脚本
前言
0.1 学习方法
- 写笔记
- 每天看笔记
- 思维导图
- 每周最后一天,方便回顾复习
- 做作业
- 如果做题碰到笔记里没有的,就将该知识点添加到笔记里
0.2 编程核心思想
- 将客户要求进行拆分,将功能拆分成各个小块,然后再进行拼接
第一章 计算机基础
1.1 硬件
计算机基本的硬件由:CPU / 内存 / 主板 / 硬盘 / 网卡 / 显卡 等组成,只有硬件但硬件之间无法进行交流和通信。
1.2 操作系统
操作系统用于协同或控制硬件之间进行工作,常见的操作系统有那些:
- windows
- linux
- centos 【公司线上一般用】
- mac
1.3 解释器或编译器
编程语言的开发者写的一个工具,将用户写的代码转换成010101交给操作系统去执行。
1.3.1 解释和编译型语言
编译型:
- 含义:日本翻译,等客户全部说完了,然后统一翻译。在实际中就是,这种语言先将所有代码全部统一翻译,然后生成一个编译完成后的文件,需要用户再将改文件提交给计算机执行才行,必须要有执行这一步
- 代表语言:c++,c#,JAVA,c
解释型:
- 含义:日本翻译,实时翻译,客户说一句,它就翻译一句。我们将代码给解释器之后,解释器会一句一句的解释每一条语句,解释一条就自动提交给计算机一条,这里就省去了人工提交编译后的文件的步骤了,这里是实时的,解释一条,自动提交一条。
- 代表语言:python,ruby,php
1.3.2 在linux中指定解释器
在linux中执行脚本方法:解释器 文件路径
- 给文件赋予一个可执行的权限
- 终端输入:./a.py,程序会寻找解释器来执行(自动去找脚本文件的第一行),相当于 #! /usr/bin/env python a.py
1 | #! /usr/bin/env python #在linux中指定解释器的路径 |
1.4 软件(应用程序)
软件又称为应用程序,就是我们在电脑上使用的工具,类似于:记事本 / 图片查看 / 游戏
1.5 进制
进制的本质:
- 对于计算机而言无论是文件存储 / 网络传输输入本质上都是:二进制(010101010101),如:电脑上存储视频/图片/文件都是二进制; QQ/微信聊天发送的表情/文字/语言/视频 也全部都是二进制。
进制:
- 2进制,计算机内部。
- 8进制
- 10进制,人来进行使用,一般情况下计算机可以获取10进制,然后再内部会自动转换成二进制并操作。
- 16进制,一般用于表示二进制(用更短的内容表示更多的数据),一版是:\x 开头。
二进制 | 八进制 | 十进制 | 十六进制 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
10 | 2 | 2 | 2 |
11 | 3 | 3 | 3 |
第二章 Python入门
2.1 环境的安装
- 解释器:py2 / py3 (环境变量)
- 开发工具:pycharm(见附录)
2.2 编码
2.2.1 编码基础
- ascii:256位,由英文字母、标点符号注册,一个符号占8位,1个字节(一个字节占8位),总共有2**8=256个
- unicode(万国码):解决全球语言问题,ascii只支持英文,unicode支持全球语言,4个字节总共有2**32个
- utf-8:对万国码进行压缩,用尽量少的位数表示一个东西,最少用1字节=8位,最多用4字节=32位,中文:3字节=24位表示,以8位一组,从末尾开始,数8位,作为一组,如果第9位是非0,那么就再往前取一组
- GBK:专属于亚洲的编码,中文也包含在内,中文占2个字节
- gb2312:专属于亚洲的编码,中文也包含在内,中文占两个字节
2.2.2 python编码相关
对于Python默认解释器编码:
- py2: ascii
- py3: unicode
如果想要修改默认编码,则可以使用:
1 | # -*- coding:utf-8 -*- |
注意:
- 对于操作文件时,要按照:以什么编写写入,就要用什么编码去打开,否则会出现乱码。
- 不管使用的何种python版本进行写代码,都建议再开头加上文件头代码, 这样就避免了编码冲突
2.2.3 编码在数据存储中的应用
问题1:既然gbk和gb2312中文占用的字节小,消耗的资源小,那为什么还要用utf-8编码(3个字节)?
- gbk、gb2312支持的范围小
- 英文是主流语言,各种最新的、最成熟的框架、数据都是用utf-8编码储存或者编写的,如果使用gbk、gb2312将会导致编码冲突,如果将utf-8修改成gbk、gb2312花费的时间太多,并且过程十分繁琐
问题2:数据写入磁盘的节本流程(编码相关):写入数据到文本里
首先需要新建文本,选择文本的编码,例如utf8编码
接着执行写入命令 write(‘你好’) ,这里首先会将 ‘你好’(python3中,字符串是用unicode编码格式) 按照utf-8编码格式进行编码,编码成字节类型(bytes),然后将二进制写入磁盘
特殊情况:我们用wb模式打开文本(此时无encoding),此时如果需要写入文本,因为没有指定编码,所以只能write()接收的参数只能是二进制,所以我们需要提前将我们的数据转化为二进制
1
2
3a = '你好'
b = a.encode('utf-8') #以utf-8的方式进行逆转编码,转化为二进制
print(b) #结果为:b'\xe4\xbd\xa0\xe5\xa5\xbd'
问题3:如何将二进制解码成其他编码的数据?
1 | a = b'\xe4\xbd\xa0\xe5\xa5\xbd' #二进制 |
2.2.4 txt文档unicode编码差异
在windows的txt文档,另存为选择编码的时候,有一个unicode,这个并不是万国码,这里实际上是utf-16,这里可以说是微软搞错了。
2.2.5 编码转换
python2和python3中编码转换的区别:
python2:是基于ascii编码表进行转换的,因为其内置是ascii
缺点:支持转换的范围太小,只有256个,不能转汉字
python3:是基于unicode编码表进行转换的,因为其内置是unicode
优点:支持的范围广,可以将汉字转换为数字
int 转str
str转int
1
2
3
4
5
6
7#将数字转换为unicode编码
a = 666
print(chr(a)) #结果为:ʚ
#将str转换为数字:基于unicode编码
a = '我'
print(ord(a)) #结果为:25105
2.3 变量
2.3.1 变量存在的意义
为某个值创建一个“外号”,以后在使用时候通过此外号就可以直接调用。
2.3.2 变量名命名规范
要求:
- 只能包含:字母、数字、下划线
- 数字不能开头
- 不能是python关键字
建议:
- 建议使用英文来命名
- 建议全局变量全部用大写英文,局部变量用小写英文
- 见名知意
- 名字长用下划线链接
2.4 输入和输出
2.4.1 输入
1 | input('提示语:') |
2.4.2 输出
print(你想要输入的东西)
print(‘内容’,sep=’*’,end=’’)
sep指定打印内容之间的间隔,一行之间的,默认是空格
end指定每一行print语句的间隔,默认是换行符
技巧:将多行内容显示在一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14#通过end取消默认的每条print语句的换行符,将其替换为空
print('我',end='')
print('爱',end='')
print('你')
#结果为'我爱你'
#不修改seps
print('我','爱','你') #结果为 '我 爱 你' 中间默认用空格隔开
#修改seps
print('我','爱','你',sep='*') #结果为 我*爱*你 中间默认用*隔开
2.4.3 py2、py3输入和输出区别
输入
1 | raw_input() #py2 |
输出:
1 | print "你好" # py2不需要括号 |
2.5 条件语句
2.5.1 if…else
基本语法:
1 | if 条件 : |
2.5.2 if…elif…else
注意点:如果if..elif语句过多,建议使用字典,活着了列表来实现
1 | if 条件 : |
2.6 循环语句
2.6.1 while
1 | while 条件 : 执行语句 |
2.6.2 for
2.6.2.1 range()高级用法
range():
python2和python3中的区别:
python2:
- range():立刻执行。缺点是,如果范围较大,十分浪费资源
- xrange(): 需要配合for遍历才执行。优点:需要调用时使用for才执行,节约资源
python3:
- range():相当于python2中的xrange(),更高效,更节约资源
参数为单个数字range(5):该数字代表执行5次
参数为2个数字range(0,5):表示从数字0递增到数字5,不包括5
参数为3个数字range(0,6,2):第三个数字为步长
- 如果没有步长,则默认是正数1
- 如果步长为负数,则代表递减
1 | #参数为单个数字range(5):该数字代表执行5次for i in range(5) : print('hello') ''''结果为:hellohellohellohellohello'''#参数为2个数字range(0,5):表示从数字0递增到数字5,不包括5for i in range(0,5) : print(i) '''结果为:01234''' #参数为3个数字range(0,6,2):第三个数字为步长 for i in range(0,6,2) : print(i) #不包括6'''结果为:024'''#参数为3个数字range(6,0,-1):步长为负数,所以递减for i in range(6,0,-1) : print(i) #不包括0'''结果为:654321''' |
应用实例:
1
#打印出0-100之间的所有偶数:完全可以利用步长来实现for i in range(0,101,2) : print(i)
2.6.2.2 for与语句开关
for实现评论过滤系统:
原理:用户输入字符,存在关键字,就将关键字替换掉,然后给其显示过滤后的字符;如果不存在敏感字,则直接显示
技巧:for循环前设置一个变量,让其为正常状态,然后for循环开始,for循环里如果满足某个条件,就修改变量,以此来,接着再在for循环外(语句最后)打印该变量,在本例中,如果用户输入的语句里没有敏感词,就直接打印,如果有,就修改掉
1
'''用户输入字符,存在关键字,就将关键字替换掉,然后给其显示过滤后的字符;如果不存在敏感字,则直接显示'''message = input('请输入内容:') #初始状态text_check = ['加','微信','www.']for i in text_check : if i in message : message = message.replace(i,'**') #有敏感字就替换 else : passprint(message)
for实现是否存在敏感字符
1 | #if 实现a = 'oldboy'switch = '不存在敏感字符' #开关初始状态 if 'ld' in a : switch = '存在敏感字符' #变更开关 print(switch) #打印开关 |
2.6.3 break,continue
特性:
- continue:当碰到该函数,程序会不再往下执行,会不再执行本次循环,会回到条件处,直接执行下一次循环,直接回到当前while循环的条件处进行再次判别
- break:跳出当前循环,仅仅跳出当前的循环
continue在while和for中的区别:
whiile
在while中,如果碰到continue,后续的代码将不再执行,直接回到while的条件判定处,重新进行判定,因为while自身不携带递增属性,所以如果构造的递增语句在continue之前,那么循环会进入下一轮,否则就一直执行相同的循环
for
在for中,如果碰到contine,后续的代码将不再执行,直接回到for的开头,接着开始第二次循环(因为for自带递增属性,所以该循环会一直递增下去),第二轮碰到continue,会继续回到开头,开始第三轮循环
2.6.4 while…else
while … else中else的触发条件:
当while的条件不满足时,才执行else语句,需要注意的是,如果while语句并不是因为条件不满足,而是因为其他情况:例如强制性跳出循环(比如break)的情况下,else语句不会被执行
- 范例一(无break)
1 | count = 1while count <= 3 : print(count) count += 1else : print('跳出了循环,此时count为:'+str(count) ) |
- 结果为:
- 范例2:(while在被强制性跳出(并不是条件不满足的情况下),else不执行)
1 | count = 1while count <= 3 : print(count) if count == 2 : break count += 1else : print('跳出了循环,此时count为:'+str(count) ) |
结果:
2.6.5 while、for的区别
while:
适用于无穷的循环,需要提前定义一个变量,并且构造递增语句才能实现递增
for
- 含义:适用于有限的循环,最大的特性是遍历
- 特性:自带递增属性,而while没有,需要定义变量来递增
2.7 字符串格式化
2.7.1 用%来格式化
%d
%s
%%(主要是用来格式化%这个符号,有时候我们需要文本里出现%这个字符,那么就需要两个%)
注意点:接受参数的时候,最后一定要是逗号(,)结尾
1
2
3
4
5
6
7
8
9
10#用法1:
print('今天有50%的可能%s'%('下雨',)) #错误写法,程序报错,必须要使用两个%
print('今天有50%%的可能%s'%('下雨',)) #正确写法
#结果为:今天有50%的可能下雨
#用法2:结合变量使用
guess = '今天有50%%的可能%s'
print(guess%('下雨',)) #正确写法
#结果为:今天有50%的可能下雨
2.7.1.1 %格式化加逗号原理
小括号特性:整体类型取决于小括号里的 单一 数据的类型
- ```python
#整体类型取决于括号里的 单一 数据的类型
a = (1) #结果为int类型
b = (‘好’) #结果为str类型
print(type(a),type(b))1
2
3
4
5
6
7
8
9
10
11
%号格式化后面的小括号整体应该是元组类型
- 原因:因为该小括号里可能需要接收多个参数,所以它是元组类型
- 存在冲突:因为小括号特性,如果接收单一的数据,那么小括号这个整体的数据类型取决于括号里的数据的类型,那么这个整体的类型就不一定是元组类型了,这就不符合%格式化后面的小括号整体的类型是元组这一原则了。
- 解决办法:在数据后面多加一个逗号
```python
print('我的年龄是%d岁'%(26)) #错误写法,这时%后的括号整体类型是int型,和基本原则冲突#添加逗号来避免冲突,使%后的小括号整体为元组类型print('我的年龄是%d岁'%(26,))
2.7.2 用format来格式化
用format来格式化
1
2
3
4
5
6
7
8
9
10
11
12
13#用法1:和字符串搭配使用
print('我叫{},我的年龄是{}'.format('张三','24岁'))
#结果为:我叫张三,我的年龄是24岁
#用法2:和变量搭配使用,变量一定要记得留下预留位置,大括号
talk = '我叫{},我的年龄是{}'
print(talk.format('张三','24岁'))
#结果为:我叫张三,我的年龄是24岁
#用法3:字符串可以是空的,但是必须要预留大括号用来传递参数
talk = '{}{}'
print(talk.format('张三','24岁'))
#结果为:张三24岁在字符串中使用变量:用 f 搭配变量来格式化:
1
2
3
4#语法格式:f"XXXX{变量名}"
name = '张三'
age = '李四'
print(f'我叫{name},我的年龄是{age}')
2.8 type()和id()
type():查看数据的类型
id():查看内存地址
1 | a = [1,2] |
2.9 三元运算
基本格式: 结果(正确) if 条件 else 结果(错误)
搭配对象:基本和if搭配使用
意义:缩减语句,简化代码
1 | a = 1 if 10 > 3 else 0 #如果条件成立,a就是1,不成立就是0print(a) #结果为:1 |
2.10 转义字符
主要字符:
\t :相当于tab
\n :相当于换行
\r:执行完前面的代码,然后迅速将光标移动到开头
主要功能:起覆盖作用
1
#\r的作用:执行完回到语句的开始位置#情况一:不加\rprint('123',end='')print('你好') #结果为:123你好#情况二:\r实现对上一句内容的覆盖print('123\r',end='') #打印完光标轨道123的开始位置print('你好') #因为光标在开始位置,所以你好对之前的内容进行了覆盖#结果为:你好
应用场景:进度条的递增,比如20%到100%的递增过程
1
#进度条:不要在pycharm中运行,有bug,可以用命令行界面运行import time for i in range(1,101): #模拟1%到100% print('%s%%\r'%i,end='') #每一次打印完,都会迅速跑到开头去,接着开始第二次打印,间接的做到了进度的递增 time.sleep(0.5) #延时使我们观看进度条更加直观
练习:复制视频文件
要求:实时显示拷贝的完成度,百分比
1
#方法一(推荐):获取读取的内容的长度(字节),最后判断读取长度和文件大小是否一样import ossize_read = 0with open(r'E:\1.mp4',mode='rb') as f1,open(r'E:\oldboy_python\code\test.mp4',mode='wb') as f2: #必须要用二进制模式 size_total = os.stat(r'E:\1.mp4').st_size #获取文件大小 while True : message = f1.read(2024) #每一次读取的内容 size = f2.write(message) #写入内容 size_read += len(message) #获取总读取的内容的字节,也就是相当于总写入的字节 print('已经拷贝{}%\r'.format(round(size_read / size_total * 100 ,2)),end='') #实现百分比 if size_read == size_total: #如果写入的大小和原文件大小一样,则退出循环 break#方法二:推荐此方法,通过光标的位置判断写入了多少# import os# with open(r'E:\1.mp4',mode='rb') as f1,open(r'E:\oldboy_python\code\test.mp4',mode='wb') as f2: #必须要用二进制模式# size_total = os.stat(r'E:\1.mp4').st_size #获取文件大小# while True :# f2.write(f1.read(2024)) #每次读1024字节# size_write = f2.tell() #查看光标位置,光标在哪个位置,就是读取了多少# print('已经拷贝{}%\r'.format(round(size_write / size_total * 100 ,2)),end='') #实现百分比# if size_write == size_total: #如果生成的文件和原文件大小一样,则退出循环# break#方法三:不推荐:进度条无法到100%,拷贝体可能和原数据相差个几个字节# import os# with open(r'E:\1.mp4',mode='rb') as f1,open(r'E:\oldboy_python\code\test.mp4',mode='wb') as f2:# size_total = os.stat(r'E:\1.mp4').st_size #获取文件大小# count,remaining = divmod(size_total,1024)# if remaining != 0:# for i in range(count + 1 ): #如果每次写入1024字节,需要写多少次# f2.write(f1.read(2024)) #每次读1024字节# size_write = os.stat(r'E:\oldboy_python\code\test.mp4').st_size #判定一共写入了多大的内容# print('已经拷贝{}%\r'.format(round(size_write / size_total * 100 ,2)),end='') #实现百分比# if size_write == size_total: #如果生成的文件和原文件大小一样,则退出循环# break
对字符串进行转义:
单一转义:适合需要转义的符号比较少的情况
用法:用 \ 来进行单一的转义
批量转义:推荐用这种方法,对字符串里的所有特定字符进行转义
用法:对需要批量转义的字符串前加一个 r 即可
示例:
1 | #情况一:不转义a = 'E:\test'print(a) #没有转义,结果为:E: est#情况二:单一转义,用\a = 'E:\\test' #多加一个\print(a) #没有转义,结果为:E:\test#情况二:单一转义,用\a = r'E:\test\ncloop' #直接在字符串前面加rprint(a) #没有转义,结果为:E:\test\ncloop |
2.11 两个变量值互换
前提:a = 0 ,b=1 ,想让a = 1 ,b = 0
通用方法:
使用范围:所有的编程语言
特点:再创建一个变量,用来保留a指向的值0
1
#a,b变量互换的通用方法:a = 0b = 1c = a #设立中转变量,用来保留a所指向的0a = bb = c #此时指向了0print(a,b) #结果为:1 0
python独有方法:
适用范围:python
特点:采用对称赋值的方法
1
#a,b互换:对称互换:a = 0b = 1a,b = b,a #该语句等价于通用方法print(a,b) #结果为:1 0
2.12 异常处理
基本格式:
1 | #异常处理基本格式 |
异常处理与for循环结合:
异常处理在for循环内
1
2
3
4
5
6
7
8
9#异常处理在for循环内:
for i in [1,2,'你好',6,7]:
try :
print(i + 666 )
except Exception as e :
pass #轮到第三个参数‘你好’,报错,程序继续向下执行,这里因为在循环里面,所以接着继续开始下一轮循环
#结果为:667 668 672 673
#可以发现,只有‘你好’没有执行,因为异常处理在循环内异常处理在for循环外
1
2
3
4
5
6
7
8#异常处理在for循环外:
try :
for i in [1,2,'你好',6,7]:
print(i + 666 )
except Exception as e :
pass #轮到第三个参数‘你好’,报错,接着往下执行,后面没有代码了,所以程序结束
#结果为:667 668
#可以发现,执行到‘你好’,报错,
第三章 数据类型
3.1 整型(int)
3.1.1 整型的定义
含义:各种数值
3.1.2 整型、整除在py2、py3的区别
整型:
- py2中有:int/long
- py3中有:int (int/long),int就包含了long
整除:py2和py3中整除是不一样。
区别
py2:
5 / 2 = 2
py3:
5 / 2 = 2.5
在py2中使用py3的除法:
1
from _future_ import division
3.2 布尔(bool)
布尔值就是用于表示真假。True和False。
3.3 字符串(str)
3.3.1 字符串、字符、字节的关系
定义:字符串是写代码中最常见的,python内存中的字符串是按照:unicode 编码存储。对于字符串是不可变。
编码格式:unicode
表现形式:’’ “ “ ‘’’ ‘’’ “”” “””
关系:’吃饭’ —这是一个字符串—-相当于2个字符—-相当于6个字节(utf-8编码)—–相当于4个字节(GBK、gb2312编码)
字符串应用实际:
- 一般应用于内存中,是unicode编码
3.3.2 字符串的方法
字符串自己有很多方法,如:
upper()、lower()、casefold()
应用实例:登录网站的验证码不区分大小写
lower()和casefold()的区别:
- lower支持的语言更少,只支持ascii表里的
- casefold支持除中文外的所有语言,所以推荐使用casefold()
实现方法:将网站预留用来校验的验证码统一转换为大写或小写,将用户输入的验证码也统一转换为大写或者小写,然后让这两个进行比对即可
1
2
3
4
5
6
7
8v = 'ABC'
v2 = 'ß' #德语
v3 = v.casefold() # 将字符串变小写,除了中文,所有其他国家的都支持
v4 = v2.casefold() #将德语'ß'转化为小写
print('casefold将英文转化为小写:{}'.format(v3)) # ss
print('casefold将德语转化为小写:{}'.format(v4))
v5 = v.lower() #lower功能少,只支持ascii表的
print('lower将英文转化为小写:{}'.format(v5))
判断是否全部是大写、小写
语法:isupper()、islower()
1
2
3
4
5
6
7
8
9
10
11
12
13
14#判断是否全部是大写
v = 'ALEX'
v1 = v.upper()
print(v1)
v2 = v.isupper() # 判断是否全部是大写
print(v2)
#判断是否全部是小写
v = 'alex'
v1 = v.lower()
print(v1)
v2 = v.islower() # 判断是否全部是小写
print(v2)strip()、lstrip()、rstrip():去除空格、\t、\n,任意字符
应用实例:网站注册时,用户名、密码处防止一不小心输入空格,会将空格过滤
实现方法:
功能:可以去除空格、换行符、\t,任意字符,因为换行符和\t的本质都是空格,所以可以去除
strip():去除左右两边的空格,换行符,tab键
- 易错点:strip() 是左右空格,\t ,\n 全部都可以直接去掉,不需要再在括号里填 \n \t
rstrip():去除右边的空格
lstrip():去除左边的空格
strip(‘内容’):去除指定内容
- 易错点:去除的指定内容兵不仅限于左右两边,字符串里的内容也会去掉,是全部去掉
实例:
1
2
3
4
5
6
7
8
9
10
11v1 = "alex "
print(v1.strip())
v2 = "alex\t" #\t相当于tab键,实质也是空格
print(v2.strip())
v3 = "alex\n" #\n换行,实质也是空格,所以也可以去除
print(v3.strip())
v1 = "alexa"
print(v1.strip('al')) #去除'al'
isdigit():返回的是bool值
实现函数:isdigit、isdecimal、isnumeric
区分标准:
1
2
3
4
5Unicode数字:'1'
byte数字(单字节):b'1'
全角数字(双字节):'1' #是在全角状态下输入的
罗马数字:'Ⅳ'
汉字:'四'isdigit():如果参数是小数,则返回False
1
True: Unicode数字,byte数字(单字节),全角数字(双字节),罗马数字False: 汉字数字,小数Error: 无
isdecimal:————————>推荐,因为这个可以判断是否是10进制的数
- 如果参数时小数,返回False
1
True: Unicode数字,,全角数字(双字节)False: 罗马数字,汉字数字,小数Error: byte数字(单字节)
isnumeric():
1
True: Unicode数字,全角数字(双字节),罗马数字,汉字数字False: 无Error: byte数字(单字节)
应用实例:拨打10086、10010的时候,会让我们通过输入序号选择不同的服务,此时需要检测用户输入的是不是数字
实现方法:
如果是数字,则返回True,否则False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50v = '1'
# v = '二'
# v = '②'
v1 = v.isdigit() # '1'-> True; '二'-> False; '②' --> True
v2 = v.isdecimal() # '1'-> True; '二'-> False; '②' --> False
v3 = v.isnumeric() # '1'-> True; '二'-> True; '②' --> True
print(v1,v2,v3)
# 以后推荐用 isdecimal 判断是否是 10进制的数。
# ############## 应用 ##############
v = ['alex','eric','tony']
for i in v:
print(i)
num = input('请输入序号:')
if num.isdecimal():
num = int(num)
print(v[num])
else:
print('你输入的不是数字')
#不同类型的数字应用
num = "1" #unicode
num.isdigit() # True
num.isdecimal() # True
num.isnumeric() # True
num = "1" # 全角
num.isdigit() # True
num.isdecimal() # True
num.isnumeric() # True
num = b"1" # byte
num.isdigit() # True
num.isdecimal() # AttributeError 'bytes' object has no attribute 'isdecimal'
num.isnumeric() # AttributeError 'bytes' object has no attribute 'isnumeric'
num = "IV" # 罗马数字
num.isdigit() # True
num.isdecimal() # False
num.isnumeric() # True
num = "四" # 汉字
num.isdigit() # False
num.isdecimal() # False
num.isnumeric() # True
replace(‘需要替换的字符串’,’用什么字符串代替’)
语法:replace(‘需要替换的字符串’,’用什么字符串代替’) ,如果后面加数字,表示需要替换几处,不填默认全部替换
应用实例:玩游戏时,屏蔽骂人字眼
实现方法:
1
2
3
4
5
6
7
8
9
10
11
12#不加数字默认全部替换
talk = '我草你大爷草'
talk_new = talk.replace('草','**')
print(talk_new)
#结果为:我**你大爷**
#加数字,则代表替换若干处
talk = '我草你大爷草你小爷草你姑爷'
talk_new = talk.replace('草','**',2) #此处添加了数字,表示替换2处
print(talk_new)
#结果为:我**你大爷**你小爷草你姑爷
split(‘分割字符’)
语法:split(‘分割标记’,数字(根据标记数量来分割)) rsplit代表着从右到左分割
应用实例:当用户录入多个姓名时,我可以通过特定符号或者字眼来分割
易错点:字符串分割后的值是一个列表,这点需要注意
实现方法:
1
#不指定数量的标记来分割text = '张三,李四,王麻子'text_new = text.split(',')print(text_new)#根据指定的标记数量来分割text = '张三,李四,王麻子,小明'text_new = text.split(',',2) #从左往右寻找2个逗号来进行分割text_new2 = text.rsplit(',',2) #从右往左寻找2个逗号来进行分割print(text_new,text_new2)#结果为:['张三', '李四', '王麻子,小明'] ['张三,李四', '王麻子', '小明']
encode:将数据转换为二进制(以十六进制表示的二进制)
- 必备知识点:将括号里的参数转换为以十六进制表示的二进制
1
a = '人生苦短,我学python'b = a.encode('utf-8')print(b)
startswith()、endswith():返回的bool值
含义:看字符串是否以特定的字符开头或者结尾
用法:
- a.startswith(‘需要匹配的字’)
- a.endswith(‘需要匹配的字’)
实例:
1
talk = '人生苦短,我用python'a = talk.startswith('人生苦') #看是不是'人生苦'这三个字开头,是就返回Trueb = talk.endswith('PYTHON') #看是不是PYTHON结尾,不是就返回Falseprint(a,b) #结果:a是True;B是False
format()
- 用法:
- ‘xxx{}xxx{}’.format(‘测试’,’测试2’)
1
#用法一:结合变量使用a = '今天气温{}℃'print(a.format(30)) #结果为:今天气温30℃#用法二:结合变量使用2a = '今天气温{}℃'b = 30print(a.format(30)) #结果为:今天气温30℃#用法三:结合字符串使用print('今天气温{}℃'.format(30)) #结果为:今天气温30℃#用法四:不用字符串及嵌套使用a = '今天气温{}℃'.format(30)b = '卧槽!'print('{}{}'.format(b,a)) #结果为:卧槽!今天气温30℃
- 用法:
find()
语法:a.find(‘好’) #查看字符串里的字符’好’所在的索引位置
易错点:该方法是字符串的专属方法,列表和元组都无法使用
如果查找内容不存在,则默认返回 -1
1
#查找内容存在时:text = 'oldboy'index = text.find('d') #查看d在字符串中的索引位置print(index) #结果为2#如果查找内容不存在,默认返回 -1text = 'oldboy'index = text.find('a') #查看d在字符串中的索引位置,如果不存在,默认返回-1print(index) #结果为:-1
count()
- 语法:a.count(‘好’) #查看该字符在字符串里出现的次数
- 易错点:该方法不仅仅适用于字符串,也适用于列表、元组
1
text = '好,狗屎,好了,好'index = text.count('好') #查看好在列表中出现的次数print(index) #结果为3
join
- 语法: ‘-‘.join(字符串) #字符串里的每个字符用 - 连接起来
- 特性:接收参数必须为字符串
1
a='oldboy' print( '*'.join(a) ) #结果为:o*l*d*b*o*y #错误做法 b = [1,2,3] print(''.join(b)) #报错:TypeError: sequence item 0: expected str instance, int found #正确做法是先利用循环将列表全部转换为字符串 #方法一: b = [1,2,3] c = [] for i in b : c.append(str(i)) print(''.join(c)) #结果为 123 #方法二: a = [1,2,3] for index in range(0,len(a)) : a[index] = str(a[index]) a = '_'.join(a) print(a) #给[1,2,3,4]用下划线链接 a = [1, 2, 3, 4] b = [] for i in a: b.append(str(i)) b = '_'.join(b) print(b)
len()
- 注意点:是计算字符的长度,并不是计算字符的大小
1
a = '我love你'print(len(a)) #结果为6
索引、切片、步长
注意点:
a[0:4] 这里包含0,不包含4,所以这里切片的是索引为0,1,2,3这四个元素
索引可以是负数,a[-1]表示最后一个元素,a[-2]表示倒数第二个元素
步长如果不填,则默认为1,步长表示间隔,如果步长为正数,则从左到右取数据,如果步长为负数,则倒着取数据
字符串通过切片拼接成新的字符串:
利用字符串拼接符号 + 来实现
实例1:
1
#索引a = 'hello'b = a[1] print(b) #结果为e#切片(索引为正)a = 'hello'b = a[1:4] print(b) #结果为ell#切片(索引为负)a = 'hello'b = a[1:-2] print(b) #结果为el#切片(步长为正数)a = 'hello'b = a[::2] print(b) #结果为hlo #切片(步长为负数)a = 'hello'b = a[::-1] print(b) #结果为olleh#切片(步长为负数)a = 'hello'b = a[-1:1:-1]print(b) #结果为oll#切片(拼接成新的字符串)s = "123a4b5c"s6 = s[5] + s[3] + s[1]print(s6) #结果为ba2
实例2:利用步长实现倒着打印
如果步长为负数,则切片就是从最后一个元素往第一个元素切片,如果没写步长,则默认是1
易错点:
步长忘记写了,必须要写成负数
3.4 数据类型的转换
其他类型转布尔型:
- int里只有0是False,其余全部是True
- str里只有空’’是False,其余全部是True
- 元组里只有空元组()是False,其余全部是True
- 列表里只有空列表[]是False,其余全部是True
- 字典里只有空字典{}是False,其余全部是True
- 集合里只有空集合set()是False,其余全部是True
- None本身就是Flase
布尔类型转整型:
- True可以转换为1
- False可以转换为0
整型int和字符串str互转
- 整型转str:str(6)
- str转整型:int(‘10’) 此处需要注意,必须要类似于数字的字符才能转整型,否则容易报错
元组、列表、结合互转
本质:相当于for循环待转换序列的每一个元素,然后将其添加到需要转换的序列
元组转列表、集合
1
2
3
4a = (3,4,5)
print(list(a)) # 元组转列表,结果:[3, 4, 5]
print(set(a)) # 元组转集合,结果:{3, 4, 5}列表转元组、集合
1
2
3a = [3,4,5]
print(tuple(a)) # 列表转元组,结果:(3, 4, 5)
print(set(a)) # 列表转集合,结果:{3, 4, 5}集合转元组、列表
1
2
3a = {'张三',26,'男'}
print(tuple(a)) # 结合转元组,结果:(26, '男', '张三')
print(list(a)) # 集合转列表,结果:[26, '男', '张三']
特殊情况
- 因为字典的结构特殊,所以其他数据类型无法转换成字典
- 原因:因为序列转换的本质就是for循环,但是for循环字典得到的只是键,所以转换字典得到的只是键
- 因为字典的结构特殊,所以字典转化为其他类型,转换的只是键,而没有值
- 原因:因为序列转换的本质就是for循环,但是for循环字典得到的只是键,所以转换字典得到的只是键
- 序列转化为str,结果还是一样,转没转都是一样
3.5 运算符及优先级
3.5.1 运算符
3.5.1.1 算术运算符
运算符 | 含义 | 示例 |
---|---|---|
+ | 两个数相加 | 2+1=3 |
- | 两个数相减 | 3-1=2 |
* | 两个数相乘 | 2*3=6 |
/ | 两个数相除 | 4/2=2 |
% | 求余数 | 9%2=1 |
// | 整除 | 5//2=2 |
** | 幂运算 | 3**2=9 |
3.5.1.2 逻辑运算符
运算符 | 含义 | 示例 |
---|---|---|
> | 大于 | 3 > 2 |
< | 小于 | 4 < 5 |
>= | 大于等于 | 3 >= 1 |
<= | 小于等于 | 2 <= 5 |
!= | 不等于 | 2 != 3 |
== | 等于 | 3 == 3 |
3.5.1.3 赋值运算符
运算符 | 示例 |
---|---|
= | 赋值, |
+= | c += 1 等价于 c = c + 1 |
-= | c -= 1 等价于 c = c - 1 |
*= | c *= 1 等价于 c = c * 1 |
/= | c /= 1 等价于 c = c / 1 |
**= | c **= 2 等价于 c = c *c |
//= | c //= 2 等价于 c = c // 2 |
%= | c %= 2等价于 c = c % 2 |
3.5.1.3 逻辑运算符
运算符 | 描述 | 示例 |
---|---|---|
and | 如果a是真,则结果取决于b;如果a是假,则结果取决于a | 10 and 0 ,结果是0;0 and 10 ,结果是0 |
or | 如果a是真,则结果取决于a;如果a是假,则结果取决于b | 5 or 0,结果是10;0 or 5,结果是5 |
not | 取反运算 | not 10,结果是False;not False,结果是True |
3.5.1.4 位运算符
含义:判断一个东西是否是另一个东西的子序列(判断一个东西是否在另外一个东西里),返回结果为True或者False
运算符 | 描述 |
---|---|
in | a in b:如果a在b的序列里,则返回True;否则返回False |
not in | a not in b:如果a不在b的序列里,则返回True;否则返回False |
实例:
```python
text = ‘张三,李四,王麻子’
print(‘李四’ in text) #返回结果是True1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
##### 3.5.1.5 运算符优先级
从最高往下排:
| ** | 指数 (最高优先级) |
| ------------------------ | ------------------------------------------------------ |
| ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) |
| * / % // | 乘,除,取模和取整除 |
| + - | 加法减法 |
| >> << | 右移,左移运算符 |
| & | 位 'AND' |
| ^ \| | 位运算符 |
| <= < > >= | 比较运算符 |
| <> == != | 等于运算符 |
| = %= /= //= -= += *= **= | 赋值运算符 |
| is is not | 身份运算符 |
| in not in | 成员运算符 |
| not and or | 逻辑运算符 |
#### 3.5.2 判别优先级(结合实例)
##### 3.5.2.1 组合:数字、or、and
判别方法:
- A and B:如果A为真,则结果看B;如果A为假,则结果为A
- A or B :如果A为真,则结果看A;如果A为假,则结果看B
- 如果判别的表达式很多很复杂,都糅合在一起了,那么我们可以根据优先级来将该语句进行拆分,可借助括号括起来,更直观明了,接着再利用同级别从左到右的规则,进行判别
易错点:A和B可以是表达式:例如:3>2等
实例:
- and
- 如果第一个数为假,那么最终结果参考第一个数;
- 如果一个数为真,那么最终结果参考第二个数
```python
print( 0 and 5 ) #第一个数为假
#结果为:0
print( 5 and 0 ) #第一个数为真
#结果为:0
print( 5 and 10 ) #第一个数为真
#结果为:10or
如果第一个数为真,那么最终结果参考第一个数
如果第一个数为假,那么最终结果参考第二个数
1
2
3
4
5
6print( 1 and 10)
#结果为1
print( 0 and 10)
#结果为10
3.5.2.2 组合:not,or,and,()
优先级:
- () > not > and > or
判别方法:
类似于下面的例子,表达式很长、很复杂,那么就根据优先级来进行拆分,可以借助括号来进行括起来,然后根据同级从左到右的方法来进行判别
1 > 1 or 3 < 4 or 4 > 5 and 2 > 1 and 9 > 8 or 7 < 6 #结果为trueand的优先级高于or,所以先将and的左右两边元素挑出来,如果有多个同级的,例如这里多个and,那么根据从左到右的原则进行拆分,可以转换为:
1 > 1 or 3 < 4 or ( (4 > 5 and 2 > 1) and (9 > 8) ) or 7 < 6
接着提取or
(1 > 1) or (3 < 4) or ( (4 > 5 and 2 > 1) and (9 > 8) ) or (7 < 6)
接下来就可以比较了,结果为True
1 | not 2 > 1 and 3 < 4 or 4 > 5 and 2 > 1 and 9 > 8 or 7 < 6 #结果为false |
3.6 元组(tuple)、列表
3.6.1 元组
表现形式:数据用小括号括起来,中间用逗号隔开
特性:静态的,不可变类型
例如:
1 | a = ('张三','李四',66) |
方法:类似于字符串,方法少于列表,没有增加、修改、删除的方法,静态的,不可变,即元组和字符串通过函数操作,其本身的值(内部元素)并不会改变
- 索引
- 切片
- 步长
3.6.2 列表
表现形式:数据用中括号括起来,中间用逗号隔开
特性:可变类型,会对原列表数据进行修改
方法:
增
- append():在最后作为一个整体添加
1 | extend():相当于两个列表的合并 |
1 | ```python |
删
del和pop的区别
del 只是单纯的删除
pop 不仅删除,如果将还会提取出删除的内容,语句赋值给变量,那么该变量的内容就是删除的内容
1
2a = [1,2,3,4]
b = a.pop(1) #此时将删掉的索引1所代表的的值2赋值给了变量b
```python
del a[0]
remove():删除,以内容为依据
pop():删除,以索引为依据
clear():清空列表1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 改
- 索引提取然后重新赋值
- a[0] = '666'
- 查
- 索引、切片查看
- a[0]
- a[0:3]
- 列表反转:reverse
- 通过方法reverse来反转
```python
a = [1,2,3,4]
a.reverse() #反转方法
print(a)列表排序:sort()
从小到大排序(默认),默认时,reverse = False
从大到小排序,需指定参数reverse = True
1
2
3
4
5
6
7
8
9#不指定sort参数,就是默认排序,默认就相当于reverse = False即从小到大排序
a = [1,3,5,0]
a.sort()
print(a) #结果为:[0, 1, 3, 5]
#指定参数排序,reverse = True ,相当于开启反转,那么就是从大到小了
a = [1,3,5,0]
a.sort(reverse = True)
print(a) #结果为:[5, 3, 1, 0]
3.6.3 元组、列表、字符串区别
方法种类对比:
列表:
- 方法最多,有增加、修改、删除的方法
字符串和元组:
- 方法少于列表,没有增加、修改、删除的方法
本身特性对比:
列表:
- 动态的,可变的,即其内部各个元素可以改变,也就是可以修改,删除,增加
元组、字符串:
- 静态的,不可变,即元组和字符串通过函数操作,其本身的值(内部元素)并不会改变
静态的解决办法:
- 首先用方法进行操作,然后将操作后的结果赋值给新的变量即可
3.6.4 for与字符串、元组、列表的组合
与for组合的作用:
- 遍历每一个元素,如果是字符串的话,就相当于遍历该字符串的每一个字符
1 | #字符串搭配for |
3.6.5 列表、元组、字符串的互相嵌套
语句嵌套:
1 | #列表和字符串嵌套a = [66,'ljdflj','张三']#列表、字符串、元组混合嵌套a = [36,('老男孩',26,'ljdlf'),] |
读取、取值:嵌套取值
- 方法:一层一层的取,按照没嵌套的时候的索引取值的方法取值,然后再在后面加索引进行嵌套取值
1 | #例如:a[1][1][2] 有几重嵌套就加几个索引来取值#列表和字符串嵌套,2重嵌套a = [66,'ljdflj','张三']print(a[1][1]) #结果为j,取列表的第2个元素里面的第二个元素,这是2重嵌套,所以要2重取值#列表、字符串、元组混合嵌套,3重嵌套a = [36,('老男孩',26,'ljdlf'),'测试']print(a[1][0][1]) #取值结果为 男 这是3重嵌套,所以要3重取值 |
修改、增加:修改嵌套语句里的数据
- 易错点和注意点:
- 列表属于可变类型,所以他的直属元素是可以修改的(新增、删除、修改),但是仅限于该列表直属元素,深度只是为1,该规则不适用于更深层次的元素
- 字符串、元组属于静态类型,所以它的元素无法修改,但是这仅仅限于深度1,也就是该字符串、元组的直属元素无法修改,如果它有个元素是列表,那么该列表里面的元素是可以修改的
1 | #列表和字符串嵌套a = [66,'ljdflj','张三']a.pop(1) #可以对列表a的直属元素进行修改,此处删除了ljdfljprint(a)b = (36,['老男孩',26,'ljdlf'],'测试')#b.pop(1) 错误语句,因为元组和字符串的直属元素是无法修改的b[1].pop(0) '''此处删除了'老男孩',那么为什么这里可以删除呢?因为元组、字符串的特性:无法修改它的直属元素,这里的['老男孩',26,'ljdlf']作为一个整体,属于元组的直属元素,所以它这个整体无法改变但是这个元素又是一个列表,这个列表里的元素能不能修改取决于这个列表,很明显,列表的特性:它的直属元素可以修改,所以我们完全可以通过索引来定位到那个列表:这里就是b[1],b[1]是元组的一个元素,同时也是一个列表,那么像修改该列表里的元素,只需要:b[1].pop[0]即可'''print(b) |
1 | #示例2:a = [11,22,33,(26,35)]a[1][0] = 99 #错误,a[1]所对应的是一个元组,所以该元组的元素a[1][0]不可修改a[1] = 88 #结果为a = [11,22,33,88] ,此处修改的是一个整体,该整体属于该列表的直属元素,所以可以修改#示例3:a = (3,6,[9,20])a[-1] = 80 #错误,处处修改的是一个整体,该整体只属于元组的元素,所以无法修改a[-1][0] = 666 #正确,因为该整体虽然属于元组的直属元素,但是这里并没有修改该整体,同时该整体是一个列表,所以如果我们修改该整体的内部元素,取决于该列表,显而易见,列表的直属元素是可以修改的 |
3.6.6 enumerate构造索引序列
索引序列:
基本构成:(索引,值)
含义:该函数将列表、元组、字符串的下表(索引)和值提取出来,每一个下表和其对应的值放在一个小括号里
用途:更方便的提取列表、元组、字符串的索引和值,一般用在for循环中
示例:
1
googs = ['汽车','飞机','火箭']for index,value in enumerate(googs): #此处相当于将googs转化为了(0,'汽车') (1,'飞机') (2,'火箭') print(index,value)'''结果为:0 汽车1 飞机2 火箭'''
3.7 字典
3.7.1 基本构造
英文:dict
应用场景:使用于储存对象有多个属性,比如要录入一个人的姓名、性别、年龄等类似的有多个属性的
表现形式:用大括号{}括起来
基本结构:
每个元素由 键 + ‘ : ‘ + 值组成,键和值是一个整体
键就类似于字典和元组的索引,不过索引彼此有关联,键没有关联,只有不可变的数据类型才可以作为字典的键
```python
a = {键:值,键:值} #每一个元素由键和值组成,键和值是一个整体;多个元素用逗号分隔1
2
3
4
5
6
7
8
9
10
11
12
13
#### 3.7.2 字典的方法
取值一:索引取某个元素:a['键']
- 缺点:如果该键不存在,那么程序会报错
- 易错点:取值一定要用中括号[],千万别用大括号{}
- 区别:大括号里只能填 键
```python
#错误做法:取值用了大括号a = {'name':'张三','age':28,'性别':'男'}print(a{'name'}) #报错:SyntaxError: invalid syntax#正确做法:用中括号[]取值a = {'name':'张三','age':28,'性别':'男'}print(a['name']) #结果为 张三#取一个不存在的键,程序报错a = {'name':'张三','age':28,'性别':'男'}print(a['fun']) #报错:KeyError: 'fun'
取值二:get方法取值(推荐使用该方法取值)
- 优点:不管取得键存不存在,使用get,程序都不会报错
- 如果存在,则返回取出来的值,如果不存在,则默认返回None
- 可以指定返回参数,当键不存在,可以返回其他的我们自定义的字符(非None),只需要在括号了多添加一个参数即可
1 | #正常取值a = {'name':'张三','age':28,'性别':'男'}print(a.get('name')) #结果为:张三 #取一个不存在的键,程序不会报错,但是返回Nonea = {'name':'张三','age':28,'性别':'男'}print(a.get('fun')) #结果为:None#如果不存在键,可以返回自定义的字符(非None)a = {'name':'张三','age':28,'性别':'男'}print(a.get('fun','该键不存在')) #结果为:该键不存在 |
获取所有元素的键:a.keys()
1 | a = {'name':'张三','age':28,'性别':'男'}key_all = a.keys()print(key_all) #获取所有键值,结果为:dict_keys(['name', 'age', '性别']) |
获取所有的值:a.values()
1 | a = {'name':'张三','age':28,'性别':'男'}key_all = a.values()print(key_all) #获取所有的值,结果为:dict_values(['张三', 28, '男']) |
获取所有字典的元素:a.items()
1 | #获取字典的所有元素,键和值作为一个整体获取a = {'name':'张三','age':28,'性别':'男'}key_all = a.items()print(key_all)#结果为:dict_items([('name', '张三'), ('age', 28), ('性别', '男')])#分别取出字典所有元素的键和值a = {'name':'张三','age':28,'性别':'男'}for k,v in a.items() : print(k,v)'''结果为:name 张三age 28性别 男''' |
获取字典长度:len(a)
和列表、元组、字符串的区别:
因为字典里,键和值是一个整体,键+值这个整体才是该列表的一个元素,才算一个长度
1
2
3a = {'name':'张三','age':28,'性别':'男'}
print(len(a))
#结果为:3
切片(没有该功能)
- 原因:因为列表、元组、字符串是根据索引来切片,但是他们的索引是有关联的;但是字典是根据键来索引的,很明显,键之间没有关联,所以无法切片
删除一:
因为字典中键和值是一个整体,该整体是字典的一个元素,所以我们执行语句del a[‘name’]就相当于删掉了键name所在的这个元素,删除的是这个元素,即键和值都删除了
1
2
3
4a = {'name':'张三','age':28,'性别':'男'}
del a['name']
print(a)
#结果为:{'age': 28, '性别': '男'}
删除二:pop()
区别:pop删除返回的只是值,没有键
1
2
3
4
5
6
7
8a = {'name':'张三','age':28,'性别':'男'}
b = a.pop('name')
print(a,b,sep='\n') #用换行符来分割,方便观看
'''
结果:
{'age': 28, '性别': '男'}
张三
'''
修改值和增加元素一:
- 如果字典里有相同的键,那么执行下面语句就是修改值:a{‘键’} = ’数据‘
- 如果字典里没有相同的键,那么执行下列语句就是相当于直接添加一个新元素:a[‘键’] = ’数据‘
1 | #修改值:列表的有键:age,所以执行下列语句就可以修改a = {'name':'张三','age':28,'性别':'男'}a['age'] = 999print(a)#结果为:{'name': '张三', 'age': 999, '性别': '男'}#新增元素:列表的没有键:funa = {'name':'张三','age':28,'性别':'男'}a['fun'] = '篮球'print(a)#结果为:{'name': '张三', 'age': 28, '性别': '男', 'fun': '篮球'} |
修改值和增加元素一:update()
含义:添加和更新,如果字典里没有该键值对,则添加,有则更新(相当于修改),将参数的每一个元素添加到字典里
接收参数:序列字典本身,变量(字典)
1
2
3
4
5
6
7
8
9
10
11
12#a和b里没有相同的键,则新增
a = {'name':'张三','age':28,'性别':'男'}
b ={'num1':1,'num2':2}
a.update(b)
print(a) #结果为:{'name': '张三', 'age': 28, '性别': '男', 'num1': 1, 'num2': 2}
#a和b里有相同的键,则修改(更新)
a = {'name':'张三','age':28,'性别':'男'}
b ={'name':'李四'}
a.update(b)
print(a) #结果为:{'name': '李四', 'age': 28, '性别': '男'}
修改键:
键无法直接修改,只能通过先删除后新增的方法来实现另一种形式上的修改
1
2
3
4
5
6
7#通过先删除后新增的方法实现另一种意义上的修改键
#这里假设将'name'修改为'姓名',因为字典是无序的,所以无论新增加的元素在哪里都可以
a = {'name':'张三','age':28,'性别':'男'}
del a['name']
a['姓名'] ='张三'
print(a)
#结果为:{'age': 28, '性别': '男', '姓名': '张三'}
字典和变量的结合使用:
1 | #实例:张三,30 |
for循环
注意点:for + 字典,取出来的只是键而已
1
2
3
4
5
6
7
8
9#此处只是单纯的for循环字典
a = {'name':'张三','age':36}
for i in a :
print(i) #这里打印出来的只是键
'''
结果为:
name
age
'''
3.7.3 字典的嵌套
分类:
1、列表、元组里嵌套字典(简单)
2、字典里嵌套列表、元组
可嵌套的位置:
- 值的位置,每个值所处的位置都是可以嵌套的
为什么字典的键的位置只能修改成数值、字符串、元组,而不能修改成列表、字典?
- 字典的键的目的一般是直观明了,平常修改只需要修改值的位置的数据即可,一般固定不动,所以需要静态不变的数据类型填充在该位置,那么又因为数值、字符串、元组是静态的,不可变的,所以他们可以填充在该位置用来修改键,但是列表、字典是动态的,所以不满足
- 字典储存在内存中,需要通过一个哈希函数将其转化为数字才能储存在内存中,而哈希函数的一个特点是只可以哈希静态的数据类型,所以列表、字典是动态的导致不满足。
特殊情况:
原因:字典的键不能重复,但是True相当于1
1
2
3#字典的键既有True又有1,只打印一种
a = {True:'hello',1:'狗屎'} #因为True就相当于1,这里就等价于键重复了
print(a) #所以只会打印一种
3.7.4 列表,元组,字典的拆分
特性:假设列表里有n个数据,我们完全可以通过n个变量来对其进行对称赋值来实现取出列表里的数据
适用范围:列表、元组、字典里的数据比较少时可以这样
例如:[‘你’,’好’]
1
2
3
4lis = ['你','好']
a,b = lis
print(a,b) #结果为 你 好
列表、元组、字典拆分的区别:
列表、元组拆分方法一直,因为他们的结构类似,正常设置变量然后对称赋值即可去除
字典:字典特殊,因为他的每一个元素都是由键和值组成的,所以我们如果用对称赋值取出的只是键和值的整体,该整体由键和值组成
怎么去除字典的键和值?
利用for循环即可
1
a = {'name':'张三','age':28,'性别':'男'}for k,v in a.items() : #获取每一个元素里的键和值,此处利用了对称赋值 print(k,v)'''结果为:name 张三age 28性别 男'''
3.8 集合
3.8.1 集合基础
英文:set
含义:是一个存放数据的容器,只有不可变的数据类型才可以作为集合的元素
特点:无序、无重复数据
应用场景:爬虫等需要数据不重复的场景
空集合:set() ,这里只有一种设置方法,其他数据类型都是两种,比如列表就是list()和[]
表现形式:
和字典类似都是大括号括起来
大括号里只有数据,没有类似于字典的键
1
a = {1,2,3} #这就是集合
3.8.2 集合的方法
添加数据
- 缺点:接收的参数只支持单个数据,不支持序列
- 易错点:如果待添加的数据集合里有,那么因为集合的特性:不重复,所以该数据不会被添加
1 | a = {1,2,3} #集合a.add(50)print(a) #结果为:{1, 2, 3, 50} |
批量添加
特性:将接收的序列里的每个元素添加到集合中,因为集合是无序的,所以添加后数据所在位置也是无序的
缺点:接收的参数不支持单个数据,只支持序列,如果待添加的数据集合里有,那么因为集合的特性:不重复,所以该数据不会被添加
1
#示例1:待添加数据在集合里存在,那么该数据就不会被添加a = {1,2,3} #集合a.update((1,2)) #待添加的数据集合里有,所以该数据不会被添加print(a)#示例2:将接收的序列里的每一个元素添加到集合里a = {1,2,3} #集合a.update([66,67]) #因为集合是无序的,所以添加后数据所在位置也是无序的print(a)
删除
remove():删除一个不存在的值会报错,可以删除指定内容
discard():删除一个不存在的值不会报错,可以删除指定内容 (推荐)
pop():随机删除,因为集合是无序的
clear():清除集合
1
#remove删除存在的内容a = {66,32,31,75,96,52} #集合a.remove(32)print(a) #结果为:{96, 66, 75, 52, 31}#remove删除不存在的内容,报错a = {66,32,31,75,96,52} #集合a.remove(100)print(a) #错误:KeyError: 100#discard删除存在内容a = {66,32,31,75,96,52} #集合a.discard(52)print(a) #结果为:{32, 96, 66, 75, 31}#discard删除不存在内容,不报错a = {66,32,31,75,96,52} #集合a.discard(100)print(a) #结果为:{32, 96, 66, 75, 52, 31}
修改
- 集合不存在该功能,因为集合是无序的,无法定位到某个元素所以无法修改
求两个集合的交集:intersection
- 定义:求两个集合的共同部分,即共有部分,然后赋值给一个新变量
1 | a = {66,32,31,75,96,52} #集合b = {66,32,101,102} c = a.intersection(b) #将获取到的2个集合的共同部分赋值给变量cprint(c) #结果:{32, 66} |
求两个集合的并集
定义:将两个集合合并,如果有相同的元素,则只取一个
1
a = {66,32,31,75,96,52} #集合b = {66,32,101,102}c = a.union(b) #将获取到的2个集合的共同部分赋值给变量cprint(c) #结果为:{32, 96, 66, 101, 102, 75, 52, 31}
求两个集合的差集
定义:集合A,集合B,集合A里有集合B里没有的元素,那么这就是求集合A相对于集合B的差集
1
#求集合A相对于集合B的差集a = {66,32,31,75,96,52} #集合b = {66,32,101,102}c = a.difference(b) #集合a相对于集合b,将集合b里没有的元素提取出来print(c) #结果为:{96, 75, 52, 31}#求集合B相对于集合A的差集a = {66,32,31,75,96,52} #集合b = {66,32,101,102}c = b.difference(a) #集合b相对于集合a,将集合a里没有的元素提取出来print(c) #结果为:{101, 102}
3.8.3 集合的嵌套
原则:集合里只能嵌套不可变的类型,所以列表、字典、集合就无法嵌套在集合里了
特殊情况:
导致原因:集合的数据不重复
1
#集合中True、False和0,1都存在,只会打印其中一个a = {True,0,1,False} #因为True就相当于1,False相当于0,这里就等价于重复了print(a) #结果为:{0, True}
3.9 None
意义:占坑
优点:本身不带任何属性
特性:返回的永远是false
3.10 判断数据是否在序列
易错点:
字典:获取数据必须要有键,如果我们使用循环for in 字典,此时获取的只是键,而不是值,所以如果判断一个数据是否在字典里,如果直接加字典,此时判断的只是键而已
1
2
3e = {1:'测试',2:'吃饭'}
for i in e: #获取的只是键
print(e) #结果为 1 2
- ```python
#1、判断数据是否在字符串里
a = ‘测试oldboy’
print(‘测试’ in a) #True#2、判断数据是否在元组里
b = (‘测试’,’狗屎’)
print(‘测试’ in b) #True#3、判断数据是否在列表里
c = [‘测试’,’无语’]
print(‘测试’ in c) #True#4、判断数据是否在字典的键里
d = {‘测试’:96,’age’:36}
print(‘测试’ in d) #True #只是和键作比对#5、判断数据是否在字典的值里
#方法一:
e = {1:’测试’,2:’吃饭’}
print(‘测试’ in e.values()) #True#方法二:
f = {1:’测试’,2:’吃饭’}
switch = ‘不在里面’ #开关初始化
for k in f.keys():
print(switch) #打印开关#6、判断数据是否在字典的元素里if f[k] == '测试' : switch = '在里面' #更改开关 else : pass
g = {1:’1’,’2’:’吃饭’}
switch = ‘不存在’
for item in g.items():
print(switch)item = list(item) if '测试' in item : switch = '存在' else : pass
1
2
3
4
5
6
7
8
### 3.11 设置空数据类型
```python
#有两种设置方法的数据类型如下:m = 123m = int() #空整型,相当于0n = bool() #空布尔型,相当于Falsen = True/Falsea = '' #方法一:空字符串a = str() #方法二:空字符串b = ()b = tuple()c = []c = list()d = {}d = dict()#只有1种设置方法的如下:e = set() #设置空集合,只有这一种设置方式
3.12 序列的查找效率问题
字典 > 列表、元组
- 原因:使用for循环,查找列表、元组里的元组是从第一位一直一直往后匹配;而字典不一样,因为字典的key是用哈希直接转换为数字(可以理解成地址),可以直接根据哈希值来查找,所以速度很快
- 推荐:字典
3.13 序列与内存
方法:id() 查看内存地址
意义:深入了解序列的修改、赋值在内存中的原理,假设[3,2,5]对应的内存地址是01011010,a指向它,b也指向改地址,那么一旦该地址的内容发生了更改,a和b的值也会更改
把握原则:如果一个可变序列的值修改了,那么所有指向它的变量的值都会改变;前提是可变序列
赋值:
1
a = 3 #实际上是将3在内存中的地址放到了a中
修改
1
#示例1:a = [1,2,3] #将该列表的内存地址给了ab = [1,2,3] #新开辟一块内存,存放列表[1,2,3]的地址print(id(a),id(b))#结果为:194402240 194171648#示例2:a = [1,2,3] #将该列表的内存地址给了ab = a #a中存储的列表的地址又给了b,所以b指向的也是列表print(id(a),id(b)) #a,b的地址是一样的,都是指向同一个列表#示例3:a = [1,2,3] #将该列表的内存地址给了ab = a #a中存储的列表的地址又给了b,所以b指向的也是列表a = [4,5]print(id(a),id(b)) #b指向的还是列表[1,2,3],但是a因为重新赋值,所以它指向了一个新的列表[4,5]print(a,b) #a变成了[4,5],但是b不变,因为b指向的始终是列表[1,2,3]'''结果为:194171776 194402240[4, 5] [1, 2, 3]'''#示例4:可变序列修改,所有指向它的变量的值都会改变(一) a = [1,2,3] #将该列表的内存地址给了a b = a #将b指向序列[1,2,3] a.append(4) #此处修改的是a指向的序列[1,2,3],所以所有指向该序列的变量都会改变 print(id(a),id(b),a,b) #结果为:194402368 194402368 [1, 2, 3, 4] [1, 2, 3, 4] #示例5:列表嵌套:可变序列修改,所有指向它的变量的值都会改变(二)a = [1,2,3] #将该列表的内存地址给了ab = [44,55,a] #此处b的第三个元素指向的并不是变量a,而是序列[1,2,3],所以[1,2,3]改变,b也会改变a.append(4) #此处修改的是a指向的序列[1,2,3],所以所有指向该序列的变量都会改变print(a,b) #结果为:[1, 2, 3, 4] [44, 55, [1, 2, 3, 4]]#示例6:列表嵌套:可变序列修改,所有指向它的变量的值都会改变(三)a = [1,2,3] #将该列表的内存地址给了ab = [44,55,a] #此处b的第三个元素指向的并不是变量a,而是序列[1,2,3],所以[1,2,3]改变,b也会改变b[2][0] = 100 #此处修改的是序列[1,2,3],所以所有指向它的变量都会被修改,这里包括a和bprint(a,b) #结果为:[100, 2, 3] [44, 55, [100, 2, 3]]#示例6:列表嵌套:可变序列修改,所有指向它的变量的值都会改变(四)a = [1,2,3] #将该列表的内存地址给了ab = [44,55]c = [5,6,a,b,a] #此处的a指向的是[1,2,3],b指向的是[4,5]c[2].append(666) #此处对序列[1,2,3]进行了修改,所以所有指向该序列的变量的值都会被修改print(a,b,c)#结果为:[1, 2, 3, 666] [44, 55] [5, 6, [1, 2, 3, 666], [44, 55], [1, 2, 3, 666]]#示例8:列表嵌套:变量被重新赋值(一)a = [1,2,3] #将该列表的内存地址给了ab = [44,55,a] #此处b的第三个元素指向的并不是变量a,而是序列[1,2,3],所以[1,2,3]改变,b也会改变a = 10 #此处并不是修改,而是将a进行重新赋值,此时a指向了10,但是b并没有影响print(a,b) #结果为:10 [44, 55, [1, 2, 3]]#示例9:列表嵌套:变量被重新赋值(二)a = [1,2,3] #将该列表的内存地址给了ab = [44,55,a] #此处b的第三个元素指向的并不是变量a,而是序列[1,2,3],所以[1,2,3]改变,b也会改变b[2] = 50 #相当于将b[2]重新赋值,将b[2原先指向的[1,2,3]改为了10,对所有指向序列[1,2,3]的变量没影响print(a,b)```
练习题:
1 | #示例1:v1 = {'k1':'v1','k2':[1,2,3]}v2 = v1v1['k1'] = 'wupeiqi'print(v2) #结果为:{'k1':'wupeiqi','k2':[1,2,3]}#示例2:v1 = '人生苦短,我用Python'v2 = [1,2,3,4,v1]v1 = "人生苦短,用毛线Python"print(v2) #结果为:[1, 2, 3, 4, '人生苦短,我用Python']#示例3:info = [1,2,3]userinfo = {'account':info, 'num':info, 'money':info}info.append(9)print(userinfo) #结果为:{'account': [1, 2, 3, 9], 'num': [1, 2, 3, 9], 'money': [1, 2, 3, 9]}info = "题怎么这么多"print(info,userinfo) #结果为:题怎么这么多 {'account': [1, 2, 3, 9], 'num': [1, 2, 3, 9], 'money': [1, 2, 3, 9]}#示例4:info = [1,2,3]userinfo = [info,info]info[0] = '不仅多,还特么难呢'print(info) #结果为:['不仅多,还特么难呢', 2, 3]print(userinfo) #结果为:[['不仅多,还特么难呢', 2, 3], ['不仅多,还特么难呢', 2, 3]]#示例5:info = [1,2,3]userinfo = [info,info]userinfo[1][0] = '闭嘴'print(info) #结果为:['闭嘴', 2, 3]print(userinfo) #结果为:[['闭嘴', 2, 3], ['闭嘴', 2, 3]]#示例6:info = [1, 2, 3]user_list = []for item in range(2): user_list.append(info)info[1] = "是谁说Python好学的?"print(user_list) #[[1, '是谁说Python好学的?', 3], [1, '是谁说Python好学的?', 3]]#示例6:data = {}for i in range(2): data['user'] = iprint(data) #结果为:{'user': 1}#示例7:data_list = []data = {}for i in range(2): data['user'] = i data_list.append(data)print(data) #结果为:{'user': 1}print(data_list) #结果为:[{'user': 1}, {'user': 1}]#示例8:data_list = []for i in range(2): data = {} data['user'] = i data_list.append(data)print(data_list) #结果为:[{'user': 0}, {'user': 1}] |
疑难点1:列表中嵌套的变量指向的到底是什么?
- ```python
a = [1,2]b = [3,4,a] #此处的b[2]元素指向的并不是变量a,指向的是列表[1,2],所以如果a重新赋值,b不会改变;只有序列[1,2]被修改,b才会改变1
2
3
4
5
6
7
8
9
10
11
疑难点2:只要是给变量赋值,就是开辟了一个内存用来储存数据,那么如果两个变量的赋值都是10,为啥它们的内存地址一样?
- 因为python有缓存机制,都有个缓存池,目的是节省空间,如果在某个范围内,那么久不再新开辟内存,而是采用共用的方法,使用同一个内存
- int缓存范围:-5 ~ 256
示例:
```python
a = 3b = 3print(id(a),id(b))#结果为:8791065478880 8791065478880
3.14 深浅拷贝
含义:
浅拷贝:a = [11,22],b = [1,2,a],c是通过拷贝b得来的,所以c也是 [1,2,a]
特点:只拷贝第一层,也就是只拷贝1,2和a所指向的序列的地址,注意这里是地址,所以b里的a和c里的a所指向的都是同一个地址,所以如果a进行了修改,那么b的值也会同步修改,c也会同步修改,因为是浅拷贝,将嵌套的序列的地址拷贝过来了
深拷贝:完全复制,a发生变化,对c没影响,在此例子中,会新建2个内存,第一个内存用来存放第一层数据,这里的b的第一层数据的a所指向的地址不再是[11,22],而是指向了此次新建的第二个内存,而该第二个内存里储存的数据就是[11,22],所以深拷贝就是完全和原数据b独立,互不影响;如果在发现不可变类型数据(元组除外),那么就不会新建内存,而是直接引用该不可变类型数据的地址,相当于直接调用
总结:
- 浅拷贝:拷贝体和源数据可以互相影响(在修改可变类型的数据时)
- 深拷贝:拷贝体和源数据完全独立,互不影响
适合的数据类型:
- 浅拷贝:不可变数据类型:bool,int,字符串、元组
- 深拷贝:字典、列表、集合
易错点:
深浅拷贝出来的结果是完全一样的,比如打印出来的结果都是一样
区别就是浅拷贝可以影响源数据,源数据也可以影响浅拷贝
- 前提就是修改他们的可变类型,修改源数据和拷贝数据都会影响另一方
深拷贝则是完全拷贝,不管源数据或者拷贝体的任何类型进行了修改,都不会影响到对方
疑难点:
既然无论是浅拷贝还是深拷贝,都会新建一块内存,为啥我拷贝字符串或者数值时,发现源数据和拷贝体的内存地址一样?
因为python的小数据池,缓存机制,如果换成列表、字典等就不会有问题了
语法:
- 导入模块:import copy
- 浅拷贝:b = copy.copy(a) #复制a,将其赋值给变量b
- 深拷贝:b = copy.deepcopy(a) #复制a,将其赋值给变量b
1 | #小数据池:没有新建新的内存#示例一:拷贝元组:import copya = (1,2)b = copy.copy(a) #浅拷贝c = copy.deepcopy(a) print(id(a),id(b),id(c)) #3个地址一样,都是指向的元组(1,2)#示例二:拷贝数值import copya = 1000b = copy.copy(a) #浅拷贝c = copy.deepcopy(a) print(id(a),id(b),id(c)) #3个地址一样,都是指向的元组(1,2)#无嵌套:深浅拷贝结果一样import copya = [1,2]b = copy.copy(a) #浅拷贝c = copy.deepcopy(a) #深拷贝print(id(a),id(b),id(c)) #3个地址都不同print(a,c,c) #结果为:[1, 2] [1, 2] [1, 2]#有嵌套浅拷贝:源数据和拷贝体互相影响:import copya = [1,2,( 5,6,{'user':[3,2] } ) ]b = copy.copy(a) #浅拷贝#浅拷贝每个元素指向的地址都一样,所以拷贝体、源数据都可以影响对方print(id(a[0]),id(b[0])) #内存地址:8791025370784 8791025370784 ,相同print(id(a[2]),id(b[2])) #内存地址:40931200 40931200 ,相同print(id(a[2][2]),id(b[2][2])) #内存地址:35280192 35280192 ,相同a.append(66)print(a,b) #结果为:[1, 2, (5, 6, {'user': [3, 2]}), 66] [1, 2, (5, 6, {'user': [3, 2]})]b[2][2]['user'] = '张三'print(a,b) #结果为:[1, 2, (5, 6, {'user': '张三'}), 66] [1, 2, (5, 6, {'user': '张三'})]#有嵌套浅拷贝:源数据和拷贝体两者独立:import copya = [1,2,(5,6,{'user':[3,2] },['dasfadsf',4],3) ]b = copy.deepcopy(a) #浅拷贝#浅拷贝每个元素指向的地址都一样,所以拷贝体、源数据都可以影响对方print(id(a[0]),id(b[0])) #内存地址:8791020979872 8791020979872 相同,因为这是不可变类型print(id(a[2]),id(b[2])) #内存地址:38180400 45431040 不相同print(id(a[2][2]),id(b[2][2])) #内存地址:38819136 41179008 不相同print(id(a[2][4]),id(b[2][4])) #内存地址:8791020979936 8791020979936 3为不可变类型,所以直接此处直接引用a.append(66)print(a,b) #结果为:[1, 2, (5, 6, {'user': [3, 2]}), 66] [1, 2, (5, 6, {'user': [3, 2]})]b[2][3].append('张三')print(a,b) #结果为:[1, 2, (5, 6, {'user': '张三'}), 66] [1, 2, (5, 6, {'user': '张三'})] |
3.15 bytes类型
别名:字节类型
标志: b
本质:就是被压缩后的二进制
字符串类型和字节类型的关系:
- 字符串类型默认采用unicode编码,一般在内存中使用
- 字节类型就是将字符串类型的数据进行编码,采用其他方式编码,比如utf-8、gbk等编码,编码后的结果就是字节型
字符串类型和字节类型的总结:
- 以后提及字符串就是代表unicode编码
- 以后提及字节类型,就是代表是utf-8、gbk等其他编码
3.16 datetime类型
第四章 文件操作
文件操作的本质:
打开文件
计算机根据文件的编码进行逆转,换算成unicode编码
执行操作
- 读取:从计算机内存中读取文件的内容(读取的时候会自动将unicode转换成文件原先的编码)
- 写入
保存文件:就是关闭文件
4.1 文件基本操作
1 | obj = open('路径',mode='模式',encoding='编码') #将文件读取到内存,根据当前文件编码(utf-8)进行逆转,将其转换为unicodeobj.write() #写入:适用于w和a模式obj.read() #读取文件,可以赋值给一个变量直接打印,直接从光标开始往后读取obj.close() #关闭文件,如果不关闭,就相当于文件没有保存,数据都在内存,所以写入会无效 |
4.2 打开模式
r / w / a:
r:只读模式:如果当前文件不存在,则会报错
特点:只能读取
1
message = open('测试.txt',mode='r',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message_read = message.read() #读取内存中的数据,以文件的原始编码utf-8读取message.close()print(message_read) #打印读取的数据
w:写入模式:一旦填写了该语句,会立刻清空当前文件(不论下面执不执行写入语句write);如果当前文件不存在,则会在该脚本所在的目录自动创建一个
1
message = open('测试.txt',mode='r',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量print(message.read()) #打印读取的数据:das;fljdaslfkmessage.close()message = open('测试.txt',mode='w',encoding='utf-8') #该语句一执行,就清空了当前文本message.write('狗屎!') #此时文件的内容变味了:狗屎!message.close
a:追加模式:不清空数据,直接在文本最后面添加;写入语句也是用write();如果当前文件不存在,则会在该脚本所在的目录自动创建一个
1
#提前将测试.txt的内容设置为:das;fljdaslfkmessage = open('测试.txt',mode='r',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量print(message.read()) #打印读取的数据:das;fljdaslfkmessage.close()message = open('测试.txt',mode='a',encoding='utf-8') #该语句一执行,就清空了当前文本message.write('狗屎!') #写入语句也是write,此时结果为:das;fljdaslfk狗屎!message.close
r+ / w+ / a+
r+:读写模式
- 先读后写再读:这里的写就类似于追加,直接在文本最后写入;因为读取会将光标调到最后,所以接下来的写入就相当于追加,因为写入,所以光标始终在最后,所以读取到的就是空白
1
#提前将测试.txt的内容设置为:das;fljdaslfkmessage = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量print(message.read()) #打印读取的数据:das;fljdaslfkmessage.write('狗屎!') #写入语句也是write,此时结果为:das;fljdaslfk狗屎!print(message.read()) #空白message.close()
w+:写读模式(清空):先写后读取,结果为空,因为光标跑到最后去了
- 因为光标默认是从0开始,所以一旦开始写,如果文本里有数据,那么数据就会被部分覆盖(写入多少字节,就覆盖几个字节(字节换算根据编码来))
1
#提前将测试.txt的内容设置为:das;fljdaslfkmessage = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message.write('狗屎!') #此时光标在0的位置,所以写入会覆盖文本从光标往后的3个字符相当于9个字节(狗屎!)message.close() #此时文本内容变成了:狗屎!slfk
a+:写读模式(不清空):光标自动跑到最后
- 特性:只要执行写入操作,光标自动跑到最后,所以该模式写入永远都是追加
rb / wb / ab:
rb:读取的直接是二进制,open里就没有encoding
1
with open('测试.txt',mode='rb') as f : #此时这里没有encoding参数,并且模式为rb就是读取二进制,读取的将是二进制 data = f.read() print(data) #结果为:b'\xe4\xbd\xa0\xe5\xa5\xbd'
wb:直接写入二进制,write接收的参数是二进制
- 先将数据按照需要的编码编码成二进制,然后提交给write
- 适用范围:爬虫,我们爬取下来的数据都是无法直接利用的,需要将其按照它的编码方式进行编码,编码成二进制,然后写入我们的磁盘
1
#没有指定encoding,默认写入二进制,所以write接收参数为二进制with open('测试.txt',mode='wb') as f : f.write('你好') #报错:TypeError: a bytes-like object is required, not 'str',因为没有指定编码encoding,所以默认是写入二进制 #将数据转换为二进制,然后提交给write即可 with open('测试.txt',mode='wb') as f : data = '测试'.encode('utf-8') #先将'测试'按照utf-8的编码进行编码,编码成二进制 f.write(data) #结果为:测试
ab:同理,接收的也是二进制
r+b / w+b / a+b
w模式和a模式的区别:
- w模式会清空文本,a模式不会清空,是直接追加;只要模式一选定,就会自动清空,不管后面有没有执行操作语句
- 使用的写入函数都是write()
- w模式主要是用来新建文件用的
- a模式主要是用来新增内容用的
4.3 操作
移动光标:seek()
接收参数:字节
移动的计量单位:字节,将内容根据字节拆分,如果光标移动到一个字或者字符的内部了(比如汉字是3个字节,如果向右移动了1个字节,那么光标就在汉字内部),如果在执行写入,会将该汉字拆分成了乱码,而乱码正中间就是我们写入的字
1
#提前将测试.txt的内容设置为:狗屎!message = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message.seek(2) #接收的单位是字节message.write('6') #此时光标在2的位置,写入会从光标起始往后写message.close() #此时文本内容变成了:�6屎!
获取当前光标所在位置:返回值是字节
tell()
1
with open('测试.txt',mode='r',encoding='utf-8') as f : print(f.tell()) #结果为:0 f.seek(10) #移动光标到第10个字节的位置 print(f.tell()) #结果为:10
读取所有内容:
1 | '''提前将测试.txt的内容设置为:发的说法;看得舒服了的吉安市弗兰克圣诞节按付款了大师傅来陪我了'''message = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message1 = message.read()message.close()print(message1) |
读取所有内容,并按照每一行将其其存储到列表:readlines()
1 | '''提前将测试.txt的内容设置为:发的说法;看得舒服了的吉安市弗兰克圣诞节按付款了大师傅来陪我了'''message = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message1 = message.readlines()message.close()print(message1)#结果为:['发的说法\n', ';看得舒服了\n', '的吉安市弗兰克\n', '圣诞节按付款了\n', '大师傅来陪我\n', '了'] |
读取光标后的几个字符:
读取的起始位置:光标处
接收参数:字符
1
message = open('测试.txt',mode='r+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message1 = message.read(3) #接收参数为字符,并不是字节message.close()print(message1) #结果一:这时一
读取大文件:
意义:当一个数据很大,比如16g,我们一下子全部读取,可能电脑都会卡死,死机
特点:千万不要用read函数,因为用这个函数就相当于全部读取了
方法:利用for循环和read()来实现
1
test = open('测试.txt',mode='r',encoding='utf-8') for i in test #直接不读取,直接从内存中打印 print(i)
强制将内存中的数据刷到硬盘(类似于关闭文件)
- flush()
1 | f = open('测试.txt',mode='w',encoding='utf-8')f.write('大家附近的水立方简单来说加法里的数据莲富大厦客服')f.flush() #强制将内存中的数据刷入到磁盘中 |
4.4 关闭文件
手动执行关闭文件:
1 | #意义:将内存中的数据保存到硬盘上,如果没有该语句,那么数据不会保存a.close() |
自动执行关闭文件:推荐
要素:
- with ,as 变量 缩进
特点:一旦出了缩进,文件就自动关闭
多个语句,直接用逗号连接,最后结尾用冒号,并且只需要一个with即可
1 | with open('测试.txt',mode='w',encoding='utf-8') as f : #打开文件赋值给变量f |
4.5 文件内容的修改
前提:文件内容无法直接修改
原因:因为文件存储的本质是将二进制写入到磁盘,所以如果我们需要修改,就需要插入或删除对应位置的二进制,但是二进制不支持插入自动后移,如果我们插入数据,后面的数据必须要自动往后移动对应字节所占的位置,但是二进制不支持这一点
折中办法:在内存中修改,然后使用w模式(清空原有数据),然后将内存中的数据写进去
大文件的修改:
疑难点:可能文件50G,我们如果直接读取到内存,内存会爆
解决方法:打开两个文件,一行一行读取,读取一行写入一行(写到另外一个文件)
1
with open('测试.txt',mode='r',encoding='utf-8') as f ,open('测试2.txt',mode='a+',encoding='utf-8') as f1: for data in f : f1.write(data) #读取一行,写入一行 f1.seek(0) #将光标调到0,否则打印为空 data2 = f1.read() #读取f1的所有数据 print(data2)
4.6 文件和字典等序列的应用
将列表里的字典的内容添加到文本里
1 | '''#将字典内容添加到文本里张三 28李四 25王麻子 20'''#方法一:message = open('测试.txt',mode='a+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message_student = [{'name':'张三','age':28},{'name':'李四','age':25} ,{'name':'王麻子','age':20}]for i in range(0,len(message_student)): for v in message_student[i].values() : message1 = message.write(str(v)+' ') message.write('\n')message.seek(0)message1 = message.read()message.close()print(message1) #方法二:'''#将字典内容添加到文本里张三 28李四 25王麻子 20'''message = open('测试.txt',mode='a+',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message_student = [{'name':'张三','age':28},{'name':'李四','age':25} ,{'name':'王麻子','age':20}]for i in message_student: for k,v in i.items(): print(v) message.write(str(v)+' ') message.write('\n')print(message.read())message.close() |
将文本内容写到列表里的字典里
1 | # #方法一:根据lines'''#将文本内容写到字典张三 28李四 25王麻子 20'''message_student_all = []message = open('测试.txt',mode='r',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message_read = message.readlines()for i in message_read : message_student = {} #必须要在此处设置空字典,这样列表里就是多个字典了,而且不受彼此影响 student = i.strip('\n').split(' ') #去除换行符并根据空格来切割 name = student[0] age = student[1] message_student = {'name': name, 'age': age} message_student_all.append(message_student)print(message_student_all)message.close()'''#将文本内容写到字典张三 28李四 25王麻子 20'''#方法二:根据linemessage_student_all = []message = open('测试.txt',mode='r',encoding='utf-8') #将文件加载到内存,并将内存中的数据以unicode给变量message_read = message.read() #读取所有内容student = message_read.split('\n') #根据换行符进行切割# print(student)for v in student : v = v.strip('\n') #去除每一行的换行符 name,age = v.strip().split(' ') #去除空格并按照空格切割 message_student = {} # 必须要在此处设置空字典,这样列表里就是多个字典了,而且不受彼此影响 message_student = {'name': name, 'age': age} message_student_all.append(message_student)print(message_student_all)message.close() |
第五章 函数
5.1 函数的基本知识
编程思想:
- 面向过程编程:学习函数之前编写的代码都是面向过程,从上到下执行
- 缺点:代码量多,不利于阅读;代码重用性不高
- 面向函数编程:学习了函数之后编写代码,就是面向函数编程
- 优点:代码分门别类,便于阅读;代码重用性高
函数的特点:
一个函数就是一个作用域
1
def echo(i): print(i)for i in range(10): echo(i) #每调用一次就创建一个内存,内存中传递的i也不一样'''结果为:0123456789'''
函数本身是不可变类型
函数名就是相当于一个变量,指向了函数里面的代码,所以如果打印函数名,得到的只是一串地址,指向函数代码的地址
函数名的特点
函数名可以将其看作是变量,完全可以将其看做变量来使用,比如可以插在字典里,列表、元组里,并且可以将其作为参数来传递
注意点:如果将函数名放在集合里,放3个 fun 那么打印的结果是什么?
- 结果:该函数的代码所在的地址,因为集合是不重复的
如果将一个函数名赋值给了另外一个变量,那么该变量相当于也指向了该函数的代码块,从另外一种意义上来说,该变量就相当于一个函数了,在该变量后面加上括号()就可以调用函数了
函数名相当于变量,所以如果给函数名重新赋值,那么该变量就不再是函数
函数名可以作为参数进行传递
函数名可以作为返回值进行返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44#函数名作为变量插入到序列中
def inner():
print('666')
lis = []
for a in range(3):
lis.append(inner)
lis[0]() #结果为:666
lis[2]() #结果为:666
#函数名赋值给其他变量
def inner():
print('888')
index = inner #将inner函数代码的内存地址赋值给变量index
index() #所以调用该变量,就是调用原inner函数
#将其他数据赋值给函数名
def inner():
print('888')
inner = '重新赋值' #给inner重新赋值,所以inner不再指向函数代码,即inner不是函数了
print(inner) #结果为:重新赋值
#函数名作为参数进行传递
def inner():
print('888')
def fun(name):
name() #此时相当于inner()
fun(inner) #结果为:888
#函数名作为返回值
def inner():
def fun():
print('777')
return fun #返回的是fun函数代码的内存地址
index = inner() #将内存地址赋值给变量index
index() #所以调用该变量,就是调用原fun函数
函数名和 函数名 +() 的区别:
- 函数名类似于变量,内容是一串指向函数代码的地址,可以作为变量插在序列里,可以作为参数来传递
- 函数名 +() 相当于是函数的调用,就是执行函数,如果将函数名赋值给了另外一个变量,那么如果执行 变量 + () 也就是相当于指向该变量所指向的函数
函数的应用场景:
- 代码量多,超过一屏
- 代码重用性高
意见建议:
- 推荐不管多少代码,都用函数
函数的定义:
特点:函数不管定义多少个,如果不调用该函数,那么该定义的函数里面的代码不会执行
def 函数名 括号() 冒号
缩进
1 | def 函数名() : #冒号必不可少 |
函数的说明(注释):
建议:每个甘薯都必须要写函数说明
功能:对函数功能的说明
使用三引号
1
2
3
4
5
6
7
8
9#定义函数,输入三引号,然后再三引号内部回车即可
def ceshi(x):
'''
这是函数的使用说明
:param x:接收的参数
:return:返回值为空
'''
print(x)
ceshi(10)
函数的调用(执行):
- 函数名 + ()
- 函数的内存地址 + ()
- 函数执行的本质:
- 每调用一次,就是新开辟了一个内存
- 多次调用同一个函数,就是开辟了多个内存,这几个内存中的数据互不影响
1 | #函数名 + () 实现函数调用 |
函数的销毁:
前提:
函数执行完毕并且该函数里的变量没被占用(使用)
易错点:必须要以上两个条件同时满足函数才会被销毁
5.2 函数的参数
分类:
- 形参:定义函数时的参数名
- 实参:调用函数时的参数名
传参:
可接受的参数类型:所有类型
根本原则:所有的位置(args传参也算位置传参)传参必须要在关键字传参(kwargs也算关键字传参)之前
分类:
位置传参:不指定关键字,直接传参数
- 注意点:和关键字传参混合使用时,一定要遵循的原则,所有的位置传参必须要在关键字传参之前
1
2
3def message(a,b,c):
print(a,b,c) #结果为:1 2 3
message(1,2,3) #位置传参,和形参一一对应关键字传参:通过关键字(形参名)来传参
- 注意点:和关键字传参混合使用时,一定要遵循的原则,所有的位置传参必须要在关键字传参之前,也就是关键词传参之后不能再出现位置传参
1
2
3
4
5
6
7
8
9
10#正确做法
def message(a,b,c):
print(a,b,c) #结果为:1 3 2
message(1,c = 2,b = 3) #关键字传参,但是位置传参必须要在最前面
#错误做法:
def message(a,b,c):
print(a,b,c) #报错:SyntaxError: positional argument follows keyword argument
message(1,c = 2,3) #错误:位置传参3在关键字传参之后默认传参:
设置默认参数的推荐方法:将默认参数设置为None
特性:调用函数时如果没传递该默认参数,则执行该函数时该参数就按照默认的值来;如果我们在调用函数,传递了该默认参数,那么该默认参数的值就按照我们传递的值来算。
表现形式: 参数名 = 数据
注意点:修改默认参数,一定要注意,最好只修改数据类型为不可变类型的参数,如果修改的参数是可变类型,那么后续会导致各种bug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def message(a,b,c = 5): #默认参数c为5
print(a,b,c) #没传递默认参数,那么默认参数的值就按照默认的5来算
message(1,2) #此时传递2个参数并不会报错,默认参数可指定可不指定
#位置传递默认参数:传递默认参数,按照传递的值来算
def message(a,b,c = 5): #默认参数c为5
print(a,b,c) #传递了默认参数10,所以此时c为10
message(1,2,10) #此时传递的第三个参数为默认参数,所以默认参数的值变为了10
#关键字传递默认参数:传递默认参数,按照传递的值来算
def message(a,b,c = 5): #默认参数c为5
print(a,b,c) #传递了默认参数20,所以此时c为20
message(1,2,c = 20) #此时传递的第三个参数为默认参数,所以默认参数的值变为了20
默认传参必须是不可变类型的原因:
基本原理:因为在我们定义函数时,函数里的变量就会被创建,所以函数里的变量会在函数执行前被创建;那么如果我们在传参时不修改默认参数,我们每一次调用函数开辟的内存实际上都是在共用一个变量(默认参数),如果该默认参数时可变类型,我们第一次调用函数,利用代码(比如append)对默认参数所指向的可变类型进行了修改,那么当我们第二次调用该函数时,该函数的默认变量就不再是原来的初始值了,而是经过第一次函数调用修改后的值,所以这样会导致默认参数失去了意义;而如果使用不可变类型,因为无法修改,所以就不存在这种情况;
```python
def fun(a,b = []): #在定义函数时,这两个变量就创建了 b.append(a) print(a,b) fun(1) #结果为:1 [1],执行完,默认参数b所指向的列表已经发生了变化[1]fun(2) #执行到这里,默认参数指向[1],然后再执行添加2,所以结果为[1, 2]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- 示意图:

接收所有的位置参数:
- 优点:不需要写多个形参,无论传递过来多少个实参,只需要一个 *args 就能全部接受
- 缺点:只能接收位置传参,不能和关键字传参结合使用
- 特性:将接收来的所有参数全部加到一个元组里了
- 表现形式:
函数位置:*args 英文单词是arguments
调用函数位置:(传参时)
- 无星号:多个参数,例如 fun(‘你好’,'s') 参数‘你好’和参数's'全部被args接收了,接收到的左右数据全部加到一个元组里了
- 有星号:*('你好','s') 星号后面的 ('你好','s') 作为一个整体传递给了args,所以args就等于('你好','s')
```python
#传参时无星号def message(*args): #将星号后的参数作为一个整体传递给了args print(args) #结果为:(1, 2, 20)message(1,2,20) #传参时无星号:不能和关键字传参结合使用def message(*args): #接收所有参数,并将所有参数加入到一个元组里print(args) #报错:TypeError: message() got an unexpected keyword argument 'c'message(1,2,c = 10) #错误:*args不能用关键字传参,不能和字传参结合传参
1 | 接收所有的关键字参数:- 优点:可以接收所有的关键字参数- 缺点:只能接收关键字参数,不能接收位置参数- 特性:将接收来的参数全部加到一个字典里了,其中关键字作为该字典的键,等于号后面的值作为字典的值- 表现形式: 函数位置: **kwargs kw是keyword的缩写 调用函数位置:(传递参数时) - 有双星号: - 主要用途:传递字典,而不是变量字典 - 原理:将双星号后的字典作为一个整体,传递给了kwargs,所以kwargs就是相当于直接等于双星号后面的这个字典 - 易错点:当我们用双星号直接传递字典时,需要注意,键和值的位置要区分号变量和字符串的关系,如果不加单引号,容易提示不存在该变量 - 无双星号: - 表现形式:用等于号 = 连接 - 将每个参数(以等号连接的表达式算一个参数)拆分成一个键值对加入到一个字典中 ```python # 有双星号 **:fun(**{'a' ='s'}) 将双星号后面的字典作为一个整体传递给kwargs,所以kwargs等于该字典 # 无双星号** :fun( a ='s',b = 's1') 将每一个参数根据等于号来进行拆分,拆分成键值对加入到字典中 # 无双星号** : def message(**kwargs): #将每一个参数根据等于号来进行拆分,拆分成键值对加入到字典中 print(kwargs) #结果为:{'a': 1, 'b': 2} message(a = 1,b=2) # 有双星号 **:fun(**{a ='s'}) def message(**kwargs): #将接收来的字典直接传递给kwargs print(kwargs) #结果为:{'a': 1} message( **{'a':1} ) #双星号用途主要是传递字典,此时双星号后的字典{'a':1}直接等于kwargs |
接收所有参数:万能接收
特点:*args和**kwargs的组合
接收的参数:位置参数、关键字参数都可以接收
易错点:在传参的时候,可以只传一种类型的参数,哪怕函数的形参有args和kwargs,我们传参的时候只传位置参数或者只传关键字参数都可以,没传的参数所代表的的形参就位空
1
#只传位置参数,那么关键词蚕食就是空字典def func1(*args,**kwargs): print(args,kwargs)func1(*{'武沛齐','金鑫','女神'}) #结果为:('金鑫', '女神', '武沛齐') {}#只传关键字参数,那么位置参数就是空元组def func1(*args,**kwargs): print(args,kwargs)func1(**{'k1':'栈'}) #结果为:() {'k1': '栈'}
语法:
1
#位置在前,关键字在后(一):def fun(*args,**kwargs): #接收所有的参数:包括位置和关键字 print(args,kwargs) #结果为:((3, 6, 8),) {'b': 20}fun((3,6,8),b = 20) #原则:位置在前,关键字在后#位置在前,关键字在后(二):def fun(*args,**kwargs): #接收所有的参数:包括位置和关键字 print(args,kwargs) #结果为:(3, 6, 8) {'b': 20}fun(3,6,8,b = 20) #原则:位置在前,关键字在后#位置在后,关键字在前,报错def fun(*args,**kwargs): #接收所有的参数:包括位置和关键字 print(args,kwargs) #报错:SyntaxError: positional argument follows keyword argumentfun(3,6,b = 20,8) #报错:此处位置参数在关键字参数之后
通过参数传递函数名:
1 | # 函数名可以作为参数进行传递def func(arg): arg() #相当于show(),这里也即是执行了函数的调用def show(): print('show函数')func(show) #结果为:show函数 |
5.3 函数的返回值
语法:
- 如果没有return,则默认返回None
- return 什么 就返回什么
特性:
碰到return,就会自动终止该函数,直接返回值,return后续的代码不再执行;当前函数执行完毕,如果是多层函数,那么就会回到上层函数
1
#return配合死循环实现输入n返回上一级def fun(): while True : #必须要加死循环,便于下级函数执行完毕后进行二次判断 print('1.业务查询\n2.业务办理\n3.人工服务') choice = input('输入n退出') if choice == 'n': return #结束本函数 elif choice == '1': business_query() elif choice == '2': print('该功能正在开发中....')def business_query(): '''业务查询''' while True : #必须要加死循环,便于下级函数执行完毕后进行二次判断 print('1.话费查询\n2.流量查询') choice = input('输入n退出') if choice == 'n': return #结束本函数,回到上级函数fun elif choice == '1': phone_query() elif choice == '2': print('该功能正在开发中')def phone_query(): '''查询话费''' while True : #必须要加死循环,便于下级函数执行完毕后进行二次判断 print('查询成功!你当前话费:63元') choice = input('输入n退出') if choice == 'n': return #结束本函数,回到上级函数business_queryfun()
如果返回多个数据,用逗号隔开,返回得到的是一个元组
1
def message(): return 5,6,7a = message() #调用函数,输入函数名 + 括号print(a) #结果为:(5, 6, 7)
返回值所支持的类型:
- 任何数据类型
- 表达式
特殊情况:return 3,4,5 相当于 return (3,4,5)
问题:return和break的区别?
- return:终止当前函数,直接返回值
- break:跳出当前循环,如果循环在函数里,那么break只会跳出该循环,而不会跳出该函数
5.4函数的作用域
定义:一个函数就是一个作用域
分类:
- 全局作用域:全局作用域无法调用局部作用域里的变量;父级
- 局部作用域:局部作用域里可以调用全局作用域里的变量;子级
- 特点:局部作用域里的变量一旦执行完毕,该变量会被销毁;可以找到父级作用域中的值,但是无法对其重新赋值;但是如果该值是可变类型,那么则可以对其进行修改
在作用域中查找数据的规则:
先在当前作用域查找,如果没找到,就向上级作用域查找,一直向上查找,如果查到头(全局作用域)还没找到该数据,那么程序会报错,提示该数据不存在(或者改变了不存在)
如果变量定义在函数调用之前,那么函数就可以找到改变量;如果变量定义在函数调用之后,那么函数就无法找到改变量,程序会报错,提示改变量不存在
子作用域中只能找到父级的值,而无法对该值进行重新赋值,但是如果该值是可变类型,那么则可以对其进行修改
1 | #变量定义在函数调用之前,那么函数就可以找到改变量a = 10def fun(): #可以不接受参数 print(a) #结果为:10 ,因为变量定义在函数调用之前fun() #可以不传递参数#变量定义在函数调用之后,那么函数就无法找到改变量,程序会报错,提示改变量不存在def fun(): print(a) #报错:NameError: name 'a' is not definedfun() a = 10 #该变量定义在函数调用之后,所以函数会找不到该变量#查找数据先从当前作用域查找,函数里的局部变量在函数执行完毕后会自动销毁(一):a = 10def fun(): a = 20 print(a) #查找数据先从当前作用域查找,如果存在,就直接使用,所以结果为20fun()print(a) #结果为10,因为函数里的变量在函数执行完毕后会销毁,并且函数没有修改10,只是给a重新赋值了而已,赋值不等于修改#查找数据先从当前作用域查找,函数里的局部变量在函数执行完毕后会自动销毁(二):a = [1,2]def fun(): a.append(6) #这里是对原列表进行了修改,和a没有关系,所以如果a销毁了,该列表还是变成了[1,2,6] print(a) fun()print(a) #结果:[1, 2, 6] 因为函数对列表[1,2]进行了修改 |
5.5函数的嵌套
特点:
- 每个函数执行完,它作用域里的变量将会被销毁
- 只定义了函数,但是没有调用函数,那么该函数还是没有执行
- 碰到函数的调用,就先进入该函数 ,然后执行里面的代码
- 作用域查找数据、查找变量,现在当前作用域查找,当前作用域没有,就向上一级找,一级一级往上找,直至全局作用域
- 只能找到父级(上面级别)作用域的值,而无法对其重新赋值,但是如果该值是可变类型,那么则可以对其进行修改
注意点:
- 定义函数就是创建函数,函数在哪个作用域被创建,那么该作用域就是被创建的那个函数的父级作用域,所以找数据,如果被创建的作用域里没有,就去它的父级找,也就是定义函数的代码在那个作用域,那么该函数找数据的父级就是该做作用域
1 | #函数的嵌套与作用域:当前作用域中有数据 |
面试题:
定义不同的函数实现10086的不同功能,然后用户输入不同的内容,程序就调用不同的函数,如果用户输入的内容不存在,那么就提示用户输入错误,此题假设选择超过5个,所以不能使用5个if,这里为了演示,不写那么多个
考察点:如果 if … else语句超过5个,就不建议使用,太繁琐了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#假设业务数量超过5个,所以不能使用5个if
def business_query():
'''业务查询'''
print('业务查询成功')
def phone_recharge():
'''手机充值'''
print('手机充值成功!')
def Business_handling():
'''业务办理'''
print('业务办理成功')
def Human_services():
'''人工服务'''
print('xx客服为你服务!')
dic = {'f1':business_query,'f2':phone_recharge,'f3':Business_handling,'f4':Human_services}
choice = input('请输入f1 - f4 选择不同的服务:')
result = dic.get(choice) #将函数的地址赋值给变量result
if result : #如果输入的值在字典的key里
result() #调用函数
else :
print('输入错误!')
5.6全局变量和局部变量总结
全局变量和局部变量命名规范:
- 全局变量:全部用大写来表示,例如:STUDENT_NAME
- 局部变量:全部用小写来表示,例如:student_name
总结:
子级作用域只能找到负极作用域(包括全局作用域)的数据,但是默认无法在子级作用域对该数据进行重新赋值,如果使用等于号进行强制操作,那样的结果只是新创建了一个变量而已,而不是对父级作用域的变量进行重新赋值,这里需要注意
1
#默认无法在子级作用域对父级作用域的变量进行重新赋值def fun(): AGE = 20 #这里并不是对全局变量AGE重新赋值,相当于是重新创建了一个变量AGE, print(AGE) #结果为:20AGE = 10 #全局变量用大写来表示fun()
子级作用域在父级作用域查找数据,如果该数据类型是可变类型,那么可以在子级作用域对该可变类型数据进行修改
1 | #如果父级作用域的数据类型为可变类型,则在子级作用域中可以对其进行修改def fun(): AGE.append(66) print(AGE) #结果为:[1, 2, 66]AGE = [1,2] #列表为可变类型fun() |
非要在子级作用域给父级作用域的数据进行重新赋值该怎么办?
global:直接定位到全局作用域的数据,使用了global + 变量名 后,在该子级作用域中如果有赋值代码,那么就相当于把全局作用域的数据进行重新赋值了;
注意点:定位的是全局作用域
1
#使用global,实现在子级作用域给全局作用域的变量进行重新赋值def fun(): AGE = 20 print(AGE) #结果为:20 def fun1(): global AGE #定位到全局变量AGE,接下来的赋值就是对全局变量的重新赋值 AGE = 30 #这里相当于是对全局变量的重新赋值 fun1() #结果为:20 print(AGE)AGE = 10 fun()print(AGE) #结果为30,因为fun1使用了global,对全局变量AGE进行了重新赋值
nonlocal:直接定位到当前子级作用域的父级作用域,如果使用了nonlocal + 变量名 后,在该子级作用域中如果有赋值代码,那么就相当于把该子级作用域的父级作用域里的变量进行了重新赋值
注意点:针对的仅仅是当前子级作用域的父级作用域的变量,如果想对更上级的父级作用域(不包括全局作用域)的变量进行重新赋值,那么需要在该子级作用域的父级作用域也使用 nonlocal + 变量名 来实现
易错点:通过nonlocal对上一级的变量进行重新赋值,这个重新赋值的结果以当前所在子作用域的最终结果为准
1
#使用nonlocal对上一级作用域的变量进行重新赋值(一)def fun(): AGE = 20 print(AGE) #结果为:20 def fun1(): nonlocal AGE #定位到当前子级作用域的父级作用域,接下来的赋值就是对上一级作用域变量的重新赋值 AGE = 30 #这里相当于是对上一级作用域的重新赋值 fun1() print(AGE) #结果为:30,因为fun1使用了nonlocal对fun的作用域里的AGE进行了重新赋值AGE = 10fun()print(AGE) #结果为10#使用多个nonlocal对上上级作用域的变量进行重新赋值(二)def fun(): AGE = 20 print(AGE) #结果为:20 def fun1(): nonlocal AGE AGE = 30 #在fun2没执行时AGE是30,相当于第一次赋值,执行fun2后,相当于对AGE重新赋值,所以最终AGE为40 def fun2(): nonlocal AGE AGE = 40 #执行到这里,对上一级作用域的AGE修改为40,相当于对上上级作用域的AGE进行二次赋值 fun2() fun1() print(AGE) #到这里,AGE为经过二次赋值,最终结果为:40AGE = 10fun()print(AGE) #结果为10#通过nonlocal对上一级的变量进行重新赋值,这个重新赋值的结果以当前所在子作用域的最终结果为准def fun(): AGE = 20 def fun1(): nonlocal AGE AGE = 30 #第一次赋值 AGE = 666 #第二次赋值,以最终的赋值为结果 fun1() print(AGE) #以它的子作用域的最终值为结果,结果为666AGE = 10fun()print(AGE) #结果为10
global和nonlocal的应用场景:
- global:一般情况下不使用,虽然可以对全局变量进行重新赋值,但是很容易造成代码混乱
- nonlocal:面试必考
5.7 lambda表达式
lambda表达式和三元运算(三目运算)的区别:
三元运算:表达简单的 if…else语句
lambda表达式:表达简单的函数
特点:是匿名函数,意思就是没有函数名
注意点:函数必须特别简单,函数的代码内容只有 return语句
1
def fun(a,b): return a+b可以转换成lambda语句:lambda a,b:a+b
lambda表达式的基本结构:
1 | lambda 参数 : 需要返回的内容 |
lambda表达式的特点:
- 自带隐藏return
lambda的几种表现形式:
1 | #1、有两个参数时:参数a b之间用逗号隔开result = lambda a,b:a+b #result相当于是个函数名,两个参数名写在冒号前print(result(10,20)) #结果为:30,此处传递参数10,20'''相当于代码:def result(a,b): return a+bprint(result(10,20))'''#2、有1个参数时:冒号前就只需要写一个参数 aresult = lambda a:a+50 #result相当于是个函数名,只有一个参数:aprint(result(10)) #结果为:60,此处传参10'''相当于代码:def result(a): return a+50print(result(10))'''#3、有0个参数时:冒号前则不需要写参数名result = lambda :666 #0个参数,冒号前就不填参数print(result()) #结果为:666'''相当于代码:def result(): return 666print(result())'''#4、用万能参数作为参数时:result = lambda *args,**kwargs:args #自带return功能print(result(20,16)) #结果为:(20,16)'''相当于代码:def result(*args,**kwargs): return argsprint(result(20,16))'''#5、当代码部分为变量时:从父级作用域开始找a = 10result = lambda :a + 60 #lambda的定义,使其自身无法给变量赋值,所以如果查找变量,必须要去它的父级去找print(result()) #结果为:70'''相当于代码:def result(): return aprint(result())'''#6、和三元运算混合使用result = lambda a,b:999 if a > b else 555 #如果a>b,则返回999,否则返回555print(result(20,10)) #结果为:999'''相当于代码:def result(a,b): return 999 if a > b else 555print(result(20,10))''' |
5.8 内置函数
分类:
- 输入、输出
- 强制转换
- 数学运算
- 进制转换
- 其它函数
输入、输出函数:print()、input()
强制转换函数:
- int
- bool
- str
- tuple
- list
- dict
- set
数学运算函数:
绝对值:abs()
转小数:float()
最大值:max()
最小值:min()
求和:sum()
求商和余数:divmod()
功能:第一个参数为除数,第二个参数为被除数,返回的结果是一个元组,元组的第一个参数时商,第二个参数时余数
1
print(divmod(50,22)) #结果为:(2, 6) ,返回的是元组,第一是商,第二个是余数
现实应用:分页显示内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19'''
前提:需要打印的数据内容太多,一页显示不了
需求:按照用户要求的多少条数据为一页,让用户输入页数,并显示
'''
lis = [] #用来存放需要打印的数据
for i in range(1,545):
message = '这是我第%d次结婚'%i
lis.append(message) #构造大批量的内容,为后续的分页做准备
# print(message) #直接打印的话,内容太多,一屏都无法显示完,所以需要分页显示
message_page = int(input('请设定每页需要显示多少条内容:'))
page_all,page_remaining = divmod(len(lis),message_page) #得到一共有多少页,和多余的行数
if page_remaining != 0 :
page_all += 1 #如果还有多余的内容,也需要多加一页来显示
page_input = input('请输入需要查看第几页:')
if 0< int(page_input) < page_all : #判断输入的页码是否在范围之内
for v in lis[(int(page_input) - 1) *message_page : int(page_input)*message_page ] : #查看页数*每页的显示数进行切片
print(v) #遍历打印切片的内容
else :
print('输入页码错误!')幂运算:pow()
1
print(pow(2,3)) #2的3次方,结果为:8
保留小数:round()
特点:自动四舍五入;如果没有指定需要保留的小数位,则默认保留整数,保留后的结果为四舍五入的结果
1
#用法:round(小数,保留位数)print(round(3.567)) #没有指定保留位数时,默认不保留小数位,结果为:4print(round(3.567,1)) #保留1个小数位,并四舍五入,结果为:3.6
进制转换:
核心原则:以十进制为中转站,进行所有的数据互转
原因:所有的进制可以转换成十进制,十进制可以转换成其他所有的进制,但是非十进制转非十进制,则无法直接转换,需要用十进制来作中转
十进制转其他进制:
十进制转二进制:bin()
十进制转八进制:oct()
十进制转十六进制:hex()
1
#用到函数:bin,oct,hexnum = 50print(bin(num)) #转二进制,结果:0b110010print(oct(num)) #转八进制,结果:0o62print(hex(num)) #转十六进制,结果:0x32
其他进制转十进制
语法:int(需要转换的数据,base = 数值(该数据的进制数))
特性:平常可以不写base,直接写数字即可,因为是位置传参;如果数字不写,base默认是10
1
#其他进制转十进制num = '0b110010' #二进制num1 = '0o62' #八进制num2 = '0x32' #十六进制print(int(num,2)) #转十进制,结果:50 ,需要转换的数据的进制数是2print(int(num1,8)) #转十进制,结果:50,需要转换的数据的进制数是8print(int(num2,16)) #转十进制,结果:50,需要转换的数据的进制数是16
- 非10进制转非10进制:
- 核心:用10进制做中转
1 | #非10进制转非10进制:用10进制做中转a = '0b110010' #二进制print( oct( int(a,2) ) ) #先将二进制转10进制,然后将10进制转八进制 ,结果为:0o62print( hex( int(a,2) ) ) #先将二进制转10进制,然后将10进制转八进制 ,结果为:0x32 |
其它内置函数:
- open
- id
- type
- range
- len
进制练习题:
将’192.168.12.79’转换为二进制,用点 . 连接
1
ip = '192.168.12.79'ip = ip.split('.') #提取每一个数字for index in range(0,len(ip)): ip[index] = bin(int(ip[index])) #将列表里的每一个字符转成整型,然后转成二进制ip = '.'.join(ip) #用.拼接起来print(ip) #结果为:0b11000000.0b10101000.0b1100.0b1001111
进制面试题:
将’192.168.12.79’转换为二进制,用点 . 拼接起来形成一个新的二进制,然后将该二进制转换成十进制
要求:用函数实现;要求每个数转换成的二进制的位数都是8位
1
def ip_change(ip): ip = ip.split('.') #提取每一个数字 lis_ip = [] #储存处理后的二进制数据 for v in ip: v = bin(int(v)).replace('0b','') #将每个数字转换成二进制,并去掉0b v = '0'*( 8 - len(v)) + v #因为是utf-8编码,所以统一按8位算,去掉0b后,不足8位在前面补0 lis_ip.append(v) ip_bin = ''.join(lis_ip) #将二进制拼接起来 ip_int = int(ip_bin,2) #将二进制字符串转换为十进制 return ip_int #返回已经转换为十进制的值IP = '192.168.12.79'result = ip_change(IP)print(result) #结果为:0b11000000.0b10101000.0b1100.0b1001111
高级的内置函数:
特点:实际工作中使用的少,但是面试遇到的多
map:对一组序列中的每个元素进行相同的操作
格式:map(函数,待操作的数据)
注意点:这里的函数既可以是函数名,也可以是lambda表达式
易错点:最后展示结果,必须要list来展示
1
#方map(函数(x),序列)a = [2,3,4]result = map(lambda x : x*100,a) #a中的每一个元素,传递到了函数里进行处理,然后将处理后的元素加入到一个新列表print(list(result)) #因为result是列表,所以最好用list来显示,结果为:[200, 300, 400]
reduce:将序列中的所有元素合并成一个元素
格式:reduce(函数(x,y),序列)
注意点:这里的函数既可以是函数名,也可以是lambda表达式
reduce在python3中已经被去除,如果想要使用,必须要导入functools模块调用reduce函数
易错点:最后的展示结果,因为result是一个值,所以直接打印即可
1
#reduce(函数(x,y),序列)import functools #在python3中,reduce已经去掉了,所以需要调用模块functools来实现a = [2,3,4,5] result = functools.reduce(lambda x,y :x+y,a) #lambda必须要接受两个参数,这里的意思是求序列所有元素的和,返回的是一个值#第一次传递第一个元素和第二个元素,二者相加得到的值作为第二次传递参数时的第一个参数,第二个参数相当于第三个元素,通过这种方法实现累加print(result) #因为result是一个值,所以直接显示,结果为:14
filter:将序列中的元素进行过滤,如果存在该元素,则返回True,同时将该元素加到一个新列表中
格式:filter(函数(x),序列)
注意点:这里的函数既可以是函数名,也可以是lambda表达式
易错点:最后展示结果,必须要list来展示
1
2
3
4#filter(函数(x),序列)
a = [2,3,4,5]
result = filter(lambda x: x>3 ,a) #将所有元素和3进行比对,如果返回的比对结果为True,则将该数据加到一个新列表中
print(list(result)) #结果为:[4, 5]
5.9 函数的闭包
功能:保留传递到函数里的变量(数据)不被销毁,方便下次直接使用
关键:
- 封装值
- 内部函数需要使用
特性:双层函数;外层函数必须要接收变量,内层函数的代码必须要操作该变量,如果不操作,那么变量无人使用,就会被销毁,有操作,那么就不会被销毁
基本原理:构造双层函数,外层函数返回内存函数的地址,作为返回值赋值给一个变量,然后用该变量来调用内层函数,因为执行外层函数时,内层函数里有对传入的变量进行操作的代码,所以传入的变量没被销毁,使其得以保留了下来,所以相当于外层函数代码没有执行完毕,所以外层函数不会被销毁,因此传入的变量就因此保存了下来
1 | #目的:保留num这个变量所代表的的值,使其不被销毁,方便下次调用 |
闭包的关键点:
- 让外层函数不被销毁,从而保留传递给外层函数的变量
- 将内存函数的地址作为返回值
闭包完成后调用内层函数,怎么找数据?
- 内层函数被B创建,那么B就是内层函数的父级,如果内层函数没找到数据,就去它的父级即去找
同一函数被多次调用:
每次调用都创建一个内存空间,这些内存空间互相独立,互不影响
1
2
3
4
5
6
7
8
9#多次调用同一个函数时
def fun(num):
def inner():
print(num + 10)
return inner
index = fun(50) #执行fun,开辟了一个内存,此处保留了50
index1 = fun(80) #第二次执行fun,开辟了另外一个内存,和第一个内存互相独立,此处保留了80
index() #结果为:60
index1() #结果为:90示意图:
练习题:
函数内调用函数:这两个函数的父级都是全局作用域
1
2
3
4
5
6
7
8# 函数内调用函数
def fun():
print(a)
def inner():
print(a)
fun()
a = 'oldboy'
inner() #结果为 oldboy inner() #结果为 oldboy示意图:
函数内嵌套函数:内层函数由谁创建,谁就是该内层函数的父级,内层函数找数据就先在当前作用域查找,如果没有就去其父级作用域查找
1
2
3
4
5
6
7
8
9# 函数内嵌套函数
def inner(a):
print(a) #结果为: oldboy
def fun(): #它由inner所在的作用域创建
print(a) #所以查找数据先从本级作用域查找,然后去父级作用域查找,inner代码所在作用域
a = '666'
fun() #结果为:666
a = 'oldboy'
inner(a) #结果为 oldboy 666示意图:
在全局作用域调用函数A内嵌套的函数B:闭包
1
2
3
4
5
6
7
8
9
10#在全局作用与调用函数A内嵌套的函数B
def inner(a):
print(a) #结果为: oldboy
def fun(): #它由inner所在的作用域创建
print(a) #所以查找数据先从本级作用域查找,然后去父级作用域查找,inner代码所在作用域
a = '666'
return fun
a = 'oldboy'
index = inner(a) #结果为 oldboy ,并将fun函数地址赋值给index
index() #调用内层函数fun,fun找数据先从本级作用域找,如果没有则从创建它的父级作用域inner代码所在作用域找示意图:
闭包和for的结合使用:
目的:保留每次for得到的数据,如果不使用闭包
闭包和不闭包的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#使用闭包保留每次for循环的数据:也就是a的值
def inner(a):
def fun(): #闭包保留传进来的a
print(a)
return fun
lis = []
for a in range(3):
index = inner(a)
lis.append(index)
lis[0]() #结果为:0
lis[2]() #结果为:2
#不使用闭包出现的问题
def inner():
print(a)
lis = []
for a in range(3):
lis.append(inner)
lis[0]() # 结果是2
lis[2]() # 结果是2
5.10 装饰器
5.10.1 装饰器基本知识
定义:类似于现实中的化妆,对人脸进行修饰
应用场景:
- 希望在不修改原函数的情况下,给原函数增加功能;
- 批量给多个函数增加类似的功能
针对对象:函数
装饰器的本质:就是构造一个装饰器(函数B),然后将需要装饰的函数A作为参数传到B里,相当于就是在B里新建了一个变量,该变量指向了函数A,所以我们可以在装饰器的作用域中,调用该变量(也就是调用原函数),并且我们可以调用变量之前或者之后,写上其他操作代码,比如打印’666’,或者循环调用该变量。这些操作对原函数A所指向的函数代码没有任何影响
装饰器的特点:
- 在不改变原函数的条件下,如果想对原函数的功能进行增加,那么就需要构造一个装饰器,将需要修饰的函数放到该装饰器中,即可对原函数进行增加功能。
- 无法对原函数的代码进行修改,同时,只能在原函数的基础上增加功能,并不能减少功能,可以在装饰器的作用域里进行添加修饰代码,该修饰代码可以放在被修饰的函数之前或者之后
装饰器的核心:
- 函数名的重新赋值(如果函数名被重新赋值了一个数值,那么它就不再是函数了)
- 被装饰函数的函数名作为参数传递给装饰器
装饰器和闭包的区别:
- 闭包是为了保存变量,该变量可以是所有类型的数据
- 装饰器保留的也是变量,只不过该变量是传进来的函数名,也就是相当于该变量是指向了一个函数的地址的(被装饰的函数)
5.10.2 装饰器的构造与调用
装饰器的构造:
分类:
- 基本写法:只适合被装饰函数不接收参数的情况下
- 推荐写法:无论被装饰函数接不接收参数,都可以使用,这里只需要在内层函数和内层函数里的指向被装饰函数的变量的形参里写上万能参数 *args **kwargs即可
本质就是一个2层函数,外层函数必要要有形参,用来接收传递进来的被装饰函数的函数名
内层函数就是相当于是一个装饰器了,内层函数里面的代码必须要调用被修饰的函数,必须要对外层函数的形参进行调用(因为此时这个形参已经指向了被修饰函数)
可以在内层函数里进行添加修饰代码,该代码可以放在调用形参(函数)之前或者之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#基本写法:只适合被装饰函数无参数
def fun(x): #外层函数,必须要有形参x,接收传来的被装饰函数的内存地址
def inner():
print('这是修饰语句1,可以放在操作被修饰函数之前')
return x() #调用x就相当于调用其指向的被修饰函数,return可以不写,这里是为了接收被修饰参数的返回值
# print('这是修饰语句2,可以放在操作被修饰函数之后')
return inner
def index():
print('这是装饰器的最基本格式')
#推荐写法:用万能形参来接收参数
def fun(x):
def inner(*args,**kwargs): #这是外部用来调用的函数,如果想被装饰函数接收参数,这里也必须要接收参数,否则其内部的被装饰函数无法接收到函数
return x(*args,**kwargs) # inner传递过来的参数再传递给被装饰函数进行执行
return inner
def index(*args,**kwargs):
print(args,kwargs)
y = fun(index) #将传递回来的inner函数地址赋值给y
y(10,a = 16) #传递参数,相当于调用inner,结果为:(10,) {'a': 16}
装饰器的调用:
前提:装饰器已经构造好
基本格式: @ + 装饰器的父级函数
功能:执行@后面的函数,并将 ‘‘@ + 装饰器的父级函数’’ 语句下面的定义的函数的函数名作为一个一个参数传递过去,当装饰器的父级函数执行完毕后,将其返回的装饰器的地址接收过来,赋值给 ‘‘@ + 装饰器的父级函数’’ 语句下面的定义的函数的函数名,所以该函数名就不再指向被装饰的函数,而是指向被装饰函数的装饰器
易错点:
- @ + 函数名 后面没有括号
- @ + 函数名 语句下面一定要紧跟定义函数的语句
1
#装饰器的调用def fun(x): def inner(*args,**kwargs): #该函数相当于被装饰函数index的装饰器 return x(*args,**kwargs) # inner传递过来的参数再传递给被装饰函数进行执行 return inner #返回装饰器的地址'''@ + 函数 一共有2个功能1、执行函数fun,并将变量名index作为fun()的参数,相当于执行fun(index)2、将fun(index)执行后的结果得返回值赋值给 “@ + 函数()”语句下面定义的函数名,也就是相当于将返回的装饰器地址inner赋值给了index, 也就是 index = inner '''@fun #注意,fun后面没有括号(),并且该语句后面必须要紧跟定义函数的语句def index(*args,**kwargs): print(args,kwargs)index(16) #这里执行x就相当于执行装饰器inner,所以结果为:(16,) {}
装饰器的小应用:
需求:
计算多个函数执行的时间
优点:虽然不使用装饰器也可以实现,但是如果需要计算很多个函数的执行时间,那么工程量就会很大,如果用装饰器的话,只需要构造一个装饰器,再加上调用即可
1
#计算多个函数执行的时间import time #导入时间模块,用于计算时间def fun(x): def inner(*args,**kwargs): begin = time.time() #获取函数执行之前的时间戳 x() end = time.time() #获取函数执行完毕后的时间戳 print('程序执行完毕,用时%s秒'%(end - begin,)) return inner #这里一定不能加括号@fun #调用装饰器def index(): print('666') time.sleep(2) #方便计算程序执行时间,这里加个延迟2秒@fun #调用装饰器def index1(): print('999') time.sleep(3) #方便计算程序执行时间,这里加个延迟3秒index() #结果为:666 程序执行完毕,用时2.0001144409179688秒index1() #结果为:999 程序执行完毕,用时3.000171661376953秒
5.10.3 带参数的装饰器
在实际中主要的功能:起到开关的作用,接收的参数相当于是一个开关,不同的开关,则对装饰器进行不同的操作
与普通装饰器的区别:
结构的区别:
普通装饰器:两层函数
带参数的装饰器:三层函数
调用时格式的区别:
普通装饰器:@ + 函数名
带参数的装饰器:@ + 函数名 + (参数)
调用时功能的区别:
普通装饰器: @fun
- 将@fun下面定义的函数名B作为参数传递给fun函数,并执行fun函数
- 将执行fun函数后的返回值(装饰器的地址)赋值给B
带参数的装饰器:@fun(9)
- 执行fun(9),请注意,此时传递过去的参数时9,然后将第二层函数的地址返回给fun(那么此时fun就指向第二层函数的地址)
- 将@fun(9)下面定义的函数名B作为参数传递给fun函数(第二层函数)进行执行
- 将执行后返回的装饰器地址(第三层函数的地址)返回赋值给函数名B
应用实例:
通过不同的开关True,False来让装饰器执行不同的操作
1
# flag为True,则 让原函数执行后返回值加100,并返回。# flag为False,则 让原函数执行后返回值减100,并返回。def x(status): def change(fun): def inner(*args,**kwargs): if status == True: #满足条件就执行,并且结束函数 return fun() + 100 return fun() - 100 #不满足条件,就执行减法 return inner return change@x(True) #待参数的装饰器,主要起到开关的作用def f1(): return 11@x(False)def f2(): return 22r1 = f1()print(r1)
获取被装饰函数的函数名
语法: 装饰器保留的变量(指向被装饰函数) + 点(.) + 两个横杠(__) + name + 两个横杠
注意点:获取的是函数的名字,而不是函数名指向的地址
name前是两个横杠
1
def fun(x): def inner(*args,**kwargs): x() print('获取到的被装饰函数名是:{}'.format(x.__name__)) #打印被装饰函数的函数名,结果为:ceshi;注意,name前面和后面都是 两个横杠 return inner@fundef ceshi(): print('获取被装饰函数的函数名,结果如下:')ceshi()
5.11 推导式
目的:对一个序列里的所有元素进行相同的操作,然后将操作后的元素加入到一个新列表中,最终的结果是一个列表
基本格式:
格式一: i for i in 可迭代对象
功能:对i执行append操作
格式二: i for i in 可迭代对象 if 条件
功能:如果条件为True,那么才对i执行append操作
1
#推导式:筛选a = [lambda :i*20 for i in range(5) if i > 0 ]#如果i>0,就将lambda语句所代表的函数地址加入到列表中,如果不满足条件,则不加入print(a[0]()) #结果为:40print(a[1]()) #结果为:40
分类:
列表推导式、集合推导式
目的:对一个序列里的所有元素进行相同的操作,然后将操作后的元素加入到一个新列表(集合)中,最终的结果是一个列表(集合)
特点:列表推导式表达式必须要在列表里;结合推导式语句必须要在集合里;
用法:列表推导式和集合推导式用法一样
格式:[ 操作A的语句 for A in 一个范围(例如:range(5) ) ]
操作A的语句可以是函数,表达式,三元运算,lambda表达式
1
#列表推导式:表达式必须在列表里a = [i+10 for i in range(3) ] print(a) #结果为:[10,11,12]#将每一个循环的i进行操作(+10),然后将操作后的结果作为一个元素加入到一个列表中#集合推导式:表达式必须在集合里a = {i+10 for i in range(3) }print(a) #结果为:{10, 11, 12}#将每一个循环的i进行操作(+10),然后将操作后的结果作为一个元素加入到一个集合中
字典推导式
区别:for语句必须要在字典的值的位置,操作A的语句既可以放在键的位置,也可以放在值的位置
功能:将操作后的键值对加入到一个 新字典中
1
#字典推导式:操作语句既可以在键里,也可以在值里a = {i+10 : i for i in range(3) } #每次循环得到的i都交给键和值去操作,然后将操作后的键值对加入到一个新的字典中print(a) #结果为:{10: 0, 11: 1, 12: 2}#每一次循环,将操作后的键和值作为一个键值对加入到一个新字典中
5.12 递归
本质:函数自己调自己
特性:占用内存大,效率低
关键:有返回值
python中默认递归次数:1000次
怎么查看python递归次数:在sys模块中查看
- sys.getrecursionlimit()
5.13 迭代器
功能:遍历可迭代对象(列表、元组这些都是可迭代对象)里的每一个元素
迭代器的标志:
- 有下面这个next方法
1 | __next__ |
- 看它的数据类型,type来查看,如果它的数据类型是 iterator 那么就是迭代器
生成迭代器:
前提:必须是可迭代对象
用iter方法来生成
1
lis = [1,2,3] #可迭代对象print(type(lis)) #结果为:list#方法一:a = iter([1,2,3])a = iter(lis) #生成迭代器#方法二:[1,2,3].__iter__()b = lis.__iter__() #生成迭代器print(type(a)) #结果为:<class 'list_iterator'>print(type(b)) #结果为:<class 'list_iterator'>
可迭代对象:须同时满足下面的条件
- 可以被for循环
- 有iter方法
- 使用iter方法后返回的是一个迭代器
调用迭代器的方法:
- 注意点:next方法可以一个一个的将迭代器里的值取出,但是如果最后没有值可以取,那么程序就会报错,但是之前取出的值还是可以取出来
- 使用next方法来调用
1 | #通过__next__()来调用迭代器,一个一个的内部取值,然后打印,如果没有值了,就报错lis = [1,2,3]b = lis.__iter__() #生成迭代器while True : #循环用next来取值 print(b.__next__()) #结果为:1,2,3 并且报错 |
解决没有值可以取从而报错的方法:
通过异常处理来解决
1
#用异常处理解决无值可取时出现的报错lis = [1,2,3]b = lis.__iter__() #生成迭代器while True : #循环用next来取值 try: print(b.__next__()) except Exception as f : break #如果报错,即所有的值取完了,那么就终止循环#最终结果为:1,2,3 但是并没有报错
for循环和迭代器的关系:
for循环的本质:for 循环一个可迭代对象,其函数内部执行了以下几个步骤:
将可迭代对象转换为迭代器
通过迭代器的方法next来一个一个取值
进行异常处理,避免报错
5.14生成器
5.14.1 生成器基础
标志:只要函数里有yield,那么该函数就是一个生成器
生成器的特性:生成器必须要配合for使用,如果没有for,那么生成器内部的代码不执行
yield的特性:语句从上往下执行,碰到yield就直接将yield后面接的数据返回,返回的数据直接传给了 for 后面的变量,然后接着开始第二轮循环,接着上次执行的位置,接着往下执行
1 | #生成器的标志:yielddef fun(): #有yield的函数就是一个生成器 print('666') yield 1 #返回1,将1传给 i yield 2 #接着上次执行的位置,接着向下执行,所以此时yield 2for i in fun(): print(i) #结果为:666 1 2 |
5.14.2 生成器和迭代器的关系
调用生成器即 上个例子中的 fun() ,会生成一个生成器,即fun()这个整体就是一个生成器
原因:
fun()这个整体是一个生成器,但是它内部有next方法,所以它也是一个特殊的迭代器
fun()这个整体既可以被for循环,并且其还有iter方法,并且其使用iter方法后返回的值有next方法,所以返回的还是一个迭代器,所以fun()这个整体也可以说是一个可迭代对象
易错点:如果一个函数的代码里有yield语句,那么该函数就是生成器,那么 函数名+() 就是一个生成器,所以必须要用for循环才能执行里面的代码
生成器既有生成数据的功能,也有迭代器的功能
.
易错点:函数里第一个语句就是return,后面却有yield语句,那么它是生成器么?
是生成器,只要函数代码里有yield,不管yield能不能执行,yield语句在哪里,该函数就是生成器
1
def fun(): return yield 1 #该语句一辈子都不会执行,但是该函数还是生成器,因为有yield
小练习:用生成器生成1-1000之间的整数
要求:不用range,不用while,只用生成器
1
#生成器生成500以内的数def fun(x): yield x for m in fun(x+1): #因为fun()是生成器,必须要配合for使用 global COUNT #找到全局变量 COUNT += 1 #修改全局变量 print(m) if COUNT == 500 : #1-500范围内的数 returnCOUNT = 1 #设置全局变量,用来设置范围for i in fun(1): print(i)
实际应用:
需求:取大文件的内容,每次取10条,取回来后,每次返回1条
要求:用生成器来做
1
# 需求:取大文件的内容,每次取10条,取回来后,每次返回1条# 要求:用生成器来做import os,timedef read(): #生成器 position_current = 0 status = 1 #代表文本没有读完 while True : with open('生成器测试',mode='r',encoding='utf-8') as f : size = os.stat('生成器测试').st_size #获取文件的大小 f.seek(position_current) #移动光标位置,从上次获取的最后一个位置继续往下获取 message_total = [] #用来储存10条信息 for i in range(10): #循环取10条信息 message_pice = f.readline().strip() #每一条信息去除空格 if position_current == size : #如果光标的位置等于文件的大小,则代表文件读完了 status = 0 #文件读完了,修改开关为读完的状态,并跳出循环 break message_total.append(message_pice) #将获取的每一条信息添加到列表里 position_current = f.tell() #读取完了10条后,获取最后一个光标位置 for message in message_total : yield message #一条一条的返回 time.sleep(0.01) #延迟0.01秒,方便观看结果 if status == 0: #如果开关是读完了的状态,就退出循环,所有文件都读取完毕了 breakfor m in read(): print(m)
生成器调用生成器:
1 | #从当前生成器调用其他的生成器def fun(): ''' 生成器1 :return: ''' yield 1 for m in inner() : #inner()就是一个生成器,所以也需要用for来取值 yield m yield 3def inner(): ''' 生成器2 :return: ''' yield 'sb'for v in fun(): print(v) |
5.14.3 生成器的推导式
基本格式:( i for i in rang(10) )
生成器推导式和列表推导式的区别:
区别:生成器推导式可以保留每一轮循环的数据,而列表推导式则不行
生成器推导式:
特点:可以保留每一轮循环产生的数据
1
#生成器推导式:注意是小括号#特点:可以每一轮产生的不同的数据num = ( lambda : i for i in range(3)) #得到的是一个生成器for m in num: #所以需要配合for来取值,保留了每一个i print(m())#等价于下面的函数fundef fun(): for i in range(3): yield lambda : ifor n in fun(): print(n())
列表推导式:
特点:无法保留数据,数据的最终结果取决于循环结束后的最新的值
1
#列表生成器#特点:无法保留数据,最终的数据取决于最新的数据num = [ lambda : i for i in range(3) ] #得到的是一个列表for m in num: #所以需要配合for来取值,保留了每一个i print(m()) #结果为:2 2 2#等价于下面的函数fundef fun(): a = [] for i in range(3): a.append(lambda : i) return afor n in fun(): print(n()) #最终结果取决于i的最新值,所以结果为:2 2 2
5.15 主文件
定义:就是主程序,程序的入口
标志:main
查看方法:
1 | #执行__name__ 如果结果为__main__ 则代表当前运行的脚本是主文件,如果结果为脚本名,那么则代表该脚本不是主文件#在当前脚本里运行print(__name__) 查看打印的结果,如果结果为:__main__ 则代表当前运行的脚本是主文件#主文件print(__name__) #结果为:__main__'''hello模块代码如下:def p(): print(__name__) #判断hello模块是不是主文件'''#判断模块hello是不是主文件,经过判断:hello不是主文件import hellohello.p() #调用hello模块里的方法p#结果为:hello,所以hello模块不是主文件 |
构造主文件入口:
- 手动输入
1 | #构造主文件入口#如果当前文件是主文件,则执行代码if __name__ == '__main__': pass |
- 快捷输入:pycharm里输入main,然后回车即可自动补全
第六章 模块
6.1 模块和函数
模块和函数的关系:模块就相当于一个py文件,该py文件里有很多同类型的函数
模块的分类:
- 内置模块
- 第三方模块
- 自定义模块
包和文件夹的区别:
包定义:一个文件夹里有个init文件,那么它就是一个包;文件名字如下:
1
__init__.py
如果文件夹里没有init文件,那么它就是一个普通的文件夹
包在python2和python3中的区别:
区别:
python2:需要用到包,包里必须要有init文件,如果没有它就是文件夹,那么就无法在python导入
python3:已经没有包这种说法,因为在python3中,支持从文件夹里导入模块,所以文件夹里不需要放入init文件,也就是不需要包了
意见建议:建议在python3中创建文件夹用以导入模块时,在文件夹里放入init文件,防止程序代码放到python2中运行出错
init文件:
特点:文件具体内容可有可无,但是文件的名字必须要是以下这个名字才行:
1
__init__.py
模块的导入:
相对导入:
- 关键:利用符号 点 来实现
- 缺点:必须要在当前运行脚本的下级目录才行
- 应用场景:当我们导入自定义模块A时,但是A又需要导入其他路径的模块,这时就可以在A里导入模块使用相对导入
普通导入:
特性:模块在导入的时候,模块里面的代码就执行了;比如,如果模块 x.py 里有个语句 print(‘hello!’),那么一旦在脚本文件A里导入该模块,然后直接运行该A,此时A里只有一行代码,就是 import x ,在这种情况下运行A,结果为 hello!
导入方法:
适用场景:
- 如果模块和脚本处于同一级目录,并且需要使用该模块里的很多函数时,可以使用 import 模块
- 其他的场景,统一推荐使用 from 包 import 模块 这种写法
1 | #导入模块:import run #前提:run是一个模块文件;导入os模块import run,x #同时导入两个模块from run import x,y #前提:run是一个包;导入包run里的x和y模块from run.python import x,y #前提:run、python都是一个包; 包run里的包python里的x,y模块#导入模块里的函数from run import x #前提:run是一个模块;导入run模块里的指定函数 xfrom run import * #前提:run是一个模块;导入run模块里的所有函数from run.python import * #前提:run是一个包并且python是一个具体的模块;导入包run里的python模块里的所有函数#特殊情况:run是一个包import run #前提:run是一个包,无论run里有多少个模块,这里都默认导入__init__.py模块from run import * #前提:run是一个包,无论run里有多少个模块,这里都默认导入__init__.py模块里的所有函数 |
疑难点:在导入模块里的一个特定函数时,如果该函数的函数名和我们脚本文件里自定义的函数名相同怎么办?
解决方法:如果函数名相同,就会产生冲突,所以我们需要在导入模块里的函数名处下手,可以将该函数名赋予以一个另外的名字
1
from run import x as y #将有冲突的函数名,传递给y,这样就不会和我们自定义的函数起冲突了def x(): pass
练习题1:
- ```python
脚本所在目录day16/moudle模块所在目录:day16/testimport osimport sysscrip_path = os.path.abspath(‘模块导入2.py’) #脚本所在的绝对路径scrip_path = os.path.dirname(scrip_path) #获取脚本文件所在目录scrip_path = os.path.dirname(scrip_path) #获取脚本文件的上级目录sys.path.append(scrip_path) #将路径添加到解释器找模块的默认路径中,这里添加day16这个路径from day16.test import old #这里from一定要从最顶级目录开始写old.message()1
2
3
4
5
6
7
8
9
10
11
脚本和模块所在位置图片:

练习题2:
```python
#导入当前脚本所在的路径里的test包里的模块oldfrom test import old #test和脚本文件在同一级目录old.message()
脚本和模块所在位置图片:
练习题3:
1 | #导入当前脚本所在的路径里的模块ximport x #x和脚本文件在同一级目录x.message() |
脚本和模块所在位置图片:
调用模块里的函数:
首先导入模块(意思就是导入py文件)
接着调用模块里的函数,用点 ‘.’来表示从属关系,比如:random.randint()表示调用random模块里的randint()函数
1
# import 模块名# 模块名.函数名import randomnum = random.randint(1,10) #调用模块random里的randint()函数随机生成一个1到10之间的整数
6.2 模块
6.2.1 random模块
模块特点:生成随机数
常用函数:
- randint(1,10):随机生成一个1到10之间的整数,包含1和10
- randrange(1,10):随机生成一个1到10之间的整数,不包含10
练习题:
定义一个函数,功能是生成随机验证码,接收的参数为验证码的长度,也可以设置一个默认参数表示长度
1
import randomdef verification_code(length = 7): '''生成随机验证码:默认7位小写字母的组合''' result = '' for i in range(length): number = random.randint(97,122) #随机生成a到z包括z所代表的数字 code = chr(number) #将数字转换为字符 result += code #进行验证码拼接 return resultcode_random = verification_code()print(code_random)
6.2.2 time和datatime模块
模块特点:和时间相关
time模块常用函数:
time():获取从1970年1月1日到现在的时间戳,单位是秒
注意点:时间戳是基于格林尼治的1970年1月1日起算
1
import timedata = time.time()print(data) #结果为:1587782765.8132167
sleep(数字):休息多少秒,sleep(5)表示休息5秒
当前时区的时间和格林林芝时间相比,过去了多少秒:
易错点:当前时区的判定并不根据人所处的实际位置,而是和计算机的时区设置有关,电脑里的时区是可以更改
1
import timeprint(time.timezone) #结果为:-28800秒,也就是我们的时间减去28800秒(8小时),就是格林尼治时间
时间分类:
本地时间
就是当前时区的时间
格林尼治时间
UTC时间:主流使用,相比GMT更精确
GMT时间:和UTC时间差不多,只不过精确度没有UTC搞
datetime模块常用函数:
获取当前时区的时间:
注意点:获取到的数据的类型是:datetime类型
datetime类型时间:
优点:可以直接进行时间的加减,比如加156天,年份和月份会自动变更
缺点:无法写入文本,无法传输,必须要将其转换为字符串类型才行
datetime.datetime.now()
将datetime类型格式化成字符串类型:
字符串类型时间:
优点:可以进行写入,传输等操作
缺点:进行时间的相加,比如加天数,需要人工计算,年份,月份等无法自动变更
a = datetime.datetime.now()
a.strftime(‘%Y-%m-%d-%H-%M-%’)
易错点:除了月m和天d是小写,其余的全部是大写
将字符串类型时间转化为datetime类型时间
语法:datetime.datetime.strptime(待转化的字符串,字符串的格式)
注意点:因为字符串并不是类datetime里面,无法直接使用strptime,所以需要一步一步的调用,所以前面需要两个datetime
易错点:接收的参数里第二个参数,必须是字符串的格式,如果和字符串的格式不一致,将无法转换
1
#字符串时间类型和datetime时间类型互转import datetimea = datetime.datetime.now()b = a.strftime('%Y-%m-%d-%H-%M%S') #将datetime类型转换为字符串类型时间print(b) #结果为:2020-05-04-20-2753c = datetime.datetime.strptime(b,r'%Y-%m-%d-%H-%M%S') #将字符串转换为datetime类型# 易错点:第一个参数为待转化的字符串类型,第二个参数,为字符串类型的基本格式print(c) #2020-05-04 20:27:53
获取UTC时间
1
import datetime#两个时间相差8小时time_current = datetime.datetime.now() #获取本地时间,也就是当前时区时间print(time_current) #结果为:2020-05-04 19:52:49.487337time = datetime.datetime.utcnow() #UTC时间,也就是格林尼治时间print(time) #结果为:2020-05-04 11:51:23.988447
增量时间
适用类型:datetime类型
主要用途:实现datetime类型的加减
参数:days ,hours,miniutes,…
示例:让当前时间 + 7小时
1
import datetimea = datetime.datetime.now() #当前时间b = datetime.timedelta(hours=7) #增量时间c = a + b #当前时间 + 增量时间print(a,c,sep='\n')
获取其他时区的当前时间:
分为这几步:
- 根据时区设置增量时间,这里是 7小时
- 将增量时间和时区函数(datetime.timezone)结合,表示需要的时区
- 将获取当前时间的函数(datetime.datetime.now())与时区结合,即可获取指定时区的当前时间
1
#获取其他时区的当前时间import datetimetime_add = datetime.timedelta(hours=7) #增量时间time_zone7 = datetime.timezone(time_add) #通过增量时间,来表示东7区b = datetime.datetime.now( time_zone7 ) #获取东7区的当前时间print(b) #结果为:2020-05-04 19:14:18.284052+07:00 ,此时我国(东8区),时间为2020-05-04 20:14:18.284052+07:00
时间戳和当前时间(datetime类型)互转
从时间戳转当前时间:对应函数的字面意思,from stamptime(时间戳)
1
#将时间戳转化为当前时间(datetime类型)import datetime,timea = time.time() #结果为:1588595706.230431b = datetime.datetime.fromtimestamp(a) #结果为:2020-05-04 20:35:06.230431print(a,b,sep='\n')
当前时间(datetime类型)转时间戳:
1
#将当前时间(datetime类型)转化为时间戳import datetime,timea = time.time() #结果为:1588595914.3423343b = datetime.datetime.fromtimestamp(a) #结果为:2020-05-04 20:38:34.342334c = datetime.datetime.timestamp(b) #结果为:1588595914.342334print(a,b,c,sep='\n')
6.2.3 hashlib模块
常用函数:
md5():
md5加密原理上无法破解的原因:
- 具有不可逆特性,无法通过加密后的密文还原成原始数据
md5解密实现的原理:
- 通过电脑24小时不间断生成随机字符串,然后将该字符串进行md5加密,并且将该随机字符串和其加密后的密文一起加入到数据库中,形成对应关系,通过此方法累积大量的 字符串–密文 相对应的数据,以后一旦客户需要解密,只需要在数据库中查找该加密密文,如果数据库中有,那么根据数据库中字符串和密文的对应关系,既可以找到字符串,该字符串就是用户提交的密文未加密前的数据。
防止md5数据被解密的方法:
- 原理:尽可能使原始数据复杂,位数更长,那么别人破解起来难度将更难,只要密码够复杂,即可实现理论上的永久无法破解
- 实现方法:通过给md5()函数加参数来实现 ‘加盐’ ,从而增加破解的难度
md5()的参数的特性:
不加参数:加密的数据可以通过网上的解密平台解密
1
#未加‘盐’的md5加密:可被破解def encryption_md5(message): '''对传递来的数据进行md5加密''' import hashlib #导入加密模块 data = hashlib.md5() #调用md5()函数 data.update(message.encode('utf-8')) #因为python3的编码是unicode,但是hash的特性是必须要utf-8编码才行 # 所以这里将其编码成utf-8 return data.hexdigest() #返回16进制的字符串值pwd = '123456'result = encryption_md5(pwd)print(result) #e10adc3949ba59abbe56e057f20f883e
测试图:
加参数:则无法解密,参数越长,越难解密
1
#加‘盐’后的md5加密:‘盐’越复杂,越不可被破解def encryption_md5(message): '''对传递来的数据进行md5加密:加强版(不易被解密)''' import hashlib #导入加密模块 data = hashlib.md5('这是一个盐,是为了防止解密,也需要转换为utf-8编码'.encode('utf-8')) #有参数,则代表加‘盐’,可以防破解,这里也要进行编码转换 data.update(message.encode('utf-8')) #因为python3的编码是unicode,但是hash的特性是必须要utf-8编码才行 # 所以这里将其编码成utf-8 return data.hexdigest() #返回16进制的字符串值pwd = '123456'result = encryption_md5(pwd)print(result) #d821098c282d578fbfb647b9ef67460a
测试图:
总结:
- 调用md5加盐或者不加盐,如果加盐需要将加盐的字符串编码转换为utf-8
- 对需要加密的数据进行加密,加密前需要将数据转换为utf-8编码
- 以特定的格式获取数据,一般是16进制的形式获取数据
易错点:
- 导入的模块时hashlib模块,而不是md5模块
- 不管是加的盐还是需要加密的数据,都必须要转换为utf-8编码
- 加密时的函数update需要自己手动打,在pycharm里输入首字母无法显示出该函数,需要自己输入
- 以特定格式显示加密后的密文的函数,必须要是16进制显示
6.2.4 getpass模块
模块特点:密码相关
函数:
getpass():让我们输入的密码不显示
适用场景:输密码时旁边有人.
缺点:在windows上使用,必须要用dos命令行打开才能运行,用pycharm都无法正常运行
实际应用场景:linux系统,因为linux主要是命令行界面
1
'''运行本程序注意点:必须使用dos打开才能正常运行 右键---Open in Terminal缺点:在windows上使用有问题,无法正常使用使用地点:在命令窗口(dos,cmd命令窗口)才能使用实际应用场景:linux系统中,因为linux主要是命令行界面,所以linux可以正常使用'''#导入getpass模块import getpassgetpass.getpass() #调用函数,不加参数默认提示语为:Password: (只有在命令行界面才会显示)getpass.getpass(prompt='请输入密码:') #修改提示语pwd = getpass.getpass('请输入密码:') #将获取的密码赋值给pwdprint(pwd)
6.2.5 sys模块
模块特点:和解释器相关
常用函数:
查看一个数据被多少个变量指向:
1
import sysa = [1,2]print(sys.getrefcount(a)) #查找指向[1,2]的变量个数,因为调用本函数,相当于也是一次指向,所以a和本函数一共2次指向
查看递归的上限:
1
#必须要在命令行窗口运行才会显示import syssys.getrecursionlimit()
修改默认递归的次数:
1
sys.setrecursionlimit()
在执行脚本前,接收参数:sys.argv()
前提:在命令行界面执行
功能:
- 接收的第一个参数脚本名(脚本名始终是该列表的第一个元素)
- 接收的参数都在一个列表里
1
#命令窗口执行:python 测试专用.py 狗屎import sys a = sys.argv #将接受来的参数赋值给变量a,此时a是一个变量print(a) #结果为:['测试专用.py', '狗屎'],第一个是脚本名,第二个是传入的参数
退出程序
1
#直接退出程序:后续代码不再执行import sys #导入模块sys.exit() #退出程序,后面的代码不会被执行print(10) #该行代码不会被执行
查看解释器查找模块的默认路径:
1 | #查看找模块的默认路径import sysprint(sys.path) |
修改解释器找模块的默认路径:
1 | #新增解释器找模块的路径import syssys.path.append('路径') #参数必须是一个路径 |
6.2.6 os模块
模块特点:和操作系统相关
常用函数:
获取文件大小
1
import os size = os.stat('gitee作业地址.md').st_size #读取文件大小,读取的是byteprint(size)
判断路径是否存在
1
#判断路径是否存在import os status = os.path.exists('E:\oldboy_python\code\测试.txt')print(status)
获取文件的绝对路径
1
#获取绝对路径import osstatus = os.path.abspath('测试.txt') #获取测试.txt所在的绝对路径print(status) #结果为:E:\oldboy_python\code\测试.txt
获取路径的上层目录:
1
#获取路径的上层目录import osstatus = os.path.dirname('E:\oldboy_python\code\测试.txt') #获取上层目录print(status) #结果为:E:\oldboy_python\code
路径拼接
易错点:第二个参数如果也为路径,可能会对对一个参数的路径进行覆盖
1
#路径拼接import osstatus = os.path.join(r'E:\oldboy_python','狗屎') #第一个参数为路径,第二个参数为待拼接字符串print(status) #结果为:E:\oldboy_python\狗屎
查看文件夹下的所有文件(当前第一层文件夹)
1
#查看文件夹下的所有文件(层级一)import osstatus = os.listdir(r'E:\oldboy_python') #列出文件夹里的所有东西,当前层级的目录和文件#不加参数,默认列出当前脚本所在目录的文件夹的所有文件和文件夹print(status) #结果为:['.git', 'code', 'upload', '尹成code', '思维导图', '数据类型方法.md', '老男孩python笔记.md']
查看文件夹下所有文件(所有层级):walk
特点:
不用for循环进行遍历,则不抓取数据,所以必须要搭配for循环使用
walk获得的是一个元组,总共有多少层文件夹,就有多少个元素
列表里的每个元素3部分组成:
第一部分:当前文件夹的绝对路径
第二部分:当前文件夹下的所有文件夹名字,放在一个列表里,如果当前文件夹下没有文件夹,那么该列表为[]
第三部分:当前文件夹下所有的文件的名字,放在一个列表里
1
#查看文件夹下每一层文件夹里的数据import osstatus = os.walk(r'E:\手机') #只是抓取,不配合for遍历,就不显示for i in status: #遍历每一个元素:相当于i就是每一层文件夹里的数据:包含该层文件夹的绝对路径,该层文件夹下的所有文件夹名,该层文件夹下的所有文件 print(i) #打印每一层文件夹里的数据#结果为第一层文件夹:('E:\\手机', ['w大'], ['小米手机助手3.2.1.zip'])#结果为第二层文件夹:('E:\\手机\\w大', [], ['miui11稳定版(W大).zip', '安全中心.rar'])
练习:显示目录A里的目录B里的所有文件(文件名要和绝对路径拼接起来)
1
#写法一:推荐,直接在for循环的变量里对三个元素进行组合#查看目录下每一层目录里的数据import osstatus = os.walk(r'E:\手机')for path_abs,folder_list,file_list in status: #将status的每一个元素A里的三个元素分别赋值给path_abs,folder_list,file_list for file in file_list: #遍历所有的文件名 result = os.path.join(path_abs,file) #文件名和当前文件所在的绝对路径进行拼接 print(result)#写法二:不推荐,这时笨办法,多了一条中转语句# #查看目录下每一层目录里的数据# import os# status = os.walk(r'E:\手机')# for i in status:# path_abs,folder,file = i #中转语句# for m in file: #遍历所有的文件名# result = os.path.join(path_abs,m)# print(result)
创建一级目录
特点:只能创建一层目录
易错点:如果创建多层目录,会报错;如果目录不存在,则报错
1
#正确写法:创建一级目录import osos.mkdir('一级目录') #创建一级目录#错误写法:创建多级目录:报错import osos.mkdir('一级目录\二级目录') #报错:FileNotFoundError: [WinError 3] 系统找不到指定的路径。: '一级目录\\二级目录'
创建多级目录(推荐):以后不管创建一级还是多级,都推荐这个命令
特点:既可以创建一层的目录,也可以创建多层目录
易错点:;如果目录不存在,则报错
使用技巧:创建目录前,得先判定目录存不存在,不存在,就创建,这样就可以避免错误。创建目录方法:os.path.exists(路径)
1
#创建多级目录import osos.makedirs('一级目录\二级目录\三级目录') #创建多级目录
目录重命名(一级)
接收参数:2个;第一个是原目录名称,第二个是修改后的目录名称
用法:os.rename()
注意点:如果对多级目录重命名,该语句就只对第一层的目录进行重命名
1
import osos.rename('一级目录','一级目录重命名') #目录重命名
目录重命名(多级)
接收参数:2个;第一个是原目录名称,第二个是修改后的目录名称
用法:os.renames()
注意点:既可以对一级目录重命名,也可以对多级目录重命名
1
#多级目录重命名import osos.renames('一级目录\二级目录\三级目录','一级目录重命名\二级目录重命名\三级目录重命名') #目录重命名
6.2.7 shutil模块
模块特点:文件操作相关
常用函数:
删除文件夹(删除文件夹和里面的文件夹、文件)
1
#删除文件夹(包括内部的文件夹、文件)import shutil,os,timeos.mkdir('删除文件夹测试') #创建文件夹time.sleep(5) #延迟5秒,方便直观的观察文件夹的创建和删除shutil.rmtree(r'删除文件夹测试') #删除文件夹
重命名
对文件夹重命名
1
#对文件夹重命名import shutilshutil.move('./day16/重命名','./day16/重命名成功')
对文件进行重命名
1
#对文件进行重命名import shutilshutil.move('./day16/重命名成功/1','./day16/重命名成功/6')
压缩
- 参数3个:第一个参数为压缩后的名字,第二个参数为压缩的格式,第三个参数为待压缩的路径
- 易错点:如果第一个参数是一个路径,比如 test/a 那么,当我们压缩的时候,实际上就是将一个东西压缩,并切压缩后的压缩包为 a.zip,并且该压缩包还在test文件夹下,具体应用实例,可以参考练习题:基于时间的解压缩
1
import shutil#压缩文件:第一个参数时待压缩路径,第二个参数时压缩格式,第三个参数为待压缩的路径shutil.make_archive('重命名','zip','重命名成功') #因为重命名成功这个文件夹和脚本处于同一目录下,所以这里路径只写目录名字也可以
解压
- 第一个参数必须要有,第二个参数不指定,则默认解压到当前目录,如果指定目录不存在,则自动创建
1
import shutil#解压:不指定解压路径就默认解压到当前目录shutil.unpack_archive('重命名成功.zip')#解压:第二个参数可以指定解压路径,如果解压路径目录不存在,则自动创建shutil.unpack_archive('重命名成功.zip','解压')
练习题:压缩与解压
要求:文件夹day16下有文件夹module,现在需要基于时间,对moudle文件夹进行压缩,压缩文件到文件夹到B3里,B3不存在,然后再将压缩包解压到‘E:\解压测试 内’
1
'''要求:文件夹day16下有文件夹module,现在需要基于时间对moudle文件夹进行压缩,压缩文件到文件夹到B3里,B3不存在,然后再讲压缩包解压到‘E:\解压测试’'''import datetime,shutil,ostime_now = datetime.datetime.now() #获取当前时间time_now = time_now.strftime('%Y-%m-%d-%H-%M-%S') #将时间格式化成指定样式if not os.path.exists('B3') : #必须要判断 os.mkdir('B3')path = os.path.join('B3',time_now) #新的路径,类似于B3/time_nowshutil.make_archive(path,'zip','moudle') #前提,B3目录得存在对moudle进行压缩,压缩后的文件在B3下的一个时间命名的压缩包file = path + '.zip' #压缩包的名字shutil.unpack_archive(file,'E:\解压测试')
6.2.8 json、pickle模块
推荐:推荐使用json
功能:实现不同编程语言数据的转换
json的意义:实现了不同编程语言数据的交换,如果需要将python的数据给java使用,那么就需要首先将python的数据转换为json格式,然后再将json格式数据转换为java格式的数据,json相当于一个中间人
json的特点:
相当于一个字符串
最外层必须是列表或者字典,内部由字符串、列表、字典构成
注意点:
json序列中不存在元组,元组会自动转换为列表;
json序列中不能有集合,否则会报错
易错点:python内容里字符串使用的引号必须要使用双引号
- 原因:大多数编程语言表示字符串都是用双引号,所以json规定表示字符串必须用双引号,否则容易导致不可预知的错误
json和pickle的区别:
json:
特点:生成的是一个字符串类型
缺点:只能转换字符串、列表、字典、整型、布尔等类型
优点:支持所有语言,生成的是字符串类型,便于阅读
pickle:
特点:生成的是一个bytes类型
缺点:只支持python,只有在python中才有该模块;生成的数据是bytes类型,不便于阅读
优点:支持所有的数据类型,出了json支持的类型,还支持函数等数据类型
常用函数:
将数据转换为json格式:json.dumps()
易错点:区分和dump的区别,dumps仅仅做数据转换,并没有保存数据;dump还需要接收第二个参数,文件操作的函数,这样转换完的数据就直接存储到文件里了
1
#dumps:将数据转换为json格式的字符串,无保存import jsona = {"测试":(1,2,{"id":66},"daf")}result = json.dumps(a)print(result) #结果为:{"\u6d4b\u8bd5": [1, 2, {"id": 66}, "daf"]}pyt
注意点:将数据转换为json格式,如果数据里有汉字,那么汉字会被转化为其他编码,这时,我们看json数据就不能很直观的看到汉字的内容
解决方法:添加参数ensure_ascii = False
1
import json#不加参数a = ['你好',3,'66']b = json.dumps(a)print(b) #结果为:["\u4f60\u597d", 3, "66"]#加参数:更直观的观察json里的汉字c = json.dumps(a,ensure_ascii=False)print(c) #结果为:["你好", 3, "66"]
将json转换为python格式:json.loads()
易错点:区分和load的区别,loads仅仅做数据转换,并没有保存数据;load还需要接收第二个参数,文件操作的函数,这样转换完的数据就直接存储到文件里了
1
#dumps:将数据转换为json格式的字符串,无保存a = {"测试":(1,2,{"id":66},"daf")}result = json.dumps(a)print(result) #结果为:{"\u6d4b\u8bd5": [1, 2, {"id": 66}, "daf"]}#loads:将json格式数据转换为python数据;无保存import jsonmessage = json.loads(result) #将json格式数据转换为python数据print(message) #{'测试': [1, 2, {'id': 66}, 'daf']}
6.3 第三方模块
6.3.1 安装第三方模块
两种方法:
pip install 模块名
在命令行窗口执行该命令来安装
源码安装
前提:下载源码包,然后解压,然后执行step.py
python学习笔记(老男孩s21)
——————————-by:发飙的登山包——————————————
第七章 面向对象
ps:从此时开始,以后都用面向对象来写代码
7.1 类和面向对象
7.1.1 类
类和其方法的特性:类和方法都可以作为变量使用
应用场景:
- 函数多,需要分类
- 需要封装数据
封装思想:
- 可以通过模块来封装,将同一类的函数封装到同一个模块(自定义模块)里
- 可以通过类来对函数来进行封装
基本格式:
1 | #命名类的基本格式:inner是类里面的方法 |
类的命名规则:
- 一个单词时:只需要首字母大写
- 两个单词时:2个单词的首字母都要大写
类的初始化:
需要用初始化方法init
1
2
3
4
5#类的初始化
class Fun(object):
def __init__(self): #特性:不需要对象的调用就会自动运行,这里一般用来封装数据
print('666')
obj = Fun() #在示例一个对象的时候,初始化函数init就已经自动运行了,不需要调用类初始化方法的特性和作用:
特性:不需要调用,在实例化一个对象的时候就会自动运行
作用:主要是用给每一个对象来封装数据的
易错点:必须要有self这个形参
读取和修改对象里的数据:
读取数据基本格式: 对象.变量名
修改数据基本格式: 对象.变量名 = ‘xxxx’
1
2
3
4
5
6
7
8
9
10
11
12class Fun(object):
def __init__(self,name,age):
self.name = name
self.age = age
obj = Fun('张三','26') #封装数据,将这两个实参传递给初始化函数里的变量name,age
#读取对象里的数据:对象.变量
print(obj.name) #读取对象里的数据,结果为:张三
print(obj.age) #读取对象里的数据,结果为:26
#修改对象里的数据
obj.name = '李四' #将张三修改为了李四
print(obj.name)
方法和函数的区别:
- 只有类里面的函数才叫做方法,其余的都叫函数
易错点:
- 类里面的方法必须要有形参 self
怎么调用类里面的方法?
根据类生成对象
通过对象来调用类里面的方法
1
2
3
4
5class Fun(objet):
def inner(self):
print('666')
obj = Fun() #创建一个对象obj
obj.inner() #通过对象来调用Fun类里的方法 inner
7.1.2 面向对象
面向对象的三大特征:
- 封装
- 继承
- 多态
对象的作用:封装数据,方便以后使用这些数据
对象的本质:就是封装内容的
对象的特性:一个类可以生成很多对象,这些对象都是独立的,如果封装数据的话,每个里面封装的数据都可以是不同的,每个对象都可以用类里面的方法
什么时候适合用面向对象?
- 函数比较多需要归类时
- 需要将数据封装到不同的对象里,方便下次调用时
7.1.3 类成员
分类与使用:
实例变量(对象变量):就是通过init初始化函数封装的变量
易错点:在通过对象(实例)来取类变量时,如果实例(对象)里也有同名的变量,那么最终取到的是对象(实例)变量,而不是类变量
类变量
取类变量:
python中:
通过对象取:优先取对象里的变量,如果对象里没有才去类里取,如果类里没有,就去改类的基类里取
- 前提:对象中没有封装相同的变量,如果封装了相同的变量num,那么最终取到的结果是对象里的变量,而不是类变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#通过对象来取类变量:对象里没有封装同名的变量
class Foo(object):
num = 1
def echo(self):
print('欢迎!')
obj = Foo()
print(obj.num) #通过对象取类变量num,结果为:1
#通过对象来取类变量:对象里有相同的变量,最终取到的是对象变量
class Foo(object):
num = 1
def __init__(self,num):
self.num = num
def echo(self):
print('欢迎!')
obj = Foo(5)
print(obj.num) #取到是对象变量,结果为:5通过类来取:如果当前类没有,则去它的基类里取,只能向上,而不能向下(朝对象找)
格式: 类 +变量名
易错点:只能取类变量,无法取到init里的变量,因为init里是对象变量
1
2
3
4
5
6
7
8#通过类来取类变量:只能取到类变量,无法取到对象变量
class Foo(object):
def __init__(self,num):
self.num = num
num = 1 #取到的是这里,init里封装的是对象变量
def echo(self):
print('欢迎!')
print(Foo.num) #取到是类变量,结果为:1其他语言中:只能通过对象来取变量
绑定方法
特点:参数里必须要有self,默认的都是绑定方法
执行:先实例化一个对象,然后通过 对象 + 方法 + () 来进行调用
易错点:仅仅只能通过对象来调用方法,因为绑定方法绑定的就是对象,所以不能脱离对象来调用
静态方法
特点:需要用到一个装饰器,masticmethod
作用:避免不必要的实例化对象,更节约资源,形式上省掉了self
执行:
- 可以通过类来调用方法(推荐)
- 也可以通过对象来调用方法(不推荐,因为在其他语言中,都仅仅只能通过类来调用方法,而无法通过对象调用方法)
易错点:既可以通过类来调用方法,也可以通过对象来调用,推荐使用类来调用静态方法
1
2
3
4
5
6
7
8
9
10
11
12#绑定方法:必须要有形参self
#静态方法:不需要形参self
class Fun(object):
def inner(self):
print('这里必须要有self形参,所以它是绑定方法')
#通过该装饰器生成静态方法
def exirse(): #这里不需要self
print('这里通过装饰器,所以不需要self')
obj = Fun()
obj.inner() #因为inner是绑定方法,只能通过其绑定的对象来调用
Fun.exirse() #exirse是静态方法,所以可以通过类来调用方法类方法
特点:需要用到一个装饰器,classmethod
参数:必须要有一个cls形参
作用:传递类的名字到方法中,方便使用
本质:静态方法的用法基本一致,只不过类方法必须要有cls形参,而静态方法没有参数限制,如果要传递类名,则使用类方法
执行:
- 通过类调用方法(推荐)
- 通过对象调用方法
1
2
3
4
5
6
7
8
9
10
11#类方法:传递类名,必须要有形参cls,用到装饰器classmethod
class Fun(object):
def exirse(cls): #必须要有形参cls
print('类名是:{}'.format(cls)) #打印外部传递过来的类名
obj = Fun
#不推荐:通过对象调用类方法
obj.exirse() #传递类名Fun到方法exirse中,结果为:类名是:<class '__main__.Fun'>
#推荐:通过类来调用类方法
Fun.exirse() #传递类名Fun到方法exirse中,结果为:类名是:<class '__main__.Fun'>属性
本质:去掉通过对象调用方法时后面的括号,使代码更简洁美观
特点:需要用到装饰器 property
前提:因为属性只适合通过对象调用的方法,所以被调用的方法的形参里必须要有self,并且有且只能有self这个形参,因为去掉了括号,也就是无法传递实参,所以形参里也只能有self这个形参
易错点:形参必须有且只能有self这唯一一个形参
1
#属性:去掉通过对象调用方法时后面的括号class Fun(object): @property #属性需要用到的装饰器 def exirse(self): #因为是通过对象调用,所以必须要有self print('属性的作用就是去掉通过对象调用方法时后面的括号')obj = Fun() #必须通过对象调用方法才能去掉括号#去掉了调用方法时的括号obj.exirse #结果为:属性的作用就是去掉通过对象调用方法时后面的括号
修改类里的变量:
通过对象:
特点:只能修改对象里的变量,无法修改类变量
原因:因为想要修改类变量,首先需要对象变量和类变量不重名,但是在这种情况下,一旦我们通过赋值语句来修改类变量,实际并没有修改,而是在对象里创建了一个和类变量同名的一个变量
易错点:如果当前对象里没有,那么修改语句(赋值语句)就相当于在该对象里创建了一个变量
通过类:
特点:只能修改当前类变量,无法修改基类里的类变量
原因:因为想要修改基类里的变量,首先需要对象变量和类变量不重名(也就是当前类里没有待修改的这个变量),但是在这种情况下,一旦我们通过赋值语句来修改类变量,实际并没有修改,而是在对当前类里创建了一个和基类里的类变量同名的一个变量
易错点:如果当前类里没有,那么修改语句(赋值语句)就相当于在该类里创建了一个变量
1
class Foo(object): num = 1 #取到的是这里,init里封装的是对象变量 def echo(self): print('欢迎!')obj = Foo()obj.num = 50 #并没有修改类变量,而是在对象里创建了一个变量numprint(obj.num,Foo.num) #结果为:50 1
取变量、修改变量总结:
- 不管是通过类还是对象,对自己的上一级(对象的上一级就是类,类的上一级就是基类)都只能读取变量,而无法修改,这时基于待查找数据在当前类或者对象里没有的情况下。但是如果带查找数据在当前类或者对象里存在,那么就优先查找当前类或者对象
- 不管通过对象或者类来修改变量,都只能修改当前自己所在的对象或者类,无法修改其上一级
小练习:
1 | #修改类里的变量:在有继承关系的前提下class Fun(object): x = 1class Foo(Fun): passobj = Foo() #实例化一个对象print(obj.x ) #通过对象变量,对象里没有,Foo类里没有,取其基类Fun找,获取到x为1print(Foo.x) #通过类Foo取变量x,当前没有,去其基类Fun找,获取到x为1obj.x = 10 #修改对象里的变量x,没有,就在对象里创建变量x,x为10print(obj.x ) #结果为:10Foo.x = 20 #修改类Foo的类变量x,没有,则在当前类里创建类变量x,x为20print(Foo.x) #结果为:20 |
绑定方法、静态方法总结:
- 绑定方法必须要有形参self,而静态方法不需要
- 绑定方法的执行方式必须通过对象来调用方法;而静态方法既可以通过对象调用,也可以通过类调用,推荐使用类调用
- 静态方法因为不需要用到对象,所以更节约资源
- 因为区别就是一个self的有无问题,而self和对象本身有关,所以如果一个方法后续会用到对象封装的数据,那么就用绑定方法;如果后续不需要用到对象封装的数据,那么就退浆使用静态方法。
类成员总结
- 类变量
- 实例变量
- 绑定方法
- 静态方法
- 类方法
- 属性
其中,类变量是和类里面的方法代码区间并列的变量;实例变量是对象里封装的变量;而绑定方法的特点就是带有形参self,并且只能通过对象来调用;静态方法就是相当于去掉了self,既可以通过对象来调用,也可以通过类来调用,类方法和静态方法大致相同,只是静态方法的形参没有限制,而类方法必须要有形参cls,用来传递类名;属性主要是用来去掉通过对象调用方法时后面的那个括号,被调用的方法有且只有一个形参self
推荐:在既能通过类来调用方法,又能通过对象来调用方法的前提下,建议使用类来调用方法
7.1.4 成员修饰符
共有:
- 特点:所有人都可以访问
私有:
适用范围:适合实例变量、类里的变量、方法
特点:并不是所有人都可以访问,通过类或者对象调用方法或者变量时,提示找不到方法:AttributeError: type object ‘Fun’ has no attribute ‘__inner’
标志: _ ‘+’ _ ‘+’ 方法名
1 | #私有方法:通过对象调用方法提示方法不存在class Fun(object): def __exirse(self): print('私有方法的标志: __方法名')obj = Fun()obj.__exirse()#私有方法:通过类调用方法提示方法不存在class Fun(object): @staticmethod def __inner(): print('私有方法的标志: __方法名')Fun.__inner() |
强制访问私有:
中转法:通过共有方法来实现访问私有方法。在共有方法里调用私有方法,当我们调用共有方法时,就可以将私有方法成功调用了。
适用范围:同一个类中,当共有方法和私有方法在同一个类中,我们可以在该共有方法中构造调用私有方法的语句来实现访问私有方法,但是如果该公有方法和私有方法不在同一个类中,那么就不是用于中转法。哪怕这两个类的关系是基类和派生类的关系也不行。
易错点:该公有方法和私有方法必须在同一个类中
1
#通过共有方法访问私有方法class Fun(object): def exirse(self): #共有方法 print('私有方法的标志: __方法名') self.__inner() #共有方法内部调用私有方法,可以正常调用 @staticmethod def __inner(): #私有方法 print('私有方法的标志: __方法名')obj = Fun() obj.exirse()
推荐方法:在调用时,直接在调用语句里加上该方法所在的类名即可
格式 : 对象._类名__变量名或者方法名
易错点:必须要通过对象才能使用该方法
1
#强制访问私有变量:class Fun(object): __x = 50 def __inner(): #私有方法 print('私有方法的标志: __方法名')obj = Fun()#通过 _类名__变量名或者方法 来实现强制访问私有变量或者方法print(obj._Fun__x) #结果为:50
7.2 继承
基本格式:
1 | #继承class Fun(object): #代表累Fun继承object,Fun是派生类(子类),object是基类(父类) pass |
继承的两个类的名称:
父类和子类
或者基类和派生类
继承在python2和python3中的区别:
- python2定义类,类名后面的括号里必有object,代表继承object类,这样的类叫做新式类;如果类名后面没有括号,即没有继承object类,那么该类就是经典类(落后的类)
- python3定义类,不需要在后面括号里写object,因为python3默认继承object类,所以python3中所有的类都是新式类(推荐)
面向对象中继承的意义:
- 当多个类有共同的方法A时,我们不可能在每个类中都写上该方法A,毕竟如果类的数量多,比如几百上千个,那么这样就很费资源,所以继承的意义就体现出来了,多个类继承同一个基类,那么只需要基类中有该方法A,那么其下面的所有派生类都可以使用该方法A
在继承关系中,怎么查找方法?
核心原则:优先在当前类中找,如果当前类没有,就去基类找
根本原则:
判断对象self到底是谁? self会传递,但是要把握它的根本
对象由谁创建的? 谁创建的,就优先从创建它的类开始找
1
#继承示例一:class Fun(object): #代表累Fun继承object def inner(self): print('666')class Fun1(Fun): #继承Fun类 def inner(self): print(111) def ceshi(self): print('狗屎')obj = Fun1() #创建了一个对象#这里obj由Fun1创建,所以优先在创建它的类开始找obj.inner() #结果为:111obj.ceshi() #结果为:狗屎#继承:示例二:class father(object): def ceshi(self): print('你好')class Fun(father): #代表累Fun继承object def inner(self): #Fun1里没有inner,所以找到了这里,但是这里的self实际上是obj,所以self.ceshi()优先取创建self也就是obj的类里去找 self.ceshi() def ceshi(self): print('张三是傻逼')class Fun1(Fun): #继承Fun类 def ceshi(self): print('狗屎')obj = Fun1() #创建了一个对象obj.inner() #结果为:狗屎'''1、Fun1里没有inner方法,所以去它的基类Fun里去找2、找到了,执行inner,但是inner又调用方法ceshi,但是ceshi在好几个类里都有3、判断inner里的self到底是谁?由谁创建?这里self实际是obj,由Fun1创建,所以找ceshi方法从类Fun1开始找4、找到了,执行测试,结果为 狗屎'''#继承:示例三:class Base1: def f1(self): print('base1.f1') def f2(self): print('base1.f2') def f3(self): print('base1.f3') self.f1()class Base2: def f1(self): print('base2.f1')class Foo(Base1, Base2): def f0(self): print('foo.f0') self.f3()obj = Foo() #对象由Foo创建obj.f0()'''1、obj由Foo类创建2、查找F0,打印foo.f03、查找f3,但是Foo中没有,所以去它的父类, 但是这里有两个父类,所以先去左边的父类Base1找4、找到f3,打印 base1.f3 ,并且查找f15、由于这里的self本质上就是obj,obj由Foo创建,所以先从Foo找f16、Foo里没有f1,所以去它的左边的父类Base1找7、找到f1,打印base1.f1所以最终结果为:foo.f0base1.f3 base1.f1'''#继承:示例四:class Base: def f1(self): print('base.f1') def f3(self): self.f1() print('base.f3')class Foo(Base): def f1(self): print('foo.f1') def f2(self): print('foo.f2') self.f3()obj = Foo() #实例化一个对象obj.f2()'''1、obj由Foo创建,去Foo里找f2,找到,打印foo.f2 ,并查找f32、Foo里没有f3,去它的基类Base找3、找到f3,接着查找f1,此时需要判断,self到底是谁?由谁创建4、结合实际,这里self是obj,由Foo创建,所以来Foo查找f15、找到f1,打印foo.f16、执行完毕,回到f3中接着往下执行,打印base.f3所以最终结果为:foo.f2 foo.f1base.f3'''
继承多个类怎么找方法?
先从当前类开始找,如果没有,就去括号里的最左边的基类A里找,如果A里也没有,就去B里找,依次往下找下去
1
class Fun(A,B): #Fun里没有就去A里找,A里没有就去B里找 pass
7.3 多态
别名:鸭子模型
- 一个鸭场,接收别人送过来的所有种类的鸭子,但是在喂养的时候,鸭场负责人并不是所有鸭子都喂食,只有那种会呱呱叫的鸭子才给他们喂食物,这就是一种多态
特点:
- 接收多种数据类型
- 但是是有满足某个特定的方法才会有效(呱呱叫)
第八章 网络编程
第九章 并发编程
第十章 数据库
第十一章 前端开发
第十二章 Django框架
附录1 python2和python3区别
1.1.1 默认编码
py2:
ascii编码
py3:
unicode编码
解决办法:添加文件头
1
# -*- coding:utf-8 -*-
1.1.2 整型和除法
整型
py2:
int,long
py3:
int
除法
py2:
除法不保留小数位,例如5/2=2
py3:
除法后的结果有小数位,5/2=2.5
解决办法
在py2脚本文件里导入py3的除法模块即可,除法的英文单词是(division)
1
from _future_ import division
1.1.3 输入和输入
输入:
py2:
- raw_input()
py3:
- input()
输出:
py2:
- print ‘’
py3:
- print(‘’)
1.1.4 迭代器
字典的values,keys,items:
py2:
values(),keys(),items():直接生成数据,开始占用内存,不需要配合for
py3:
values(),keys(),items():生成的是一个迭代器,必须要配合for才能取出里面的数据
filter,map函数
py2:
直接生成数据,立刻开始占用内存,不需要配合for
py3:
生成的是一个迭代器,必须要配合for才能取出里面的数据
1.1.5 最大的区别:str,unicode
python2:
有str类型,unicode数据类型,这里的str相当于就是python3中的字节类型bytes,这里的unicode类型就是相当于py3中的str类型(都是unicode编码)
易错点:这里的unicode是一个数据类型
python3:
- 有str类型,bytes类型,py3中,str类型是unicode编码(一般用于内存中),bytes是非unicode编码的其他编码后的数据类型(以b开头,一般用于数据传输、存储)
1.1.6 类的区别
python2:
- 定义类时,类名后面的括号里必须要写上object,代表继承object类,同时该类被称为新式类;如果定义类,类名后面没有括号,那么这种类就是经典类(落后的类)
python3:
- 类名后面的括号里不强制必须写object,可写可不写,因为python3中,默认继承object类,所以可以不写,所以python3中所有的类都是新式类(推荐)
推荐:推荐新式类,同时推荐每次定义类时,括号里都写上object,这样即使在python2中运行代码也不会出错
附录2 python错题
1.git上传码云,提示无权限
第一步:在git终端上生成sshkey
如果之前生成过,再输入该命令会提示是否覆盖,此时需要输入y才能覆盖,如果不输入,则不覆盖:ssh-keygen -t rsa -C “7403103+python_road@user.noreply.gitee.com“
查看生成的ssh秘钥:cat ~/.ssh/id_rsa.pub
第二步:在码云个人设置里—ssh公钥—新增公钥—-名称建议输入和项目名,公钥输入刚才复制的
第三步:回到git终端输入: ssh -T git@gitee.com 显示下图界面就代表成功了
2 .为特定脚本更改解释器发现只有一个解释器
解决方法
3.使用%格式化
格式化‘%’这个字符需要使用两个%,即 %%
易错点:
用%接收参数时,必须要用逗号进行结束
1
guess = '今天有50%%的可能%s'print(guess%('下雨',) ) #逗号一定不能少
4.pycharm中选择Run File in python control后怎么恢复?
顶部工具栏Run—Run alt + shift + F10 —Edit Configuration — 取消勾选
5.利用步长实现倒着打印
如果步长为负数,则切片就是从最后一个元素往第一个元素切片,如果没写步长,则默认是1
易错点:
步长忘记写了,必须要写成负数
6.步长是从当前索引开始的
1 | a = 'hello'b = a[::2] print(b) #结果为hlo,并不是elo |
7.查找字符所在的索引位置:find( )
易错点:该方法是字符串的专属方法,列表、元组都无法使用
8.查找字符在字符串中出现的次数:count()
易错点:不仅适用于字符串,也适用于列表、元组
1
#字符串使用counttext = '好,狗屎,好了,好'index = text.count('好') #查看好在列表中出现的次数print(index) #结果为3#元组中使用counttext = ('好','狗屎','好了','好')index = text.count('好') #查看好在元组中出现的次数print(index) #结果为2#列表中使用counttext = ['好','狗屎','好了','好']index = text.count('好') #查看好在列表中出现的次数print(index) #结果为2
9.循环输入时错误的将输入语句放到了循环外
1 | age = int( input('请猜测我的年龄:') )count = 0while count < 3 : if age == 26 : print('恭喜你猜对了!') break else : print('猜测错误!') count += 1#结果是'''请猜测我的年龄:69猜测错误!猜测错误!猜测错误!'''#正确做法,因为是循环输入,所以必须要将输入语句放到循环里才行count = 0while count < 3 : age = int(input('请猜测我的年龄:')) if age == 26 : print('恭喜你猜对了!') break else : print('猜测错误!') count += 1 |
10.字符串方法replace,不填数字默认全部替换
易错点:不填数字是默认全部替换,而不是只替换一处。
11.切片中括号下标的范围
a = ‘abcdef’,
a[0:5]:这里包含索引0所对应的值,但是不包含索引5对应的值,所以这里的结果是:abcde
1 | name = "abc"print(name[-2:-1]) ##错误语句,结果为b,不包含-1这个索引的值,所以错误print(name[-2:]) #正确语句,结果为bc |
12.find()和count()功能使用混淆
- find()返回的只是需要查找的元素所对应的索引位置,一定要记住是索引位置
- count()返回的是指定字符在字符串里出现的次数,通俗的将就是查看该字符在字符串里有多少个,返回的是一个数量,数字。
13.列表追加元素出现并打印出现None
原因分析:列表是可变类型,对列表进行操作会直接对原列表进行修改,所以无法使用赋值等操作,也无法进行直接打印操作,如果给操作后的列表赋值、打印都会导致None
正确做法是:
14.列表插入内容和位置混淆
a.insert(位置,’内容’) 代表位置的数字应该填写在前面,内容在后面
错误示例:
正确示例:
15.pop删除指定索引的值发生索引超出范围问题
特点:在删除索引值所对应的值后,后面值会前移,也就是后面的值得索引相对于删除前会往前进一位
易错点:当用户需要删除索引位置2,3,4的元素时,我们不能死板的执行删除索引值为2,3,4的命令,因为当我们删除第一个索引的值时,原来需要删除的索引3所对应的值,会因为删除索引2导致后续的值得索引都前移一位,所以此时,所以原来的索引值3会变化成2,同样的道理,索引4会变成3,如果继续删除,索引还会前移。
错题:
正确解决办法:
16.append()和extend()的区别
append():
- 接受参数可以是任何类型,数值,字符串,列表,元组,作用是将参数所代表的值作为一个整体加入到另外一个列表中,这里强调的是作为一个整体
extend():接收的参数必须是一个序列:字符串(可以是单个字符)、列表、元组,必须是序列才行,例如整型(数值)就不行
```python
#示例一:appenda = [1,2,3]a.append(10)print(a) #结果为[1,2,3,10] 10是作为一个整体加入到a中了,因为10本身是一个数值,并不是序列,所以10没有用中括号括起来相当于列表a的一个元素#示例二:appenda = [1,2,3]b = [4,5,6]a.append(b)print(a) #结果为[1,2,3,[4,5,6]] b是作为一个整体加入到a中了,b这里是一个序列,所以它是作为一个序列加入到a中,所以需要用中括号括起来表示一个整体,相当于列表a的一个元素#示例三:extendc = [1,2,3]d = [4,5,6]c.extend(d)print(c) #结果为[1,2,3,4,5,6] ,d中的每一个元素都加到列表a中了,相当于两个列表的合并#示例三:extendc = [1,2,3]c.extend(‘a’)print(c) #结果为[1,2,3,’a’] ,d这里的’a’是字符串,字符串实际上也是一种序列,所以可以接收该参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 错误示例:
- 
### 17.切片和索引的取出来的值得类型混淆不清
- 切片取出来的是列表
- 索引取出来的是具体的值:字符串、整型
错误示例:
- 
正确例子:
- ```python
li = [1, 3, 2, "a", 4, "b", 5,"c"]# 通过对li列表的切片形成新的列表 ["c"]li6 = li[-1:]print(li6)#结果是:['c']
18.列表从右往左切片取值
易错点:切片的区间和步长不一致
要点:a[m:n]
易错点:不要在意m和n的正负
- 如果是从左往右切片取值,起始索引在列表中所代表的值必须要在终止索引所代表的的值得左边,并且步长要是正数 —->相当于:因为m是索引起始值,n是索引终止值,那么只要a[m]在列表中所代表的的值在a[n]所代表的的值得左边就行了,同时步长必须要为正数
- 如果是从右往左切片,起始索引所代表的的值必须要在终止索引所代表的的值得右边,并且步长要是负数—————–>相当于:首先步长必须要为负数,a[m:n:-1] ,列表里,a[m]所代表的值必须要在a[n所代表的的值的右边即可]
1
#因为是正向取值,a[m:n]在列表中只要a[m]所代表的值1在a[n]所代表的的值得左边就行a = [1,2,3,4]#正向取值,从左往右print(a[0:-2]) #结果为:[1,2]print(a[0:2]) #结果为:[1,2]#因为是逆向取值,从右往左,a[m:n:-1],a[m]所代表的的值必须要在a[n]所代表的的值的右边#逆向取值,从右往左print(a[2:0:-1]) #结果为:[3,2]print(a[-1:1:-1]) #结果为:[4,3]
19.嵌套的列表取值定位错误,导致无法插入
易错点:在嵌套的列表里修改数据时,定位不精确,导致无法插入
示例:定位到字符串上了,导致该字符串所在的列表无法插入数据
正确做法:
20.操作字典取值
错误原因:键没有引起来,必须要用单引号引起来
1 | #正确方法:a = {'name':'张三','age':28,'性别':'男'}a['age'] = 999 #错误原因:键没有引起来print(a) #结果为:{'name': '张三', 'age': 999, '性别': '男'} |
21.操作字典使用大括号报错
原因:使用了大括号,应该用中括号[]
22.字典get修改值出现函数不能调用
错误写法:用get修改值
错误信息:cannot assign to function call
正确写法:用键修改
23.判断数据A是否在字典B里
目的:应该是比对字典里的数据,即比对字典的值
易错点:如果直接比对字典,那么比对的只是字典的键,所以必须使用:A in a.values() 这样才是比对字典里的值
错误写法:
正确写法:
24.字典循环嵌套与内存
易错点:循环嵌套,嵌套进去的也是指向的数据,而不是某个变量
1 | #字典循环嵌套与内存1:data_list = []data = {}for i in range(2): data['user'] = i data_list.append(data)print(data) #此时结果为:{'user': 1}#此时data_list的两个元素都是指向同一个字典,所以它两的结果是最后一轮循环修改后的字典数据print(data_list) #结果为:[{'user': 1}, {'user': 1}]#字典循环嵌套与内存2:a = [1,2,3]b = []for i in range (2): b.append(a) #添加的并不是变量,而是所指向的数据a[0] = 66print(b) #结果为:[[66, 2, 3], [66, 2, 3]]#字典循环嵌套与内存3:a = []b = {}for i in range (2): b['user'] = i a.append(i) print(a) #结果为:[0, 1]print(b) #结果为:{'user': 1}#字典循环嵌套与内存4:b = []for i in range(2): a = {} #每次循环都生成一个新字典,所以最终b里都是不同的字典 a['user'] = i b.append(a)print(b) #结果为:[{'user': 0}, {'user': 1}] |
25.文件操作:readline和readlines同时存在无作用
易错点:
如果先执行readline(),再执行readlines,那么readline执行就是读取一行,readlines()读取的就是减去之前读取的一行,因为第一次读取,光标到了第一行的末尾,所以第二次读取导致第一行无法读取
如果先执行readlines(),再执行readline(),那么readlines()执行就是读取所有行,readline()读取的就是空,因为第一次读取,光标到了文本的末尾,所以第二次读取导致什么都读取不到
1
#易错点1示例:'''#文本内容张三 28李四 25王麻子 20'''message = open('测试.txt',mode= 'r',encoding='utf-8')message_readline = message.readline()message_readlines = message.readlines()print(message_readline,message_readlines)'''结果为:张三 28 ['李四 25 \n', '王麻子 20']'''#易错点2示例:'''#文本内容张三 28李四 25王麻子 20'''message = open('测试.txt',mode= 'r',encoding='utf-8')message_readlines = message.readlines()message_readline = message.readline()print(message_readline,message_readlines)'''结果为: ['张三 28 \n', '李四 25 \n', '王麻子 20']'''
26. 装饰器的调用
错误1:装饰器的调用语句后面没有紧跟定义函数的语句
错误2:@ + 函数 +() 这里不应该有括号
错误3:返回装饰器地址时加了括号,报错
27.shutil.rmtree()删除文件夹
易错点:无法直接删除文件,只能直接删除文件夹,但是如果该文件夹里有其他文件夹或者文件,那么这些文件夹和文件都会被删除
1 | #删除文件夹树(包括内部的文件夹、文件)import shutil,os,timeos.makedirs('E:\oldboy_python\code\文件夹树\文件夹A\文件夹B') #创建文件夹树time.sleep(6) #延迟5秒,方便直观的观察文件夹树的创建和删除shutil.rmtree(r'文件夹树') #删除文件夹树 |
28.git报错:git error: pathspec
错误语句:git error: pathspec ‘-‘ did not match any file(s) known to git
错误原因:git commit - m “备注” 时错误,这里应该是双引号,如果用单引号就会报错
29.导入已存在的模块,却找不到方法
问题:导入一个模块,该模块命名存在,我通过 模块名 + 方法名 调用时,却提示该方法不存在
原因:要搞清楚python找模块的依据,首先会在脚本的当前运行路径查找模块,如果没有,则根据默认的路径来查找,所以这里错误的原因就是在该运行脚本的当前目录下有另外一个py文件,该py文件的名字可能由于我们命名不规范,导致其和我们需要导入的模块的名字一样,所以当我们调用模块时,找到的仅仅只是这一个同名的py文件,又由于该py文件里没有我们需要的方法,所以pycharm就报错了
解决方法:规范py文件的名称,或者使用模块的相对导入方法来导入
30.md5加密导入的是hashlib模块
错误点:是hashlib模块,并不是md5模块
易错点:调用update和hexdigest函数时,需要手动输入,pycharm里无法补全,容易让人产生错觉(该函数不存在)
附录3 陌生单词
单词 | 中文意思 |
---|---|
enumerate | 枚举 |
tuple | 元组 |
decimal | 十进制 |
numeric | 数字 |
devision | 除法 |
push | 推送 |
strip | 去除 |
startswith | 以…开始 |
assign | 分配 |
reverse | 反转 |
message | 消息 |
switch | 开关 |
intersection | 交叉,交集 |
flush | 冲刷 |
argument | 变量 |
odd number | 奇数 |
divmod | 商和余数 |
verification | 验证 |
encryption | 加密 |
prompt | 提示 |
timestamp | 时间戳 |
encryption | 加密 |
perimeter | 周长 |
附录4 面试相关试题
1.编码与储存的关系
- 既然gbk和gb2312中文占用的字节小,消耗的资源小,那为什么还要用utf-8编码(3个字节)?
- gbk、gb2312支持的范围小
- 英文是主流语言,各种最新的、最成熟的框架、数据都是用utf-8编码储存或者编写的,如果使用gbk、gb2312将会导致编码冲突,如果将utf-8修改成gbk、gb2312花费的时间太多,并且过程十分繁琐
2.字符串倒着打印(利用索引)
1 | a = '王麻子是个傻逼'print(a[-1:0:-1]) |
3.小括号特性
1 | #题目:[(1),(2),(3)] 相当于什么?a = [(1),(2),(3)] #因为小括号特性,这里相当于[1, 2, 3]print(a) |
4.is和==的区别
==:是用来比较数值的
is:是用来比较内存地址的
1 | a = [1,2]b = [1,2]print(a == b,a is b)#结果:True False |
1 | #疑难题:为啥结果为True,因为pyton的缓存池a = 3b = 3print(a == b,a is b)#结果为:True True |
示例:
1 | #示例1:v1 = {'k1':'v1','k2':[1,2,3]}v2 = {'k1':'v1','k2':[1,2,3]}result1 = v1 == v2 result2 = v1 is v2 print(result1) #结果为:Trueprint(result2) #结果为:False,比较的是内存地址#示例2:v1 = {'k1':'v1','k2':[1,2,3]}v2 = v1 result1 = v1 == v2 result2 = v1 is v2 print(result1) #True,指向的都是同一个字典print(result2) #Ture |
5.简述深浅拷贝
浅拷贝:拷贝后,拷贝体和源数据互相可以影响
深拷贝:拷贝后,拷贝体和源数据完全独立,无法影响到彼此
附录 5 工具及其使用方法
1.1 安装pycharm专业版与使用
1.1.1 安装pycharm专业版
激活方法(稳定):使用补丁破解
1、先在专业版上选择试用
2、将补丁压缩包拖进pycharm即可
3、选择重启
4、选择方式(有网和无网)
5、选择激活方式,建议license server方式教程地址:https://zhile.io/
1.1.2 通过ctrl+滚轮调节字体大小设置
1.1.3 快速定位当前脚本位置
1.1.4 自动规范代码
1.1.5 pycharm自动加脚本头
在pycharm里settings—Editor—Code Style —File and Code Templates — python script
加入:
1
#! /usr/bin/env python# -*- coding:utf-8 -*-
1.1.6 断点与debug
1.1.7 快速批量注释
- ctrl + /
1.1.8 查看对象的其他方法
查看:鼠标移动到对象上,然后按下 ctr l键 ,鼠标会变成小手,然后点击即可
1.1.9 pycharm修改编码
要点:修改三处,三处都修改为utf-8
1.2 码云的安装和使用
1.2.1 安装
注册码云账号,并且新建仓库
2、下载git并安装,一直下一步就行。https://git-scm.com/
3、新建一个专用的文件夹 oldboy_python,然后进入该文件夹,右键—Git Bash Here
1.2.2 初始化和操作命令
git初始化:
如果第一次使用git,没有绑定用户名和邮箱的情况下:
输入命令:
- git init
- git config –global user.name “发飙的登山包”
- git config –global user.email “7403103+python_road@user.noreply.gitee.com“
- git remote add origin git@gitee.com:python_road/old_boy_python_self_study.git
以上就初始化完成
开始操作:
查看状态(在文件夹下的内容有变动的情况下)
git status
将所有有变化的内容进行收集
git add .
为每次提交填写一个说明
git commit -m “笔记更新”
将内容进行上传
git push origin master
1.3 Typora的使用
1.3.1 表格行数和列数的自定义编辑
1.3.2 给本地所有图片换路径
原理:md文件里的所有图片实际上就是一个路径的指向
怎么换路径?
ctrl +f —–选择替换
将原来的路径,替换成新路径,当然前提是原来路径的所有图片转换到新路径里来了
1.3.3 自动上传图片到码云
1、安装PicGo软件,推荐使用exe程序:https://github.com/Molunerfinn/PicGo/releases
2、安装gitee插件(码云上传插件):
- 前提:安装node.js 插件:https://nodejs.org/en/
3、在码云创建仓库
仓库名称
设置公开
勾选使用Readme文件初始化这个仓库
4、获取token
打开码云—-设置—–个人令牌—–新增令牌
token:722c1dc27e91969847230c2019655e89
github token: df43a6a0e0a20977b09a55055a077167aaec1f8c
5、配置gitee上传插件
repo:用户名/仓库名称,比如我自己的仓库leonG7/blogImage,找不到的可以直接复制仓库的url
branch:分支,这里写上master
token:填入码云的私人令牌
path:路径,表示上传的图片在码云里的途径,区别是在码云的根目录还是其他目录存放
customPath:提交消息,这一项和下一项customURL都不用填。在提交到码云后,会显示提交消息,插件默认提交的是
Upload 图片名 by picGo - 时间
6、上传测试
成功示意图:
7、开始上传
单个上传:每当我们插入一个图片到md里,都会提示是否上传,此时可以选择上传
批量上传文件夹里的文件:不建议使用,容易造成md里的图片混乱
前提1:该md文档里的所有图片都储存在同一个目录里,如果不在,则拷贝到同一个目录
前提2:此时因为我已经通过 格式—-图像—-设置图片根目录 设置好了图片的根目录,并且偏好设置里选择的是上传模式,所以在图片根目录下会生成一个upload文件夹,以后的每一次插入图片,图片的实际存储位置都会跑到这个文件夹来
易错点:
第一次配置并上传所有图片时,首先一定要先备份md文件,并且千万不要一键上传,因为一键上传上去的图片因为顺序问题导致md里面的图片混乱
上传图片时,picgo必须要在后台开启着,typora无法自己打开picgo,如果后台没开启picgo,则上传失败
必须在picgo里配置好上传的路径(本地路径),否则提示错误
端口冲突,将端口改为36677端口
没有打开用时间戳重命名图片
没有选择默认图床
千万不要将码云里面的图片删除,一旦删除了码云里的图片,那么md文档里的图片都无法显示
怎么一建删除PicGo相册里的残留记录?
PicGo设置——打开配置文件——删除所有内容(里面包括上传记录和配置信息),一旦删除,仅仅只需要重新配置即可,重新配置参考之前的步骤
一键上传图片,图片错位怎么办?
PicGo配置步骤还是一样的,但是不要在Typora里点击一键上传,md里的图片会错位,首先找到md文件里所有的图片所在的文件夹,然后将该文件夹里的所有图片通过PicGo上传,通过全选—-拖曳方式进行
给md文件里的所有图片地址进行替换
1
gitee图片地址样式: 只需要将md文件里的图片地址里的 C:\Users\DH\AppData\Roaming\Typora\typora-user-images\ 或者 upload/ 的路径替换成地址 https://gitee.com/python_road/python_img/raw/master/img/ 就行了,全部替换
替换完成后就可以开启通过时间戳来重命名了,以后每次粘贴图片选择上传就行了
数据类型的方法汇总
1、整型int
无方法
2、布尔型
无方法
3、字符串(str)
大写:
1 | a = 'oldboy' |
小写:
- lower:只支持ascii列表里的
- casefold:支持除中文外所有语言
1 | a = 'OLDBOY' |
替换:
- a.replace(‘被替换的字符’,‘替换后的字符’)
1 | a = 'oldboy' |
分割:split
1 | a = 'old-boy' |
去除空格:
去除左侧空格:lstrip()
去除右侧空格:rstrip()
去除左右空格:strip()
去除指定字符:strip(‘字符’) ,主要去除\t,\n,因为他们都是由空格组成的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#去除左侧空格
a = ' oldboy '
b = a.lstrip()
print(b) #结果为:oldboy
#去除右侧空格:
a = ' oldboy '
b = a.rstrip()
print(b) #结果为: oldboy
#去除左右两侧空格:
a = ' oldboy '
b = a.strip()
print(b) #结果为:oldboy
#去除指定字符
a = 'oldboy\n'
b = a.strip('\n')
print(b) #结果为:oldboy
更改连接符
接收参数:必须是字符类型
1
2
3a = 'oldboy'
b = '-'.join(a)
print(b) #结果为:o-l-d-b-o-y
是否是数字:isdigit
- 如果是小数,则返回False
1 | a = '12' |
判断是否是十进制数字
要点:是十进制,就返回True,否则False
如果是小数,则返回False
1
2
3
4
5
6
7
8
9
10#例一:
a = '⑿'
b = a.isdecimal()
print(b) #False
#例二:
a = '12'
b = a.isdecimal()
print(b) #True
查找字符所在的索引位置
- 返回的是索引位置
1 | a = 'oldboy' |
查找字符出现的次数
返回的是该字符在字符串中 出现的次数
1
2
3a = 'oldboy'
b = a.count('o')
print(b) #结果为:2
求字符串的长度
- 返回的是字符串里面字符的长度
1 | a = 'oldboy' |
索引:
索引从0开始,0代表第一个元素
变量名 + [ + 索引值 + ]
1
2a = 'oldboy'
print(a[2]) #结果:d
切片:
- a[0:2]:取索引0和1所代表的的值,不包括2
1 | a = 'oldboy' |
步长:
不写步长,则默认1
步长为负数,则倒着取值
如果步长是正数,那么切片的起始索引所代表的的值必须要在终止索引所代表的的值的左边
如果步长是负数,那么切片的起始索引值所代表的的值必须要在终止所代表的的值的右边
1
2
3
4a = 'oldboy'
print(a[::2]) #步长为2,结果为:odo
print(a[::-1]) #步长为负数,则倒着打印:结果为:yobdlo
print(a[4:0:-1]) #结果为:obdl
格式化:format
- 必须要和大括号{}进行搭配
1 | a = 'oldboy{}' |
以指定的编码显示数据
encode
1
2
3a = '你好呀'
b = a.encode('utf-8') #指定以utf-8编码的形式存储到内存中
print(b) #结果为:b'\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x91\x80'
以什么开头:
1 | a = 'oldboy' |
以什么结尾
1 | a = 'oldboy' |
for循环遍历元素:
1 | a = 'oldboy' |
4、元组
以指定字符串连接元素:
1 | a = ('oldboy','666') |
for循环遍历元素:
1 | a = ('oldboy','666') |
长度:
1 | a = ('oldboy','666') |
查看数据在元组中出现的次数
1 | a = ('oldboy','666','666') |
索引:
1 | a = ('oldboy','666','666') |
切片:
1 | a = ('oldboy','666','666')print(a[:]) #结果为:('oldboy', '666', '666') |
步长:
1 | a = ('oldboy','666','666') |
5、列表
取值:
1 | a = [1,2,11,22] |
增加:
append():追加,接收的参数作为一个整体加入
extend():扩展,接收的参数里的每个元素都加进来
1
2
3
4
5
6
7
8
9
10
11
12#append()
a = [1,2,11,22]
b = ['狗屎','666']
a.append(b)
print(a) #结果为:[1,2,11,22,['狗屎','666']]
#extend()
a = [1,2,11,22]
b = ['狗屎','666']
a.extend(b)
print(a) #结果为:[1,2,11,22,'狗屎','666']
删除:
pop():接收参数为索引值,删除并且返回删除的值
remove():接收参数为具体的数据,如果该数据不存在则报错
clear():清空列表
del a[0]:根据索引删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#del和remove
a = [1,2,11,22]
b = ['狗屎','666']
del a[0]
print(a) #结果为:[2, 11, 22]
b.remove('666')
print(b) #结果为:['狗屎']
#pop和clear
a = [1,2,11,22]
b = ['狗屎','666']
a.pop(2)
print(a) #结果为:[1, 2, 22]
b.clear()
print(b) #结果为:[]
修改:
索引修改
1
2
3a = [1,2,11,22]
a[3] = 99
print(a) #结果为:[1, 2, 11, 99]
插入:
接收参数第一个为索引位置,第二个为需要插入的数据
1
2
3a = ['你好','old','boy']
a.insert(1,'666')
print(a) #结果为:['你好', '666', 'old', 'boy']
排序
- sort():不加参数默认是从小到大
- sort(reverse=’True’):则代表从大到小
1 | #从小到大排序 |
反转
reverse
1
2
3a = [1,66,55,22]
a.reverse()
print(a) #结果为:[22, 55, 66, 1]
查找数据出现的次数
1 | a = [55,66,55,22] |
索引:
1 | a = [55,66,55,22] |
切片:
1 | #示例1: |
步长:
1 | a = [55,66,55,22] |
长度:
1 | a = [55,66,55,22] |
以特定字符连接元素:
1 | a = ['你好','old','boy'] |
6、字典
获取所有的键
1 | a = {'name':'张三','age':26,'sex':'男'} |
获取所有的值
1 | a = {'name':'张三','age':26,'sex':'男'} |
获取所有的键值对
1 | a = {'name':'张三','age':26,'sex':'男'} |
增加
- 通过键来增加
- 通过update来增加
1 | #通过键来增加 |
修改
通过索引修改
通过update修改
1
2
3
4
5
6
7
8
9
10#通过键来修改
a = {'name':'张三','age':26,'sex':'男'}
a['name'] = '李四'
print(a) #结果为:{'name': '李四', 'age': 26, 'sex': '男'}
#通过update修改
a = {'name':'张三','age':26,'sex':'男'}
a.update({'name':'李四'})
print(a) #结果为:{'name': '李四', 'age': 26, 'sex': '男'}
删除
通过del删除
通过pop删除
通过clear清空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#通过del删除
a = {'name':'张三','age':26,'sex':'男'}
del a['sex']
print(a) #结果为:{'name': '张三', 'age': 26}
#通过pop删除
a = {'name':'张三','age':26,'sex':'男'}
a.pop('name')
print(a) #结果为:{'age': 26, 'sex': '男'}
#通过clear清空
a = {'name':'张三','age':26,'sex':'男'}
a.clear()
print(a) #结果为:{}
查看
索引查看:注意索引使用的是中括号,如果不存在,则报错
get查看:如果不存在默认返回None,也可以指定返回内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#1、键存在
#索引查看
a = {'name':'张三','age':26,'sex':'男'}
print(a['name']) #结果为:张三
#get查看
a = {'name':'张三','age':26,'sex':'男'}
print(a.get('name')) #结果为:张三
#2、键不存在
#索引查看:不存在则报错
a = {'name':'张三','age':26,'sex':'男'}
print(a['money']) #报错:KeyError: 'money'
#get查看:不存在则默认返回None
a = {'name':'张三','age':26,'sex':'男'}
print(a.get('money')) #结果为:None
#get查看:不存在可以指定返回内容
a = {'name':'张三','age':26,'sex':'男'}
print(a.get('money','键不存在')) #结果为:键不存在
for循环
for + 字典 得到的只是键而已
for + 字典.values() :得到的才是值
for + 字典.items() :得到的才是键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#for + 字典 得到的只是键而已
a = {'name':'张三','age':26,'sex':'男'}
for i in a:
print(i)
'''
结果:name
age
sex
'''
#for + 字典.values() :得到的才是值
a = {'name':'张三','age':26,'sex':'男'}
for i in a.values():
print(i)
'''
结果:
张三
26
男
'''
#for + 字典.items() :得到的才是键值对
a = {'name':'张三','age':26,'sex':'男'}
for k,v in a.items():
print(k,v)
'''
结果:
name 张三
age 26
sex 男
'''
7、集合
新增
- 特性:如果集合理由相同内容,则不新增
- 当个元素新增:add
- 批量新增:update,接收参数为序列,可以将接收参数的每一个元素添加到集合中
1 | #add添加 |
删除
remove:直接删除指定的值,如果不存在,则报错
1
2
3
4
5
6
7
8
9#删除的值存在
a = {'张三',26,'男'}
a.remove(26)
print(a) # 结果:{'张三', '男'}
#删除的值不存在:报错
a = {'张三',26,'男'}
a.remove(33)
print(a) # 结果:KeyError: 33discard:直接删除指定的值,不管删除的值存不存在都不报错
1
2
3
4
5
6
7
8
9
10
11#删除的值存在
a = {'张三',26,'男'}
a.discard(26)
print(a) # 结果:{'张三', '男'}
#删除的值不存在:不报错
a = {'张三',26,'男'}
a.discard(66)
print(a) # 结果:{'张三', 26, '男'}随机删除:pop
1 | a = {'张三',26,'男'} |
交集
1 | a = {'张三',26,'男'} |
并集
1 | a = {'张三',26,'男'} |
差集
1 | #a相对于b的差集:提取元素(在b里所没有的a的元素) |
8、None
作用:占坑
特性:不带任何属性
9、数据类型的转换
其他类型转布尔型:
- int里只有0是False,其余全部是True
- str里只有空’’是False,其余全部是True
- 元组里只有空元组()是False,其余全部是True
- 列表里只有空列表[]是False,其余全部是True
- 字典里只有空字典{}是False,其余全部是True
- 集合里只有空集合set()是False,其余全部是True
- None本身就是Flase
布尔类型转整型:
- True可以转换为1
- False可以转换为0
整型int和字符串str互转
- 整型转str:str(6)
- str转整型:int(‘10’) 此处需要注意,必须要类似于数字的字符才能转整型,否则容易报错
元组、列表、结合互转
本质:相当于for循环待转换序列的每一个元素,然后将其添加到需要转换的序列
元组转列表、集合
1
2
3
4a = (3,4,5)
print(list(a)) # 元组转列表,结果:[3, 4, 5]
print(set(a)) # 元组转集合,结果:{3, 4, 5}列表转元组、集合
1
2
3a = [3,4,5]
print(tuple(a)) # 列表转元组,结果:(3, 4, 5)
print(set(a)) # 列表转集合,结果:{3, 4, 5}集合转元组、列表
1
2
3a = {'张三',26,'男'}
print(tuple(a)) # 结合转元组,结果:(26, '男', '张三')
print(list(a)) # 集合转列表,结果:[26, '男', '张三']
特殊情况
- 因为字典的结构特殊,所以其他数据类型无法转换成字典
- 原因:因为序列转换的本质就是for循环,但是for循环字典得到的只是键,所以转换字典得到的只是键
- 因为字典的结构特殊,所以字典转化为其他类型,转换的只是键,而没有值
- 原因:因为序列转换的本质就是for循环,但是for循环字典得到的只是键,所以转换字典得到的只是键
- 序列转化为str,结果还是一样,转没转都是一样
- 因为字典的结构特殊,所以其他数据类型无法转换成字典
10、空数据类型的设置
两种设置方法:
字符串
1
2a = ''
a = str()元组
1
2a = ()
a = tuple()列表
1
2a = []
a = list()字典
1
2a = {}
a = dict()
一种设置方法:
整型只有一种设置方法
1
a = int()
布尔型只有一种设置方法
1
a = False #这就是相当于布尔类型的空了
集合只有一种设置方法
1
a = set()
None只有一种设置方法
1
a = None #本身就相当于空
11、 数据类型方法的返回值
通用判别方法:
str、tuple,dict一般情况下都是有返回值的
特殊情况:del(),clear() 这些没有返回值
可变类型list,set一般都没有返回值
特殊情况:pop()、intersection(),difference(),union() 这些有返回值