笔记来源;
尚硅谷 Python 基础——李立超
1 计算机相关知识
1. 交互界面介绍:
TUI:文本交互界面
GUI:图形化交互界面
2. Windows 下的文本交互界面
Windows 下比较常用的进入文本交互界面(也叫命令提示符,dos窗口等)的方式是 win+r 键打开运行窗口,输入 cmd 后回车。还可以在打开的文件夹窗口地址栏输入 cmd 进入文本交互界面,此时文本交互界面的路径就在当前路径下。
Wndows 下命令行提示符如下:
Microsoft Windows [版本 10.0.18362.1016]
© 2019 Microsoft Corporation。保留所有权利。
C:\Users\11763>
命令提示符大致可分为两部分,其中第一部分为版本及版权信息,内容如下:
Microsoft Windows [版本 10.0.18362.1016]
© 2019 Microsoft Corporation。保留所有权利。
第二部分为命令提示符:C:\Users\11763>
其中 C: 为当前所在盘符,我们可以在命令行窗口输入X:(X代表任一盘符) 来切换所在盘符,比如我们需要切换到 D 盘,只需要输入 D: 并回车就可以切换至 D 盘。
\Users\11763 为所在磁盘的文件夹路径,我们可以通过 cd 来切换所在文件夹路径,如 cd Documents 将切换至 \Users\11763 路径下的 Documents 文件夹。
行末的 > 表示命令提示符,我们可以在其后面输入指令了。
3. Windows 下常用 Dos 命令
命令 | 作用 |
---|---|
dir | 查看当前目录下有哪些文件/文件夹 |
cd | 切换目录,cd Desktop,其中Desktop为目录名. . 代表当前目录 … 代表上一级目录 |
md | 创建一个目录,md test,test为新建目录名 |
rd | 删除一个目录,rd test,test为待删除目标目录名,删除不了文件 |
del | 删除文件,del test.txt |
cls | 清屏 |
type nul> | 创建文件,type nul> test.txt ,test.txt 为文件名 |
1. 按上下方向键可以查看历史命令。
2. 按 tab 键自动补全命令。
4. Windows 下环境变量
环境变量指的是操作系统中的一些变量,我们可以通过修改环境变量,来对计算机进行设置。环境变量主要用来配置一些路径。
查看环境变量:选中此电脑,鼠标右键,点击属性,弹出属性窗口在最左侧找到高级系统设置并点击,弹出的系统属性窗口下方找到环境变量并点击就可以查看系统的环境变量。
环境变量界面可以看到分成了两部分,上一部分是用户环境变量,下一部分是系统环境变量。他们的作用是一样的,只是作用域不同,用户环境变量只在当前用户下生效,系统环境变量对该系统下所有用户生效。
环境变量可以有多个值,值之间使用英文下的分号隔开。
5. Windows 下path 环境变量
环境变量(environment variable)中保存的是一个个的路径,当我们在命令行中输入一个命令或访问一个文件时,我们的系统会在当前目录下寻找,如果找到了就直接打开或执行,如果没有找到则会依次到path环境变量中去寻找。如果在path环境变量中也没找到则给出提示。
我们可以将一些经常用到的文件或者程序的路径添加到path环境变量中,这样我们就可以在任意的位置访问到这些文件了。
6. 进制
二进制:满二进一,在二进制中一共有两个数字,分别是0和1。计算机只认二进制。
八进制:满八进一,在八进制中一共有八个数字,分别是0,1,2,3,4,5,6,7。
十进制:满十进一,共有十个数字。这就是我们日常生活中用到的数字。
十六进制:满十六进一,共有十六个数字,由于十六进制满十六才进位,所以引入了A-F来表示10-15。
我们可以将计算机的内存看成一个个小格子,每个小格子中可以只能存储0或1,这每个小格子就是计算机中的最小单位,被称为1bit(位)。我们能操作的单位是字节(byte),1byte=8bit
Binary Viewer:二进制查看器
7. 文本文件和字符集
文本分为两种,一种叫纯文本,一种叫富文本。纯文本只能保存单一的文本内容,无法保存图片、字体、颜色等。富文本则可以保存纯文本外的图片、字体、图片等,比如word就是一个富文本。我们写程序的时候用的是纯文本。
将字符转换为二进制编码的过程,我们称之为编码。将二进制码转换为字符的过程称之为解码。而编码与解码所采用的规则,我们称之为字符集。
常见字符集:
ASCII:
美国用编码,使用7位来对美国常用的字符进行编码。包含128个字符。
ISO-8859-1:
欧洲的编码,使用8位,包含256个字符。
GB2312、GBK:
国标码,中国的编码。
Unicode:
万国码,包含世界上所有的语言和符号,编写程序时一般都会使用Unicode编码。Unicode编码有多种实现,UTF-8 UTF-16 UTF-32,最常用的就是UTF-8。
2 Python 入门
什么是计算机语言?
计算机语言从某种程度上来讲,和我们人类使用的各种语言没有本质的区别,只是交流的主体不同罢了。计算机理解不了人类的语言,但是可以理解它独有的计算机语言。本质来讲,计算机只能理解有无电流通过这件事,最开始的机器语言使用0和1来表示有无电流通过,无数的0和1就可以组成计算机可以理解的语言。由于机器语言对于人类来说可读性太低,理解起来太难,后来就有了较为高级的汇编语言,再后来又出现了高级语言如 Python、Java等。
2.1 Python 开发环境搭建
2.1.1 Python 解释器的安装
Python 解释器的分类:
CPython:官方解释器,使用C语言编写。
PyPy:使用Python语言编写的Python解释器。
IronPython:用.net编写的Python解释器。
Jython:用Java编写的Python解释器。
Python拥有多语言编写的解释器,目的是使其可以在多种语言下运行。
1. 下载安装包
需要注意的是,Python的2.*版本和3.*版本并不兼容,我们学习所使用的是3.*版本。
访问Python官网,点击顶部Downloads下载导航菜单,根据所使用的系统环境选择相应的版本进行下载。这里使用的是windows环境下的3.6.5版本。
2. 安装
双击下载完成的exe可执行程序,进入安装界面。
点击自定义安装,接着点下一步。
提示安装成功后点击close即可,此时已完成安装。
可能会出现一个环境变量长度限制的警告,点击此警告即可。
安装完成后打开命令提示符窗口,输入python,回车,如果出现如下界面则安装成功。(该黑窗口是Python的一个Shell窗口,即Python的交互界面)
如果未安装成功可将python卸载后重新安装。
Python交互界面键入exit(),退出。
2.1.2 Python的交互模式
在安装Python的时候,会默认安装一个开发工具,叫做IDLE。win10环境下,我们可以通过开始菜单找到该程序。打开后的界面也是一个Python的交互界面,如下:
在IDEL中,我们可以使用tab键进行命令补全。并且可以对当前窗口的字符进行保存,但是会保存无用代码,没有实际意义,不适合开发使用。
我们可以在一个txt文本中编写Python代码,然后使用Python交互模式解析执行。比如我们在一个txt文本中写入了print('hello')
,打开命令提示符窗口后我们不在直接使用python直接进入python的交互模式,而是在后面指定该txt文件路径,这样就可以解析执行该文本中保存的代码。
Python的文件后缀名为py,虽然用txt也能够解析,但用py文件更方便理解与辨别。而且,使用后缀为py的文件,可以不使用python 命令,直接解析文件中的代码。
2.1.3 Python 和 Sublime的整合
在 Sublime 中可以通过 ctrl + b 自动在 Sublime 内置的控制台中执行。但是该方式在某些 Sublime 版本中对中文的支持不是太好,而且不能使用 input() 函数。我们可以使用 SublimeREPL 插件来运行 Python 代码。这就需要我们使用 Package Control 中的 Install Package 来安装该插件。
点击顶部的 Preferences->Package Control,点击弹出窗中的 Install Package,等待弹出搜索窗口后输入 SublimeREPL ,点击进行安装。安装成功后我们可以在 Tools下找到 SublimeREPL 选项,找到子菜单 python中的 python 菜单,点击后即可进入交互界面。如果想运行文件,需要点击 Python - RUN curent file。
在运行 Python 代码时可能会出现如下的异常提示:Python was not found but can be installed from the Microsoft Store,此时我们需要到系统环境变量中检查 Path 下是否有 %USERPROFILE%\AppData\Local\Microsoft\WindowsApps 配置,如果有,删除该配置即可。
原因:%USERPROFILE%\AppData\Local\Microsoft\WindowsApps会造成用户在使用python时,会优先连到windows应用商店去下载python,而不是使用电脑本地安装的python。
很显然,使用 SublimeREPL 运行还需要点选,过于麻烦。我们可以通过配置 Sublime 快捷键的方式来简化点选的操作。点击 Sublime 顶部菜单中的 Preferences->key bindings,在弹出的快捷键配置窗口贴入下面的配置即可。此时,只要点击 F5 即可运行 Python 文件。
{ "keys": ["f5"], "caption": "SublimeREPL:Python","command": "run_existing_window_command", "args":{"id": "repl_python_run","file": "config/Python/Main.sublime-menu"}},
效果图如下:
2.2 基本概念
1. 表达式
表达式类似于数学公式,比如:10+5 就是一个表达式,10>5 也是一个表达式。表达式一般仅仅用来计算一些结果,不会对程序产生实质性的影响。如果在交互模式中输入一个表达式,解释器会自动计算结果并将结果进行输出。但是文件里的表达式如果不使用输出语句进行输出,运行文件后是不会有输出结果的。
2. 语句
在程序中,语句一般需要完成某种功能,比如打印信息、获取信息、为变量赋值等。比如print()
为打印语句,a=10
为赋值语句。
语句与表达式的区别是,语句的执行一般会对程序产生一定的影响。在交互模式中,不一定会输出语句的执行结果。
3. 程序(program)
程序就是由若干的语句和表达式构成的。
4. 函数(function)
函数就是一种语句,函数专门用来完成特定的功能。函数又可以分为内置函数和自定义函数,内置函数是 Python 解释器提供的函数,可以直接使用。自定义函数是由程序员自主创建的。
函数有两个要素,一个是参数,一个是返回值。
2.3 Python 的基本语法
1. 在Python中严格区分大小写。
2. Python 中的每一行就是一条语句,每条语句以换行符结束。
3. 在Python的规范中,建议每行不要超过80个字符。
4. 我们可以使用反斜杠来接下行,以此来解决一条语句过长的问题。
##下面的写法同 print(11112222)
print(1111\
2222)
5. Python是缩进严格的语言,不能在代码中随便写缩进。缩进在Python中表示的是代码块。
6. 在Python中,使用#来表示注释。#作为一行的开头是一个多行注释,跟在语句后面是一个单行注释。
2.4 python 中的字面量和变量
字面量:就是一个一个的值,如:1,2,3,“hello” 等。字面量表达的意思就是它的字面意思,可以在程序中直接使用。
变量(variable):变量可以用来保存字面量,并且变量中保存的字面量是可变的。
Python是一个动态类型的语言,在Python中使用变量,不需要声明,可以直接赋值使用。但需要注意的是不能使用未赋值的变量,否则会提示变量为定义。
2.5 标识符
在Python中所有可以自主命名的内容都属于标识符,如变量名、函数名、类名等。
标识符中可以包含字母、数字、下划线,但不能数字开头,但不能是关键字和保留字,否则会报语法错误。也不建议使用函数名作为标识符,因为在Python中使用函数名做表示符后原函数将会被覆盖。
2.6 数据类型
2.6.1 数值
在Python中,数值被分为整数、浮点数(小数)、复数三种。
1 整数
Python中所有的整形都是int类型,且大小没有限制,可以是一个无限大的整数。
如果一个整数过长,我们在编码的时候可以用下划线进行分割以方便识别。而作为分割的下划线不会被解释器忽略,因此对数值的大小没有影响仅仅是方便代码阅读。
# 代码
i = 111_222_333
print(i)
# 输出
111222333
在Python中,十进制不能以0开头,其它进制的表示方式如下:
c = 0b10 # 二进制以0b开头
c = 0o10 # 八进制以0o开头
c = 0x10 # 十六进制以0x开头
2 浮点数
在Python中,所有的小数都是浮点类型(float)。需要注意的是对浮点数进行计算的时候可能会得到一个不精确的结果,如0.1+0.2的结果是0.30000000000000004。
2.6.2 字符串
字符串用来表示一段文本信息,是程序中使用最多的数据类型,在Python中需要使用引号(单/双引号)引起来。需要注意的是,单引号和双引号不能混用,相同的引号不能嵌套使用(不同的可以),引号不能跨行使用,之后匹配当前行的另一半。在Python中可以用反斜杠标注多个行为一行,这样就能在代码中换行使用引号了。
# 代码
print("谁知盘中餐,\
粒粒皆辛苦。")
#输出
谁知盘中餐,粒粒皆辛苦。
在Python中,我们可以使用长字符串来保留字符串中的换行,缩进等格式。长字符串就是使用三重引号包裹的一段字符。
# 代码
print('''谁知盘中餐,
粒粒皆辛苦。''')
# 输出
谁知盘中餐,
粒粒皆辛苦。
在Python中,也可以使用反斜杠作为转义符,示例如下:
# 代码
print("子曰:\"学而时习之,不亦说乎。\"")
# 输出(如果不加转义符,会报错,双引号无法嵌套使用)
子曰:"学而时习之,不亦说乎。"
在 Python 中,可以在字符串前加 r 标注这是一个原始字符串,即不会对其中的符号进行转义。
file_name = "D:\\WorkSpace\\PythonLearndemo.txt"
print(file_name) ## D:\WorkSpace\PythonLearndemo.txt
## 使用前缀 r 标注原始字符串
file_name1 = r"D:\\WorkSpace\\PythonLearndemo.txt"
print(file_name1) ## D:\\WorkSpace\\PythonLearndemo.txt
在Python中字符串也可以用加号进行拼接,但是其不能与其他类型的值通过加号进行拼接,否则将会报错,因此很少使用加号来拼接字符串。下面将介绍另外三种字符串格式化方法。
1 print 方法的字符串拼接
# 代码
print("aaa","bbb","ccc")
# 输出
aaa bbb ccc
print 语句默认输出内容后以换行结尾,但在 python 中我们可以指定其结尾的字符,示例代码如下:
# 代码
print("11",end="")
print("22")
print("33")
# 输出
1122
33
2 创建字符串时指定占位符
# %s 表示任意字符,可以在字符串后使用%来指定该占位符
str = "str = %s"%"这是一个字符串"
print(str)# str = 这是一个字符串
str = "%s,%s"%("中秋节","要吃月饼")
print(str)# 中秋节,要吃月饼
str = "%3.5s"%"abcdef" # 限制字符串最短为三,最长为5
print(str)# abcde
str = "%.2f"%34.567 # %f 是浮点数的占位符,这里.2限制浮点数为两位,多余的四舍五入
print(str) # 34.57
str = "%d"%123.567 # %d 是整数的占位符,会直接舍去小数部分,不进行四舍五入
print(str) # 123
3 格式化字符串
# 通过在字符串前加一个f来注明这是一个格式化字符串
a = "这是一个格式化字符串"
str = f"a = {a}" # 在格式化字符串中可以直接嵌入变量,使用花括号获取
print(str) # a = 这是一个格式化字符串
在Python中我们可以使用乘法来对字符串进行复制,一个字符串乘以几就被复制几次。
str = "abc"
str = str * 2
print(str) # 输出:abcabc
2.6.3 布尔值和空值
布尔值的完整拼写是 boolean ,在 Python 中简写为bool。布尔值主要用来做逻辑判断,一共有 True 和 False 两个取值。
布尔值也属于整形,True 相当于 1(真),False 相当于 0(假)。
print(1 + True) # 输出:2 证明布尔值确实是整形
需要注意的是,Python 中布尔值的取值,True 和 False 首字母必须要大写。否则解释器会将其当成一个变量进行解析。
Python 中使用 None 标志空值。
str = None
print(str) # 输出:None,表示未正常赋值,不存在
2.6.4 类型检查
由于Python是一个动态类型语言,我们在使用一个变量时并没有声明它的类型,因此我们并不能直观的分辨 Python 中变量的类型。
在 Python 中,我们可以使用 type() 函数来检查变量的类型,这个函数将返回目标值的类型,示例如下:
# 数值
print(type(1)) # 输出:<class 'int'>
print(type(1.5)) # 输出:<class 'float'>
print(type(True)) # 输出:<class 'bool'>
# 字符串
print(type("abc")) # 输出:<class 'str'>
# 空值
print(type(None)) # 输出:<class 'NoneType'>
2.7 对象(Object)
Python 是一门面向对象的语言,对象是一个信息的集合,比如一个人就是一个对象,他有姓名、性别、身高、体重、能吃饭、喝水、说话等信息,这些信息的总和就可以来代表这个人。
对象分为三部分,分别是唯一标识(id)、类型(type)、值(value)。
id:
1. id 用来标识对象的唯一性,每个对象都有唯一的 id。在 CPython 中,id 就是对象的内存地址。
2. 我们可以通过id(clazz)
函数来查看对象的 id,clazz 为需要查看 id 的对象。
3. 对象一旦创建,它的 id 就永远不可改变。
type:
1. 类型用来标识当前对象所属的类型,如 int、str、bool 等。
2. 可以通过type(clazz)
函数来查看对象的类型。
3. Python 是一门强类型语言,对象一旦创建,类型就不可修改。
value:
1. 值就是对象中存储的具体数据。
2. 有些对象的值是可变的,而有些则不可变,据此可将对象非为可变对象和不可变对象两大类。
3. 上面我们学的数值、字符串、布尔类型都属于不可变对象。
2.7.1 变量和对象
在 Python 中并没有将对象直接存储到变量中,而类似给对象取了一个别名。比如整形123是一个对象,有下面一个语句a=123
,这样,a 就是123这个对象,就像一个人,他有一个名字,但这个人并没有被放在名字组成的盒子里,当然,名字也组成不了盒子。
变量中存储的不是对象的值,而是对象的 id ,在 CPython 中也就是对象的地址。当我们使用变量时,实际上是在通过 id 查找对象。为变量赋值其实就像相与更新该变量所指向的地址。
2.8 类型转换
顾名思义,类型转换就是将一个类型的对象转换为另外一个类型的对象。Python 是一个强类型语言,对象创建后类型就不可改变,这看似和类型转换相矛盾,但类型转换并不是直接改变一个对象的类型,而是将对象的值转换类型后创建一个新的对象。也就是说,类型转换并不会改变原有对象。
在 Python 中我们可以使用int()
、float()
、str()
、bool()
这三个函数来进行类型转换。这四个函数需要需要被转换的对象作为入参,获取目标对象的值后创建新的对象并返回。 示例代码如下:
# 字符串转整形,需要注意的是如过不是整形字符串将报错(ValueError)
a = "123"
print(a,type(a)) # 输出:123 <class 'str'>
a = int(a)
print(a,type(a)) # 输出:123 <class 'int'>
# 浮点转整形,会直接舍去小数部分
a = 11.2
print(a,type(a)) # 输出:11.2 <class 'float'>
a = int(a)
print(a,type(a)) # 输出:11 <class 'int'>
# 布尔值转整形(True 转成 1,False 转成 0)
a = True
print(a,type(a)) # 输出:True <class 'bool'>
a = int(a)
print(a,type(a)) # 输出:1 <class 'int'>
# 其他值转字符串
a = True
print(a,type(a)) # 输出:True <class 'bool'>
a = str(a)
print(a,type(a)) # 输出:True <class 'str'>
a = 123
print(a,type(a)) # 输出:123 <class 'int'>
a = str(a)
print(a,type(a)) # 输出:123 <class 'str'>
# float函数和int函数类似
# 任何对象都可以转化成布尔类型,对于表示空的值,如0、""、None等会被转成false,否则就是false
a = ""
print(a,type(a)) # 输出: <class 'str'>
a = bool(a)
print(a,type(a)) # 输出:False <class 'bool'>
a = 111
print(a,type(a)) # 输出:111 <class 'int'>
a = bool(a)
print(a,type(a)) # 输出:True <class 'bool'>
2.9 运算符(操作符)
运算符可以对一个值或多个值进行运算或进行其他操作,可以分为算术运算符、赋值运算符、比较运算符、逻辑运算符、条件运算符(三元运算符)。
2.9.1 算数运算符
加法(+):目标对象是数字时和数学中的运算符一致,目标是字符串时会进行字符串的拼接。
减法(-):目标对象只能是数字,和数学中的减法一致。
乘法(*):目标对象是数字时和数学中的乘法一致,如果将字符串和数字相乘则会对字符串进行复制,详见字符串复制。
幂运算(**):使用两个乘号来表示幂运算,后面的是幂。
除法(/):目标对象只能是数字,除数不能为零,返回结果是一个浮点类型。
整除(//):使用两个除号的方式来表示整除,返回类型为整数,小数部分会被直接舍弃。
取余(%):求两个数相除的余数。
2.9.2 赋值运算符
赋值运算符(=)可以将等号右侧的值赋值给等号左侧的变量,如a = 5
。
a += 5 等价与 a = a + 5,此外还有 a -= 5、a *= 5、a **= 5、a /= 5、a //= 5、a %= 5。
2.9.3 关系运算符
关系运算符用来比较两个值之间的关系,如果关系成立返回布尔类型 True,否则返回 False。
>、>=、<、<=、==、!=
在Python中关系运算符比较数字时,规则和数学中一致。但关系运算符在比较字符串时则是根据 Unicode编码进行逐位比较的。只有在当前位比较不出结果时才会进行下一位的比较。
在Python中,关系运算符比较的是值,而不是对象的id,如果想要比较id需要使用is(相等)和is not(不相等)进行比较。
a is b
a is not b
2.9.4 逻辑运算符
名称 | 符号 | 示例 |
---|---|---|
非 | not | not True(取反,结果为 False) |
与 | and | True and True(两边都为True结果为True,否则为False) |
或 | or | True or True(一个为True,结果就为True) |
Python 中的与和或运算为短路运算符,与运算中,如果第一个是False,则不会在计算右边的表达式。在或运算中,如果第一个是True,则不会在执行右边的表达式。
当我们对非布尔值进行或和与运算时,Python 会将其当成布尔值进行运算,但最后返回的是原值。由于这两个运算符的短路特性,返回的这个原址就是左右两边最后执行的那个表达式的值。如 1and 2 返回 2。
2.9.5 条件运算符(三元运算符)
语法:语句1 if 条件表达式 else 语句2
条件运算符在执行时,会先对条件表达式进行求值判断,如果结果为 True ,则执行语句1并返回执行结果,否则执行语句而并返回结果。
print(1) if False else print(2) ## 输出:2
2.9.6 运算符的优先级
在 Python 中,运算符也是有优先级的,优先级越高对应的表达式越先执行。我们没必要去记运算符的优先级,实际开发中可以像数学运算一样,将需要优先执行的表达式用花括号包裹,这样也有利于代码的可读性。
需要注意的是,在 Python 中,逻辑运算符可以连着写,这是 Python 所独有的,示例如下:
## 下面的表达式相当于b>a且b<c,以 b 为比较的核心
a<b<c
2.10 流程控制语句
控制语句即用来实现对程序流程的选择、循环、转向和返回等进行控制。可以分为"选择语句“,”循环语句“,”转向语句“,”返回语句“四类。
2.10.1 if 语句
在 Python 中,我们可以使用 if 语句来根据条件选择要执行的代码块,语法为:if 条件表达式 : 代码块
。默认情况下,if 语句只会控制紧随其后的那条语句,需要使用 if 语句控制多条语句时就需要使用代码块。
Python 中代码块以缩进开始,直到恢复上一缩进级别结束。示例如下:
## input([arg]) 函数用来接收键盘输入并将值返回,其入参是提示文字,返回值是一个字符串。
## input 函数会阻塞程序,回车后才会继续执行,其返回值是字符串类型需要注意类型的转换,例子中就对其进行了类型## 转换。
num = float(input("输入一个数字: "))
if num > 0:
print("正数")//代码块1
print("正数")//代码块1
elif num == 0:
print("零")//代码块2
else:
print("负数")//代码块3
print("程序结束")//代码块4
上述示例代码的输出如下:
输入一个数字: 2
正数
程序结束
如果需要进行多条件判断,可以使用逻辑运算符连接多个条件作为 if 后的条件表达式。
Python 中我们可以使用两种方式进行缩进,一种是使用 TAB键,一种是使用四个空格。在 Python 官方文档中推荐使用空格进行缩进。
需要注意的是,一定要统一缩进方式。我们可以将编译器的 TAB 键设置为转化为空格。
Sublime Text 只需要在设置中添加
"translate_tabs_to_spaces": true
配置项即可将 TAB 转换为空格。
if 语句后必须跟一个代码块,否则将抛出异常,但我们可以使用 pass 来进行占位,解决这一问题,示例如下:if 3>2: pass ## pass无实际作用,只作为占位使用
2.10.2 循环语句
循环语句可以使指定的代码块重复指定的次数,可分为while循环和for循环。
2.10.2.1 while 循环
while 循环的语法如下:
while 条件表达式 :
代码块
else :
代码块
while语句执行时,会先对 while 后的条件表达式进行求值判断,如果判断结果为 true,则执行循环体(代码块),循环体执行完毕后会继续对条件表达式进行判断,直到不满足条件跳出循环,此时如果有 else 语句则会执行其中的代码块。注意条件表达式恒为 True 时会死循环,一定慎用。
i = 10
while i > 0:
i-=1
print(i)
else :
print("循环执行完了!")
2.10.2.2 for 循环
在 Python 中,for 循环只能用来遍历序列,语法示例如下:
for 变量 in 序列 :
代码块
else:
print("循环执行完了")
for 循环会遍历序列,将其中的元素依次取出赋值给变量,我们可以通过该变量获取列表中的元素,示例代码如下:
## 遍历序列,将名字依次输出
my_list = ["勇太","六花","凸守","七宫","丹生谷"]
for nam in my_list:
print(nam)
2.10.2.3 break 与 continue
break 可以跳出整个循环,continue 跳出本次循环。
需要注意的是,break 与 continue 只对离自己最近的循环生效。
注意:如果不小心写了死循环,可能导致 python 程序在后台一直执行,此时我们可以使用任务管理器进行结束进程。
3 序列
序列(sequence)是 Python 中最基本的一种数据结构(计算机中数据存储的方式),用来保存一组有序的数据。在序列中,每个数据都有一个唯一的位置(索引),该位置按照数据添加的顺序进行分配。
3.1 序列的分类
可变序列:序列中的元素可以改变
> 列表(list)
不可变序列:序列中的元素不可以改变
> 字符串(str)
> 元组(tuple)
3.1.1 列表
列表 (list) 是 Python 中的一个对象,列表中可以保存多个有序的数据。我们可以通过下面的方式创建一个空列表。
## 创建一个空列表,注意这里不能使用 list 因为它是一个函数名,如果使用了会将函数覆盖掉。
my_list = []
print(my_list, type(my_list)) ## 输出结果:[] <class 'list'>
列表存储的数据,我们称之为元素。一个列表中可以存储多个元素,我们可以在创建列表时指定列表中的元素,在 Python 中,列表可以存储任意的对象。示例代码如下:
my_list = [10,"hello",3.14159,print] ## 创建列表时指定元素,多个元素之间使用逗号隔开
print(my_list, type(my_list))
## 输出结果:[10, 'hello', 3.14159, <built-in function print>] <class 'list'>
注意:在 Java 中,列表有类型限制,一个列表只能存储指定类型的对象,但在 Python 中一个列表可以存储任意类型的对象。
列表会按照对象插入的顺序,有序的进行存储,每一个被存储的对象都有一个索引(index),这个索引就是这个对象在列表中的位置。需要注意的是,索引从 0 开始,也就是说,列表中的第一个元素的索引为 0 ,之后依次递增。
既然索引代表了对象在列表中的位置,我们就可以使用索引来获取列表中的元素,示例代码如下:
my_list = [10,"hello",3.14159,print]
print(my_list[1]) ## 输出结果:hello
print(my_list[-1]) ## 输出结果(倒数第一个数据):<built-in function print>
print(len(my_list)) ## 使用len函数获取列表的长度,输出结果:4
注意:如果我们使用的索引超出了最大范围,将会抛出异常:
IndexError: list index out of range
在 Python 中,允许列表的索引为负数,此时将从后往前取列表中的数据,如 -1 即为倒数第一个数据。
3.1.2 元组
元组(tuple)是一个不可变序列,除了不能修改外,其他和列表基本一致。当我们不希望序列中的数据被修改时,就可以使用元组。
元组的创建:使用()
来创建元组。
my_tuple = () ## 创建一个空元组
my_tuple = 10, ## 当元组不是空时,括号可以省略,但需要注意的是,元组中至少包含一个逗号
3.1.3 字符串
暂略
3.2 序列通用操作
Python 中对序列的非修改操作适用于可变/不可变序列,其中较为特殊的为切片于解包。
3.2.1 切片
切片指从现有序列中获取一个子序列,即从“蛋糕”上切下一块。切片的语法为列表[起始索引:结束索引:步长]
或元组(起始索引:结束索引:步长)
或字符串[起始索引:结束索引:步长]
。
使用切片时,注意点如下:
1. 在使用切片获取元素时,会包括起始位置的元素,不会包括结束位置的元素。
2. 做切片操作时,总会返回一个新的序列,不会影响原来的列序列。
3. 起始和结束位置的索引可以省略不写,省略结束位置将截取到最后一个元素,如果省略开始则会从第一个开始截取,如果开始和结束索引都省略,相当于将序列复制了一份。
4. 步长表示每次获取元素的间隔(默认为 1 ),如果步长是 1 则每次获取索引加 1 的元素。
5. 步长不能是 0 但是可以是负数,如果是负数,将从后往前取元素。
## 以列表为例,切片示例
arrs = ["1","2","3","4","5"]
print(arrs[0:2]) ## 输出(不包含结束位置):['1', '2']
print(arrs) ## 输出(不影响原列表):['1', '2', '3', '4', '5']
print(arrs[:2]) ## 输出(省略起始位置):['1', '2']
print(arrs[1:]) ## 输出(省略结束位置):['2', '3', '4', '5']
print(arrs[:]) ## 输出(起始及结束位置都省略):["1","2","3","4","5"]
print(arrs[0:3:2]) ## 输出(步长为正):['1', '3']
print(arrs[2::-1]) ## 输出(步长为负):['3', '2', '1']
print(arrs[0:3:-1]) ## 输出:[]
print(arrs[::-1]) ## 输出:['5', '4', '3', '2', '1']
3.2.2 解包(解构)
解包指将序列中的每一个元素都赋值给一个变量,语法为a,b,*c = 序列
。需要注意的是,进行解包时,变量的数量必须和序列中元素个数是一样的,否则就需要使用一个星号变量接收无法匹配的元素。
my_list = [1,2]
a,b = my_list ## 变量数量必须和序列元素数量一致
print(a)
print(b)
my_list = [1,2,3,4,5,6]
a,b,*c = my_list ## 前两个元素给a和b,剩下的作为列表给c
print(a)
print(b)
print(c) ## 解包对象无论是列表还是元素或字符串,最后返回的都是列表
a,*b,c = my_list ## 第一、第二个元素分别给a和c,其余元素作为列表给b
*a,b,c = my_list ## 倒数第一、第二个元素分别给c和b,其余元素作为列表给a
我们可以使用元组的解包来实现两个变量值的互换。
a = 100
b = 200
a,b = b,a
print(a) ## 200
print(b) ## 100
3.2.3 其他常用操作
## + 操作:将两个列表拼接为一个
test_list = [1,2,3] + [4,5,6]
print(test_list) ## 输出:[1, 2, 3, 4, 5, 6]
## * 操作:将列表重复指定的次数
test_list = [1,2] * 3
print(test_list) ## 输出:[1, 2, 1, 2, 1, 2]
## in:检查指定元素是否存在于列表中,存在返回True,否则返回False
test_list = ["A","B","C","C","D","E"]
print("A" in test_list) ## 输出:True
## not in :检查指定元素是否不在列表中,不在返回 True,否则返回 False
print("F" not in test_list) ## 输出:True
## min 函数:获取列表中的最小值
print(min(test_list)) ## 输出:A
## max 函数:获取列表中的最大值
print(max(test_list)) ## 输出:E
## len 函数:获取列表中的元素个数
print(len(test_list)) ## 输出:6
## index 方法:获取指定元素在列表中第一次出现时的索引
## 有三个入参,第一个为指定元素(必须),第二个为开始查找的索引(可选)
## 第三个为结束查找的索引(可选),同样不包含结束索引的元素
## 如果列表中无查找元素,会抛出异常
print(test_list.index("C",0,3)) ## 输出:2
##print(test_list.index("C",0,2)) ## 抛出异常 ValueError: 'C' is not in list
## count 方法:统计指定元素在列表中出现的次数
print(test_list.count("C")) ## 输出:2
3.3 可变序列的修改
现 Python 版本中,可变序列只有列表,我们可以通过索引、切片、方法三种方式来对其进行修改。
3.3.1 通过索引修改
my_list = [1,2,3]
my_list[0] = 0 ## 将第一个元素修改为 0
print(my_list) ## [0, 2, 3]
del my_list[1] ## 删除索引为 1 的元素
print(my_list) ## [0, 3]
3.3.2 通过切片来修改
需要注意的是,使用切片修改序列时,只能使用序列进行赋值。
my_list = [1,2,3,4,5]
my_list[0:2] = [0,1,2] ## 使用新列表替换切片部分,无指定步长无需保证前后元素个数一致
print(my_list) ## [0, 1, 2, 3, 4, 5]
my_list1 = [1,2,3]
my_list1[0:0] = [0] ## 等价于向索引为 0 的位置插入元素
print(my_list1) ## [0, 1, 2, 3]
my_list2 = [1,2,3,4,5]
my_list2[::2] = [0,0,0] ## 指定步长时,新序列元素个数必须和需要替换的元素个数一致
print(my_list2) ## [0, 2, 0, 4, 0]
通过切片删除元素:
my_list3 = [1,2,3,4,5]
del my_list3[0:2]
print(my_list3) ## [3, 4, 5]
3.4 列表的方法
## append(x) 将 x 添加到目标序列的最后一个元素
my_list = ["四谎","中二病","钢炼","月色真美","高木同学"]
my_list.append("青春之旅")
print(my_list) ## ['四谎', '中二病', '钢炼', '月色真美', '高木同学', '青春之旅']
## insert(index, value) 将 value 插入到序列的 index 索引处,其后内容顺次后移
my_list1 = ["四谎","中二病","钢炼","月色真美","高木同学"]
my_list1.insert(1, "钢琴之森")
print(my_list1) ## ['四谎', '钢琴之森', '中二病', '钢炼', '月色真美', '高木同学']
## extend(t) 使用新的序列对当前序列进行拓展,入参必须为一个序列
my_list2 = ["四谎","中二病","钢炼","月色真美","高木同学"]
my_list2.extend(("悠久之翼","萤火之森")) ## 入参可以是列表、元组、字符串
print(my_list2) ## ['四谎', '中二病', '钢炼', '月色真美', '高木同学', '悠久之翼', '萤火之森']
## clear() 清空序列
my_list3 = ["四谎","中二病","钢炼","月色真美","高木同学"]
my_list3.clear()
print(my_list3) ## []
## pop(index) 根据索引删除并返回指定元素,不指定索引则删除最后一个元素
my_list4 = ["四谎","中二病","钢炼","月色真美","高木同学"]
print(my_list4.pop(2)) ## 钢炼
print(my_list4) ## ['四谎', '中二病', '月色真美', '高木同学']
## remove(value) 删除指定值对应的元素,如果存在多个则只删除第一个
my_list5 = ["四谎","四谎","钢炼","月色真美","高木同学"]
my_list5.remove("四谎")
print(my_list5) ## ['四谎', '钢炼', '月色真美', '高木同学']
## reverse() 反转列表
my_list6 = ["四谎","中二病","钢炼","月色真美","高木同学"]
my_list6.reverse()
print(my_list6) ## ['高木同学', '月色真美', '钢炼', '中二病', '四谎']
## sort() 对列表元素进行排序,默认升序,如果想要升序需要指定入参reverse=False
my_list7 = list("fjalgirgnalg")
my_list7.sort()
print(my_list7) ## ['a', 'a', 'f', 'g', 'g', 'g', 'i', 'j', 'l', 'l', 'n', 'r']
my_list7.sort(reverse=True)
print(my_list7) ## ['r', 'n', 'l', 'l', 'j', 'i', 'g', 'g', 'g', 'f', 'a', 'a']
3.5 range 函数
range 函数可以用来生成一个自然数的序列,语法为range(起始值,结束值,步长)
,起始值默认为 0 ,可省略,步长默认为 1 ,可省略,示例如下:
r = range(5)
print(list(r)) ## [0, 1, 2, 3, 4]
r2 = range(0,5)
print(list(r2)) ## [0, 1, 2, 3, 4]
r3 = range(5,0,-1)
print(list(r3)) ## [5, 4, 3, 2, 1]
我们可以使用 range 函数来实现执行固定次数的for循环,示例代码如下:
for num in range(5):
print(num)
else:
print("循环执行完了")
注意:range 函数生成的元组不包括结束值。
4 可变对象
Python 中,每个对象都保存了三个数据,分别为 id(标识)、type(类型)、value(值)。对于 value 可变的对象,我们称之为可变对象,列表就是一个可变对象。
我们要区分什么是对象,什么是变量。就面向对象编程而言,对象是在内存中占有一定空间,实际存在的数据定义集合。而变量则是一个别名,这个别名能够指向任意对象,指向改变只会影响变量的表现而不会影响对象本身,但对象的改变则会影响指向该对象的所有变量。
以人为例,把一个人的身高、体重、年龄等数据作为一个数据集合就可以代表这个人,即这个对象。不同的人就是一个不同的数据集合,即不同的对象。每个人都有名字,这个名字就是变量,如果张三这个对象长高了,从我们的感官来看,张三这个变量发生了改变,如果某天,张三这个名字被夺走了,原来就那个张三本身不会变,只是不再叫张三了。
在 Python 中,我们要明确修改变量和修改对象的不同,下面将给出示例代码。
a = [1,2,3]
b = a
print("a:",a,id(a)) ## a: [1, 2, 3] 1271192076680
print("b:",b,id(b)) ## b: [1, 2, 3] 1271192076680
## 修改对象,id不会变
b[0] = 10
print("a:",a,id(a)) ## a: [10, 2, 3] 1271192076680
print("b:",b,id(b)) ## b: [10, 2, 3] 1271192076680
## 修改变量(改变变量指向),id会变
b = [10,20,30]
print("a:",a,id(a)) ## a: [10, 2, 3] 1271192076680
print("b:",b,id(b)) ## b: [10, 20, 30] 1271192076744
5 字典
字典(dict)的作用和列表类似,都是用来存储对象容器。但字典是一种新的数据结构,我们称之为映射。字典的特点如下:
1、字典是一个键值对结构,可以存在多个键值对,每一个键值对(key-value),我们称之为一项。在字典中键必须是不重复的不可变对象(int、str、tuple等,常用 str),值则可以是任意对象。
2、字典存储数据的性能较列表差,但查询效率优于列表。因为我们可以通过唯一的 key 快速获取对应的 value,这比列表通过无太大意义的索引查询更有针对性。
3、如果字典中存在重复的 key ,那么后面的键值对将覆盖前面的键值对。如果使用字典中不存在的 key 进行取值,会抛出KeyError: 'names'
异常。
5.1 字典的创建
我们可以通过{}
来创建一个字典,也可以使用dict()
函数来创建字典,示例代码如下:
1. 使用{}
创建字典
使用{}
创建字典时,我们可以直接在花括号内对该字典数据进行初始化,示例代码如下:
my_dict = {} ## 创建一个空字典
print(type(my_dict)) ## <class 'dict'>
my_dict2 = {"keyInfo":"valueInfo"} ## 创建具有一个键值对的字典
my_dict3 = {"name":"张三","age":"28","gender":"男"}
2. 使用dict()
函数创建字典
如果我们不为 dict 函数指定入参,那么创建的就是一个空字典。
my_dict = dict()## 创建一个空字典
print(type(my_dict)) ## <class 'dict'>
我们可以使用param=paramVal
作为 dict 函数的入参来初始化字典数据,其中 param 这个参数名(这里就是 param)将作为键值对的 key,对应的 paramVal 的值将作为 value。
my_dict = dict(name="张三",age="28",gender="男")
print(my_dict) ## {'name': '张三', 'age': '28', 'gender': '男'}
我们还可以把一个序列作为 dict 函数的入参,需要注意的是,这个序列必须是一个每一项都包含两个元素的序列,只有这样,才能将包含两个元素的子项中的第一个元素作为 key ,第二个元素作为 value 生成字典。
my_dict = dict([("name","张三"),("age","28")])
print(my_dict) ## {'name': '张三', 'age': '28'}
注意:如果出现重复的 key , 后出现的将对前面的进行覆盖,最后生成的字典该 key 对应的 value 一定是最后出现那组键值对对应的 value。
5.2 字典的使用
1. 获取字典键值对个数
len 函数能够计算并返回字典键值对个数,示例如下:
my_dict = {"name":"张三","age":"28","gender":"男"}
dict_length = len(my_dict)
print(dict_length) ## 3
2. 检查字典中是否包含指定 key
我们可以使用 key in 字典
的方式来检查指定 key 是否在字典中,也可以使用key not in 字典
的方式来检查指定 key 是否不在字典中。
my_dict = {"name":"张三","age":"28","gender":"男"}
has_key = "name" in my_dict
print(has_key) ## True
not_has_key = "name" not in my_dict
print(not_has_key) ## False
3. 根据 key 获取 value
我们可以直接根据 key 获取 value ,但这种方式下,如果字典中不存在该 key ,将会抛出一个异常:KeyError: 'aaa'
。
my_dict = {"name":"张三","age":"28","gender":"男"}
get_value = my_dict["name"]
print(get_value) ## 张三
4. 使用 get 方法获取 value
get 方法也是通过 key 获取 value ,不同的是字典中无指定 key 是不抛出异常,且可自定义返回值。语法为get(key[, default])
,其中 default 为可选参数,用于指定字典中无指定 key 的返回值。如果我们不填 default ,无指定 key 的返回值默认为 None。
my_dict = {"name":"张三","age":"28","gender":"男"}
get_value1 = my_dict.get("name")
print(get_value1) ## 张三
get_value2 = my_dict.get("name1")
print(get_value2) ## None
get_value3 = my_dict.get("name1","默认值")
print(get_value3) ## 默认值
5. 直接通过 key 修改字典
我们可以直接通过d[key] = value
的方式来修改字典,当指定 key 已存在时对旧数据进行覆盖更新,若不存在则新增键值对。
我们还可以使用del 字典[key]
的方式来删除字典中的键值对,需要注意的是,当要删除的 key 不存在时会抛出异常KeyError: 'a'
。
my_dict = {"1":"a","2":"b","3":"c"}
my_dict["1"] = "A"
print(my_dict) ## {'1': 'A', '2': 'b', '3': 'c'}
my_dict[2] = "B"
print(my_dict) ## {'1': 'A', '2': 'b', '3': 'c', 2: 'B'}
del my_dict["1"]
print(my_dict) ## {'2': 'b', '3': 'c', 2: 'B'}
6. 使用setdefault(key[, default])
添加键值对
setdefault 方法中的 default 是可选参数,默认为 None,代表插入键值对的 value 值。如果添加的 key 已经存在,则该方法返回字典中该 key 对应的 value 值,不进行新增操作,若不存在则进行新增操作并返回 value 值。
my_dict = {"1":"a","2":"b","3":"c"}
resVal = my_dict.setdefault("1","A")
print(resVal) ## a
print(my_dict)## {'1': 'a', '2': 'b', '3': 'c'}
my_dict.setdefault("4")
print(my_dict) ## {'1': 'a', '2': 'b', '3': 'c', '4': None}
resVal1 = my_dict.setdefault("5","E")
print(my_dict) ## {'1': 'a', '2': 'b', '3': 'c', '4': None, '5': 'E'}
print(resVal1) ## E
7. 使用 update 方法添加键值对
我们可以使用 update 方法将其他字典中的键值对添加到当前字典中,如果有重复的 key ,后面的会覆盖前面的 value 值。
a = {"1":"a","2":"b","3":"b"}
b = {"3":"c","4":"d"}
a.update(b)
print(a) ## {'1': 'a', '2': 'b', '3': 'c', '4': 'd'}
8. 使用 popitem 方法删除键值对
popitem 方法无入参,作用是随机删除字典中的一个键值对并以元组的方式返回。这里的随机一般都是删除最后一对键值对。需要注意的是,当删除一个空字典时会抛出异常KeyError: 'popitem(): dictionary is empty'
。
my_dict = {"1":"a","2":"b","3":"c"}
my_dict.popitem()
print(my_dict) ## {'1': 'a', '2': 'b'}
9. 使用 pop 方法删除键值对
pop(key[, default]) 作用是删除指定 key 对应的键值对,并返回该键值对的 value。其中的 default 是可选参数,作用是要删除的 key 不存在时返回默认值。需要注意的是,如果不指定 defaule ,删除不存在的 key 时会抛出异常,指定后不会抛出异常而是返回指定的默认值。
my_dict = {"1":"a","2":"b","3":"c"}
## my_dict.pop("4") 抛出异常 KeyError: '4'
res_val = my_dict.pop("1")
print(my_dict) ## {'2': 'b', '3': 'c'}
print(res_val) ## a
## 删除不存在的 key
res_val = my_dict.pop("4","默认值")
print(res_val) ## 默认值
10. 使用 copy 方法对字典进行浅复制
通过 copy 复制得到的对象与原对象有着一样的内容,但却是两个独立的对象,一般情况下不会互相影响。但 该拷贝方法只是浅复制,只会简单复制对象内部的值,如果值也是一个可变对象,那只会复制该可变对象的引用而不会复制其本身,此时对这个内部可变对象的任何修改其实都是改变的原对象,因为它本就只有一个并没有被复制。
my_dict = {"1":"a","2":"b","3":"c"}
my_dict1 = my_dict.copy()
print(my_dict) ## {'1': 'a', '2': 'b', '3': 'c'}
print(my_dict1) ## {'1': 'a', '2': 'b', '3': 'c'}
my_dict1["1"] = "A"
print(my_dict) ## {'1': 'a', '2': 'b', '3': 'c'}
print(my_dict1) ## {'1': 'A', '2': 'b', '3': 'c'}
5.3 字典的遍历
1. keys 方法
该方法会将字典中所有的 key 放到一个序列中返回,之后我们就可以对该序列遍历,来遍历字典。
my_dict = {"name":"张三","age":"28","gender":"男"}
print(my_dict.keys()) ## dict_keys(['name', 'age', 'gender'])
for k in my_dict.keys():
print(k,my_dict[k])
2. values 方法
该方法会将字典中所有的 value 放到一个序列中返回。
my_dict = {"name":"张三","age":"28","gender":"男"}
print(my_dict.values()) ## dict_values(['张三', '28', '男'])
for v in my_dict.values():
print(v)
3. items 方法
该方法会将字典中的所有键值对作为元组放到序列中,并把该序列返回。我们只需要遍历该序列并对其中的元组解包即可获取相应的值。
my_dict = {"name":"张三","age":"28","gender":"男"}
print(my_dict.items()) ## dict_items([('name', '张三'), ('age', '28'), ('gender', '男')])
for k,v in my_dict.items():
print("k=",v)
6 集合
集合(set)和列表相似,不同点如下:
1. 集合中只能存储不可变对象
2. 集合中存储的对象是无序的,并不会按照元素的插入顺序进行存储
3. 集合中的元素是唯一的,不允许出现重复元素
4. 不能使用索引来操作集合
可以使用{}
来创建一个集合,但需要注意的是如果不指定任何元素,那么空的大括号创建的是一个空字典而不是集合,如果想使用其创建集合,必须指定其中的元素。
s = {}
print(type(s)) ## <class 'dict'>
s1 = {1,2,3}
print(type(s1)) ## <class 'set'>
## s2 = {[1,2,3],[4,5,6]} TypeError: unhashable type: 'list'
我们还可以使用set()
函数来创建一个集合,此时是可以创建一个空集合的,该函数可以将序列和字典转换成集合,但字典只会将 key 添加到集合中。
s = set()
print(s, type(s)) ## set() <class 'set'>
s1 = set([1,2,3]) ## {1, 2, 3}
print(s1)
s2 = set({"1":"a","2":"b","3":"c"})
print(s2) ## {'1', '2', '3'}
序列中能够使用的 in
、not in
、len()
同样适用于集合。此外,我们可以使用一下方法操作集合。
1. 使用 add 方法添加元素
s = set([1,2,3])
s.add(4)
print(s) ## {1, 2, 3, 4}
2. 使用 update 合并新集合到当前集合
该方法的入参可以是序列或字典,也可以是集合。如果是字典,依旧只会使用字典的 key 。
s = set([1,2,3])
s.update({4,5})
print(s) ## {1, 2, 3, 4, 5}
s.update({"6":"f","7":"g"})
print(s) ## {1, 2, 3, 4, 5, '7', '6'}
3. 使用 pop 方法随机删除集合中的一个元素
该方法会随机删除并返回集合中的一个元素。
s = set([1,2,3])
s.pop()
print(s) ## {2, 3}
4. 使用 remove 删除集合中的指定元素
s = set([1,2,3])
s.remove(2)
print(s) ## {1, 3}
除上述方法外,集合同样有清空方法clear()
和浅拷贝方法copy()
,用法与序列的一致。
6.1 集合的运算
在对集合进行运算时,不会影响原来的集合,而是返回运算结果。
1. 交集(&)
两个集合共有的部分,即相交的部分。
my_list = {1,2,3,4}
my_list1 = {3,4,5,6}
result = my_list & my_list1
print(result) ## {3, 4}
2. 并集(|)
求两个集合合并后的集合。
my_list = {1,2,3,4}
my_list1 = {3,4,5,6}
result = my_list | my_list1
print(result) ## {1, 2, 3, 4, 5, 6}
3. 差集(-)
my_list = {1,2,3,4}
my_list1 = {3,4,5,6}
result = my_list - my_list1
print(result) ## {1, 2}
4. 异或(^)
获取只在一个集合中出现的元素。
my_list = {1,2,3,4}
my_list1 = {3,4,5,6}
result = my_list ^ my_list1
print(result) ## {1, 2, 5, 6}
5. 检查子集(<=)
my_list = {1,2,3,4}
my_list1 = {3,4,5,6}
my_list2 = {1,2,3,4,5}
result = my_list <= my_list1
print(result) ## False
result2 = my_list <= my_list2
print(result2) ## True
6. 检查真子集(<)
my_list = {1,2,3,4}
my_list1 = {1,2,3,4}
my_list2 = {1,2,3,4,5}
result = my_list < my_list1
print(result) ## False
result2 = my_list < my_list2
print(result2) ## True
类似检查真子集,>=
检查超级,>
检查真超级。
7 函数
函数也是一个对象,用来保存一些可执行的代码,在需要时对这些语句进行多次调用。
## 创建一个函数入参可为0或多个
def fn(s1, s2) :
print(s1, s2) ## 代码块
## 调用函数
fn("hello", "world")
上述函数定义中的def
是关键字,fn
是函数名,s1、s2
是形参。函数调用中的"hello", "woeld"
是实参。
在我们定义函数时,一般情况下形参和实参个数必须相同,但可以直接使用等号为形参设置默认值,而设置默认值的形参调用函数时可省略不写。默认值只未指定该形参对应的实参时起作用。
def fn(s1, s2 = "test", s3 = "!") :
print(s1, s2, s3)
fn("hello", "world") ## hello world !
7.1 参数的传递方式
在 Python 中,有两种参数传递方式,按特别分为位置参数和关键字参数。
1. 位置参数
将对应位置的实参赋值给对应位置的形参。
def fn(s1, s2) :
print(s1, s2)
fn("hello", "world") ## hello 处在第一个位置,赋给第一个位置的形参
2. 关键字参数
直接使用形参名传递参数,无需按照函数定义中的形参顺序进行传递。位置参数和关键字参数可以混用,但需要注意的是,必须将位置参数写在前面。
def fn(s1, s2 = "test", s3 = "!") :
print(s1, s2, s3)
fn("hello", s3 = ",",s2 = "world") ## hello world ,
注意:在函数中对形参进行重新赋值不会影响实参,但如果形参是一个可变对象,我们在函数中进行了修改,那么将影响到所有指向该对象的变量。
7.2 不定长参数
在定义函数的时候,可以在形参前面加上一个*
,这样的形参是一个元组,元组的内容根据带星号形参位置收集实参。需要注意的是,一个函数中只能存在一个带星号的形参,该形参可以和其他形参配合使用。
1. 星号参数在形参列表末尾
星号参数会收集匹配剩下的所有参数。
def fn(a, b, *c) :
print("a =", a, "b =", b, "c =", c)
fn(1,2,3,4,5) ## a = 1 b = 2 c = (3, 4, 5)
2. 星号参数不在参数列表的末尾
当星号参数不在参数列表末尾时,其后面的所有形参必须是关键字参数。
def fn(a, *b, c) :
print("a =", a, "b =", b, "c =", c)
fn(1,2,3,4,c = 5) ## a = 1 b = (2, 3, 4) c = 5
星号形参只能接收位置参数,我们可以使用双星号形参来接收其他关键字参数(未匹配的关键字参数),并保存到一个字典中。该参数只能存在一个,且必须在参数列表的最后。
def fn(a, b, **c) :
print("a =", a, "b =", b, "c =", c)
## a = 1 b = 2 c = {'c': 3, 'd': 4, 'e': 5}
fn(a = 1, b = 2, c = 3, d = 4, e = 5)
7.3 参数解包
和不定长参数中使用星号对参数进行装包一样,在调用函数时我们可以使用星号对实参进行解包。同样的,序列使用单星号进行解包,字典使用双星号进行解包。
def fn(a, b, c) :
print("a =", a, "b =", b, "c =", c)
my_list = [1,2,3]
## 序列解包
fn(*my_list)
my_dict = {"a":"1","b":"2","c":"3"}
## 字典解包
fn(**my_dict)
7.4 返回值
返回值就是函数执行以后返回的结果,我们可以通过return
来指定函数的返回值。在调用有返回值的函数时,我们可以使用一个变量来接收函数的返回值。
return
后面可以跟任意对象,甚至可以是一个函数。如果仅写一个return
或者不写,相当于return None
。
在函数中,return
后面的代码都不会执行,return
一旦执行,函数自动结束。
def fn() :
print("这是一个函数")
return fn
print(fn())
## 输出:
##这是一个函数
##<function fn at 0x000002A34A702E18>
fn
与fn()
的区别:前者是函数对象,后者是函数调用语句。
7.5 文档字符串
为提高函数的可读性,我们可以使用文档字符串来给函数编辑介绍内容,之后使用help(fanName)
函数查看该文档字符串内容。
为更清晰的表现函数的入参类型及返回值类型,我们还可以在定义函数时指定文档字符串中的函数入参及返回值类型,示例代码如下:
def fn(a:int, b:bool, c:str="hello") -> str :
'''
这是一个文档字符串(使用三个单引号指定)
a:int 表示在文档字符串中展示入参a的类型
-> 表示在文档字符串中展示返回值的类型
'''
print("这是一个函数")
return "10"
help(fn) ## 输出函数的文档字符串
## 输出:
##Help on function fn in module __main__:
##fn(a:int, b:bool, c:str='hello') -> str
## 这是一个文档字符串(使用三个单引号指定)
## a:int 表示在文档字符串中展示入参a的类型
## -> 表示在文档字符串中展示返回值的类型
7.6 作用域
作用域指的是变量生效的区域,在 Python 中一共有两种作用域,分别是全局作用域和函数作用域。
1. 全局作用域
全局作用域在程序执行时创建,在程序执行结束时销毁。所有函数外的区域都属于全局作用域。在全局作用域里定义的变量都属于全局变量。全局变量可以在变量定义语句后的任何位置被访问。
2. 函数作用域
函数内部的区域属于函数作用域,其在函数被调用时创建,在调用结束时销毁。函数每调用一次就会产生一个新的函数作用域。在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
当我们使用变量时,会优先在最近的作用域中寻找变量,如果找不到才会去上一级作用域里寻找。下级作用域可以访问上级作用域的变量,反之则不行。
在函数中为变量赋值时,默认都是为局部变量赋值,如果希望为全局变量赋值,需要使用global
关键之字来声明该变量是全局变量。
a = 10
def fn() :
a = 20
b = 30
print("函数内部a =", a)
print("函数内部b =", b)
def fn1() :
global a ## 声明a是全局变量的a
a = 30
print("函数外部a =", a)
fn()
fn1()
print("函数外部a =", a)
print("函数外部b =", b)
## 输出:
## 函数外部a = 10
## 函数内部a = 20
## 函数内部b = 30
## 函数外部a = 30
## Traceback (most recent call last):
## File "demo1.py", line 14, in <module>
## print("函数外部b =", b)
## NameError: name 'b' is not defined
7.7 命名空间
命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中。每一个作用域都会有一个它对应的命名空间。
全局命名空间用来保存全局变量,函数命名空间用来保存函数中的变量。而命名空间实际上就是一个专门用来存储变量的字典。
我们可以使用locals()
函数来获取当前作用域的命名空间,使用globals()
函数在任意位置获取全局命名空间。
scope = locals() ## 获取当前命名空间
print(type(scope)) ## 输出:<class 'dict'>
def fn() :
scope = locals() ## 获取当前命名空间
global_scope = globals() ## 获取全局命名空间
global_scope["c"] = "c" ## 等价于创建一个全局变量,可以直接通过获得的字典操作命名空间,但不建议这么做
7.8 高阶函数
在 Python 中,函数是一等对象,一等对象有一下特点:
1. 对象是在运行时创建的。
2. 能赋值给变量或作为数据结构中的元素。
3. 能作为参数传递。
4. 能作为返回值返回。
并非所有变成语言中,函数都是一等对象。
Python 是一门支持函数式编程的语言(以函数为主进行编程),而支持函数式编程必须支持高阶函数。高阶函数必须满足如下特点中的一个。
1. 接收一个或多个函数作为参数。
2. 将函数作为返回值返回。
my_list = [1,2,3,4,5,6,7,8,9]
def fn1(i) :
'''
判断是否为偶数,是返回True,否则返回False
'''
return i % 2 ==0
def fn2(i) :
'''
判断是否大于5,是返回True,否则返回False
'''
return i > 5
def fn(myFun,my_list) :
'''
根据指定规则从指定序列中取数据放到新序列并返回
myFun 为指定函数
my_list 为指定序列
'''
my_list_new = []
for i in my_list :
if(myFun(i)) :
my_list_new.append(i)
return my_list_new
print(fn(fn1, my_list)) ## [2, 4, 6, 8]
print(fn(fn2, my_list)) ## [6, 7, 8, 9]
7.8.1 闭包
闭包就是能够读取其他函数内部变量的函数,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。我们可以将一些私有数据藏到闭包中。
高阶函数的一种形式是将函数作为返回值进行返回,这就是一个闭包。
my_list = (1,2,3,4)
def make_sum(my_list) :
sunRes = []
def sumFn(my_list) :
sunRes.append(sum(my_list))
return sunRes
return sumFn
sumFun = make_sum(my_list) ## 返回值是一个函数
print(sumFun(my_list))
7.9 匿名函数
在 8.8 节例子中,fn()
函数可以不用实现,在 Python 中提供了一个filter(function, iterable)
函数用于从序列中过滤出符合条件的元素并保存到一个新序列返回。
filter 函数,第一个参数为函数,第二个是一个可迭代的对象。对上一节的例子改造后如下:
my_list = [1,2,3,4,5,6,7,8,9]
def fn1(i) :
'''
判断是否为偶数,是返回True,否则返回False
'''
return i % 2 ==0
def fn2(i) :
'''
判断是否大于5,是返回True,否则返回False
'''
return i > 5
print(filter(fn1, my_list)) ## <filter object at 0x0000022C41DC9128>
print(list(filter(fn1, my_list))) ## [2, 4, 6, 8]
filter 函数的返回值是一个可迭代的对象,并不是一个序列。其不仅可以过滤序列,也可以过滤其他可迭代的对象。
上面的fn1
及fn2
函数,实际上只是使用一次就遗弃了,我们在专门定义一个函数显得有点多余。我们可以使用匿名函数来代替它们。
匿名函数就要使用到lambda
函数表达式,这是一个语法糖,详细内容可搜索了解。
匿名函数的语法:lambda 逗号隔开的参数列表 : 返回值
my_list = [1,2,3,4,5,6,7,8,9]
lambda i : i % 2 ==0 ## 创建一个匿名函数
res = (lambda i : i % 2 ==0)(2) ## 调用一个匿名函数,注意前后两个括号,不建议这样用
print(res) ## True
## 经匿名函数改造后
print(filter(lambda i : i % 2 ==0, my_list)) ## <filter object at 0x0000026E4E9D9048>
print(list(filter(lambda i : i % 2 ==0, my_list))) ## [2, 4, 6, 8]
和filter
函数类似的还有map
函数。该函数可以对可迭代对象中的所有元素做指定操作,然后返回一个新的可迭代对象。当然,指定操作也和filter
一样,需要通过一个函数指定,同样可以使用匿名函数。
my_list = [1,2,3,4,5,6,7,8,9]
## 输出:[2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(map(lambda i : i+1, my_list)))
sort()
方法能够对列表中的元素进行排序,默认情况下直接比较列表中的元素大小,此时如果列表中存储的数据类型不统一就可能抛出异常。
my_list = ["aaa","bbb","ccc",1]
my_list.sort()
print(my_list)
## 输出
##Traceback (most recent call last):
## File "demo1.py", line 2, in <module>
## my_list.sort()
##TypeError: '<' not supported between instances of 'int' and 'str'
sort()
方法也具备和filter()
函数类似的能力,其有一个key
关键字参数,该参数需要一个函数的实参,key
关键字对应的函数会对列表中的每项进行处理,之后sort()
方法根据处理返回值进行比较排序。
my_list = ["aaa","bbb","ccc",1]
my_list.sort(key = str) ## 将每个元素转化为 str 后进行排序
print(my_list)
## 输出
##[1, 'aaa', 'bbb', 'ccc']
上面的sort()
方法是列表所独有的,会直接影响到列表中的数据(无法对不可变对象进行排序)。在 Python 中,还提供了一个可以对所有可迭代对象进行排序的函数sorted(iterable, *, key=None, reverse=False)
,该函数会返回一个新的排序后的可迭代对象。通常我们只需要传入可迭代对象和key
(和sort()
方法中的作用一致)对应的函数即可。
my_list = ("aaa","bbb","ccc",1)
my_list_sort = sorted(my_list, key = str)
print(list(my_list_sort))
## 输出
## [1, 'aaa', 'bbb', 'ccc']
7.10 装饰器
Python 装饰器的作用是在不改变原函数的情况下扩展函数功能,为此我们可以实现一个生成扩展后函数的函数,这个函数就是装饰器。我们可以通过调用装饰器得到扩展后的函数并执行。
def printFun() :
print("hello......")
def addFun(a, b) :
return a + b
def decoratorFun(oldFun) :
'''
定义装饰器,在函数执行前后输出提示文字
*args 装包位置参数
**kwargs 装包关键字参数
'''
def newFun(*args, **kwargs) :
print("方法开始执行......")
res = oldFun(*args, **kwargs)## *args 解包位置参数 **kwargs 解包关键字参数
print("方法执行结束......")
return res
return newFun
## 通过调用装饰器获取新函数方式使用扩展后的功能
new_printFun = decoratorFun(printFun)
new_printFun()
new_addFun = decoratorFun(addFun)
addRes = new_addFun(1, 2)
print(addRes)
还可以使用@装饰器
的方式直接装饰一个函数(被装饰的函数已经不再是原函数了,已具备扩展后功能)。在使用@装饰器
对函数进行装饰时,我们可以为一个函数指定多个装饰器,按照由近及远生效,一层层完成装饰。
def decoratorFun(oldFun) :
'''
定义装饰器,在函数执行前后输出提示文字
*args 装包位置参数
**kwargs 装包关键字参数
'''
def newFun(*args, **kwargs) :
print("方法开始执行......")
res = oldFun(*args, **kwargs)## *args 解包位置参数 **kwargs 解包关键字参数
print("方法执行结束......")
return res
return newFun
def decoratorFun1(oldFun) :
'''
为验证同一个函数指定多个装饰器
'''
def newFun(*args, **kwargs) :
print("方法开始执行1......")
res = oldFun(*args, **kwargs)## *args 解包位置参数 **kwargs 解包关键字参数
print("方法执行结束1......")
return res
return newFun
## 使用 @装饰器 的方式直接对函数进行包装
@decoratorFun
@decoratorFun1
def printFun() :
print("hello......")
## 调用函数
printFun()
## 输出结果:
##方法开始执行......
##方法开始执行1......
##hello......
##方法执行结束1......
##方法执行结束......
8 面向对象(OOP)
Python 是一个面向对象的编程语言。所谓的面向对象语言,我们可以理解为语言中的所有操作都是通过对象来进行的。
面向过程的编程思想将一个功能分解为一个个小的步骤,通过完成这一个个步骤来完成一个程序。面向对象语言关注的是对象,而不是过程。
8.1 类(class)
类,简单的理解其就相当于一个图纸/模板,在程序中,我们需要根据类来创建对象,因此又称对象是类的实例(instance)。前面的int
、float
、bool
等都是类,a = int(1)
即是创建一个 int 类的示例。
类也是一个对象,是一个用来创建对象的对象。其是一个 type 类型的对象,定义类实际上就是定义了一个 type 类型的对象。
上面提到的类都是内置类,我们还可以自定义类。需要注意的是,自定义的类名需要使用大驼峰命名法进行命名。自定义类的语法如下:
class 类名([父类]) : ## []代表可省略不写,没有父类时 () 也可以省略不写
代码块
创建一个简单类的代码如下:
## 定义一个简单类
class MyClass() :
pass
print(MyClass) ## 输出:<class '__main__.MyClass'> 解析:class表示这是一个类,main表示主文件
print(id(MyClass), type(MyClass)) ## 输出:1498412783064 <class 'type'>
## 创建对象
mc = MyClass()
print(mc, type(mc)) ## 输出:<__main__.MyClass object at 0x0000025CE1664FD0> <class '__main__.MyClass'>
## 使用 isinstance(对象,类)函数检查一个对象是否是一个类的示例
isInst = isinstance(mc, MyClass)
print(isInst) ## 输出:True
我们可以向对象中添加变量(对象中的变量称为属性),语法为:对象.属性名 = 属性值
,也可以通过对象.属性名
来读取属性值。
## 定义一个简单类
class MyClass() :
pass
## 创建对象
mc = MyClass()
## 为对象设置属性
mc.name = "张三"
## 读取对象的属性
print(mc.name) ## 张三
8.1.2 类的定义
类和对象都是对现实生活中或程序中内容的抽象,实际上所有的事物都有两部分构成,分别是数据(属性)和行为(方法)。
在类的代码块中,我们可以定义变量和函数,变量会成为该类示例的公共属性,可以通过对象.属性名
的形式访问。函数会成为该类实例的公共方法,可以通过对象.方法名()
的形式调用。
方法还可以通过类名.方法名()
的方式调用,但该方式不会自动传递第一个参数,需要我们指定一个实例对象。如A.test(a)
,其中 a 是 A 的一个实例。
注意:方法调用时,第一个参数由解析器自动传递,因此定义方法时,至少要定义一个形参。方法的第一个参数就是调用方法的对象本身,我们一般命名为self
当我们访问一个对象的属性/方法时,解析器会先在当前对象中寻找是否含有该属性,找不到则到当前对象的类对象中寻找,如果依旧找不到则报错。
在 Python 中,类的属性是公共的,这并不符合实际,不同的对象属性一般也应该是不同的。因此,我们可以借助特殊方法(魔术方法)来完成属性的定义。
特殊方法都是以双下划线__
开头并以双下划线结尾的,且无需主动调用,会在特殊的时刻自动调用。
__init__()
方法会在对象创建后立即执行,我们可以使用该特殊方法为对象初始化属性。
class ClassName():
## 公共属性
publicData = "123"
## 初始化方法,name 为对象私有属性,可有多个,逗号隔开
def __init__(self, name):
self.name = name
print("init 方法初始化 name 属性为->", name)
## 其他方法
def method_1() :
print("hello word!")
## 创建对象时要根据 init 方法入参传递初始化参数,self 为解析器自动传入
class_1 = ClassName("张三")
print(class_1.name)
8.2 封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。
上面的示例中,类的属性可以直接使用对象.属性名
的方式进行赋值,这样就会存在一些安全隐患,对于一些私有数据,不应该直接允许外部访问修改。我们可以采用隐藏属性名的方式进行封装。
class ClassName():
def __init__(self, name) :
## 属性名前加下划线,主要作用是隐藏属性名并标记这是一个私有属性,不能随便修改
self._name = name
print("init方法中初始化数据")
## 提供外部访问接口
def get_name(self) :
return self._name
def set_name(self, name) :
self._name = name
class_1 = ClassName("张三")
## 使用暴露的接口修改属性
class_1.set_name("zhagnsan")
print(class_1.get_name())
在 Python 中可以在属性名前加双下划线来对属性进行隐藏,其本质也是 Python 对其进行更名。但该方式不推荐使用, 我们可以使用上述例子中的方式加单下划线的方式来隐藏属性名并标记这是一个私有属性。
8.3 property 装饰器
我们可以使用property
装饰器对getter
和setter
方法进行装饰,使其可以像属性一样使用。该装饰器的作用就是将被修饰的方法转换为对象的属性,但需要注意的是,使用 property 装饰器的方法,必须和属性名是一样的。getter
方法使用@property
进行装饰,setter
方法使用@属性名.setter
进行装饰。
注意:setter
装饰器依赖setter
装饰器。
class ClassName():
def __init__(self, name, age) :
self._name = name
self.age = age
## 装饰getter方法
@property
def name(self) :
return self._name
## 装饰setter方法,该装饰器依赖getter装饰器
@name.setter
def name(self, name) :
self._name = name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
class_1 = ClassName("张三","18")
print(class_1.name, class_1.age)
class_1.age = 28
print(class_1.name, class_1.age)
上面的方法都是实例方法,我们还可以使用@classmethod
装饰器来定义一个类方法。类方法也同样有一个默认传递的参数,与实例方法默认传递实例对象类似,类方法默认传递类对象,一般我们写为cls
。和实例方法一样,类方法可以通过类和实例调用。
class A :
## 定义一个类方法
@classmethod
## 定义方法时,需要标明默认传递的第一个参数,由于是类对象,一般写为cls
def test(cls):
print("这是一个类方法。")
a = A()
A.test() ## 这是一个类方法。
a.test() ## 这是一个类方法。
@staticmethod
装饰器用来标明一个静态方法,被其修饰的方法无默认参数,其本质是一个存储在类中的函数。静态方法同样可以使用类和实例调用,该类方法基本和当前类无关,一般是一个工具方法。
class A :
@staticmethod
def test():
print("这是一个静态方法")
a = A()
A.test()
a.test()
8.4 继承
继承是面向对象的三大特性之一,通过继承,我们可以使一个类获取到其父类的属性和方法。在 Python 中可以在类名后的括号中指定当前类的父类,进而继承其所用的属性和方法。
声明类时如果未在括号里指定父类或直接省略了括号,则等价与在括号中指定object
父类,即object
是所有类的父类。
class ParentClass:
def aFun(self):
print("父类中的方法")
## 指定父类
class Subclass(ParentClass):
pass
subClass = Subclass()
## 调用继承的方法
subClass.aFun();
8.4.1 方法的重写
如果在子类中又和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这就是方法的重写。
当我们调用一个对象的方法时,会先在当前对象中寻找该方法,如果不存在则去父类中寻找,如果找到 object 类中依旧不存在则报错。
class ParentClass:
def aFun(self):
print("父类中的方法")
class AAA(ParentClass):
def aFun(self):
print("AAA")
class BBB(AAA):
pass
clazz = BBB()
clazz.aFun();## AAA
8.4.2 super()
父类中的所有方法都会被子类继承,包括特殊方法。对于__init__()
方法,如果子类中新增了属性,就需要对父类的该方法进行重写,此时最好的方式是使用使用父类的 init 方法来加载父类中有的属性,子类中只需要加载新增属性即可。
super()
可以用来获取当前类的父类,通过 super()
返回对象调用父类方法时,不需要传递 self 指明当前对象。
class ParentClass:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
class AAA(ParentClass):
## 重写父类的初始化方法
def __init__(self, name, age):
## 使用父类的初始化方法加载原属性
super().__init__(name)
## 加载拓展属性
self._age = age
pass
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
clazz = AAA("张三", 28)
print(clazz.name, clazz.age) ## 张三 28
8.4.3 多重继承
Python 支持多重继承,即一个类可以指定多个父类。我们只需要在声明类时在括号后指定多个父类(逗号隔开)即可。多重继承会使子类同时拥有多个父类的方法,如果有同名的则指定父类列表的第一个的方法将覆盖其他的。
我们可以使用类名.__bases__
来查看类的所有父类。
class A :
def test(self):
print("AAA")
class B :
def test(self):
print("BBB")
def test1(self):
print("BBB_TEST1")
## 指定多个父类
class C(A, B):
pass
c = C()
## 测试同名方法使用父类列表第一个父类的
c.test() ## AAA
## 测试继承方法
c.test1() ## BBB_TEST1
print(C.__bases__) ## (<class '__main__.A'>, <class '__main__.B'>)
8.5 多态
多态是面向对象的三大特征之一,意思就是多种形态,即一个对象可以以不同的形态去呈现。如狗可以表现为狼狗、藏獒、哈士奇等。
9 垃圾回收
在程序中,没有被引用的对象就是垃圾,这种垃圾过多会影响程序的运行性能。垃圾回收机制就是将垃圾对象从内存中删除。
在 Python 中,在对象垃圾回收前会调用一个特殊方法__del__()
。
class A :
def __init__(self, name):
self._name = name
def __del__(self):
print("A()对象被删除了", self)
a = A("张三")
print(a._name)
## 将 a 设置为 None ,此时没有任何变量引用 A() 对象,它就变成了垃圾,回收前调用del特殊方法
a = None
10 特殊方法
特殊方法也称为魔术方法,其都是使用双下划线开头和结尾的,一般不需要手动调用,会在一些特殊情况下自动执行。我们可以看 Python 帮助文档中的The Python Language Reference -> Data model -> 3.3. Special method names
章节来详细了解特殊方法,这里只对其中几个进行介绍。
1. __str__()
该特殊方法会在尝试将对象转换为字符串的时候调用,用于指定对象转换为字符串的结果。在我们打印一个对象时,实际上打印的就是该特殊方法的返回值。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def __str__(self):
return "Person[name=%s , age=%s]"%(self._name,self._age)
p1 = Person("张三",28)
print(p1) ## Person[name=张三 , age=28]
2. __repr__()
该特殊方法和上面的 str 方法很相似,其在使用 repr() 函数时调用,作用是指定对象在“交互模式”中直接输出的结果。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def __repr__(self):
return "Person[name=%s , age=%s]"%(self._name,self._age)
p1 = Person("张三",28)
print(repr(p1)) ## Person[name=张三 , age=28]
3. 比较运算
在 Python 中,如果想使用小于、大于等运算符来比较两个自定义对象时,需要运算符支持。我们可以定义相关的特殊方法,用于支持运算符,即在使用运算符时调用相应的特殊方法。根据帮助文档我们可以看到相应的特殊方法。
object.__lt__(self, other) ## 小于
object.__le__(self, other) ## 小于等于
object.__eq__(self, other) ## 等于
object.__ne__(self, other) ## 不等于
object.__gt__(self, other) ## 大于
object.__ge__(self, other) ## 大于等于
上述方法会在使用比较运算符的时候调用,需要我们在自定义类中给出实现。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
## 定义小于运算符调用的特殊方法,如果无该方法,使用小于运算符将报错
def __lt__(self, other):
return self._age<other._age
p1 = Person("张三",18)
p2 =Person("李四",28)
print(p1 < p2) ## True
4. __bool__()
该特殊方法用于指定对象转换为布尔值的结果。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
## 定义对象转换为布尔类型的规则
def __bool__(self):
return self._age > 17
p1 = Person("张三",16)
print(bool(p1)) ## False
p2 = Person("李四",18)
print(bool(p2)) ## True
特殊方法还有很多,详细的可以参看 Python 的帮助文档。
11 模块(module)
模块化指将一个完整的程序分解为一个一个小的模块,通过模块组合来搭建一个完整的程序。采用模块化可以将程序分别编写到多个文件中。
在 Python 中,一个 py 文件就是一个模块,创建模块,实际上就是创建一个 Python 文件。需要注意的是,模块名要符合标识符规范。
可以使用import 模块名
的方式为当前模块引入外部模块,这里的模块名就是 Python 文件的名字(仅名字,不带后缀)。
import 语句可以再程序的任意位置调用,但一般会统一写在程序的开头,方便管理和阅读。
## module_1.py 文件代码
print("模块一", 'hello word!')
## module_2.py 文件代码
import module_1 ## 引入模块一
print(module_1)
## 直接运行module_3.py得到的输出结果为:
## 模块一 hello word!
## <module 'module_1' from 'D:\\WorkSpace\\PythonLearn\\module_1.py'>
我们可以多次引用同一个模块,但只会创建一个模块实例,也就是说,引入的模块是一个单例。
我们可以使用as
为引入的模块取一个别名,如上述例子中,我们就可以为module_1
取一个别名。此时再使用原模块名将会报错,只能使用新取的名字。
import module_1 as mod1
print(mod1) ## <module 'module_1' from 'D:\\WorkSpace\\PythonLearn\\module_1.py'>
print(module_1) ## NameError: name 'module_1' is not defined
在每一个模块内部都有一个__name__
属性,我们可以通过该属性获取到模块的名字,需要注意的是,主模块的模块名统一为__main__
,非主模块的模块名则就是文件名。
主模块就是我们直接通过 Python 执行的模块,一个程序中只有一个主模块。
11.1 模块的使用
我们可以通过模块名.变量名
或模块名.函数名
或模块名.类名
来使用模块中的相应内容。
## module_1.py 文件代码
a = 10
def fun():
print("module_1的fun函数")
class ClassTest(object):
def __init__(self, name):
self.name = name
## module_2.py 文件代码
import module_1 as mod1
print(mod1.a) ## 10
mod1.fun() ## module_1的fun函数
clazz = mod1.ClassTest("张三")
print(clazz.name) ## 张三
上述例子中,使用模块中的功能时较为麻烦,我们如果想直接使用变量、函数等,可以不整个模块引入,只引入相关内容,语法为from 模块名 import 变量名/函数名/类名
多个引入内容用逗号隔开,同样可以使用as为为引入的内容取别名,语法为from 模块名 import 变量名/函数名/类名 as 别名
。别名可以解决同名覆盖问题。对上述例子进行改造后如下:
## module_1.py 文件代码
a = 10
def fun():
print("module_1的fun函数")
class ClassTest(object):
def __init__(self, name):
self.name = name
## module_2.py 文件代码
from module_1 import a,fun,ClassTest
print(a) ## 10
fun() ## module_1的fun函数
clazz = ClassTest("张三")
print(clazz.name) ## 张三
我们还可以使用from 模块名 import *
引入模块中的所有内容,这样引入后也可以直接使用模块中的变量/函数/类等内容。需要注意的是,该方式会将其他模块的所有内容引入到当前模块并对当前模块的同名内容进行覆盖,因此不建议使用该方式进行引用。
通过import *
引入模块时,我们可以在变量/函数/类前加下划线来标明该内容为私有,不允许其它模块使用。注意只有在使用import *
引入模块时才生效。
一般,模块中都会有一些测试代码,该部分代码应该在以该模块为主模块运行时才执行,其他模块引用时不执行,此时我们就可以使用到上面说说的__name__属性来判断模块名,进而控制代码执行。
a = 10
def fun():
print("module_1的fun函数")
class ClassTest(object):
def __init__(self, name):
self.name = name
## 测试方法
def test():
print("这是module_1的测试方法")
## 判断当前模块是否已主模块运行,是则执行测试代码
if __name__ == "__main__":
test()
11.2 包(package)
包也是一个模块,当我们模块中的代码过多时,或者一个模块需要被分解为多个模块时,就需要使用到包。
普通的模块是一个 py 文件,包则是一个文件夹。需要注意的是包中必须要有一个__init__.py
文件,该文件用于标识当前文件夹是一个包,可以包含包中的主要内容,
使用模糊引入,即import 包名
或from 包名 import *
时,__init__.py
文件中的内容默认被引入,可以通过模块名.
进行访问。也就是说,我们可以在这个包初始化文件中指定模糊引入的基础内容,但是需要注意的是,尽量保证该初始化文件的简单。
## a.py
a = 10
## __init__.py
b = 20
## module_2.py
import hello ## 模糊导入整个包,默认导入初始化文件__init__.py中的内容
from hello import a ## 指定导入包中的a模块,包中的其他模块需要这样精确导入
print(a.a) ## 包中其他模块中的内容需要精确导入包后通过包名访问
print(hello.b) ## __init__.py文件中的内容是包初始化内容,可直接包名.内容名访问
__pycache__
是模块的缓存文件。Python 代码在执行前,需要被解析器先转换为机器码,然后执行机器码,使用模块(包)时也需要先将模块(包)的代码转换为机器码。如果每次运行都重新转换,效率将受到很大的影响,因此将编译的内容放到缓存文件中,下次加载无需重新编译,进而提高性能。
11.3 标准库
为了实现开箱即用的思想,Python 中提供了一个标准库。在这个标准库中,有很多强大的模块供我们直接使用。标准库会随 Python 的安装一同安装。
我们可以翻阅 Python 帮助文档的 Python Module Index 章节内容查看所有的标准库信息,这里只对几个标准库进行介绍。
标准库也是一个个的模块,引入标准库就是引入相应的模块,因此语法是一致的。
11.3.1 sys 标准库
sys 标准库提供了一些变量和函数,供我们获取 Python 解析器的信息。
1. sys.argv 属性
该属性用于存储执行代码时,命令行中所包含的参数。该属性是一个列表,列表中保存了当前命令的所有参数。如我们有一个名为test.py
的文件,该文件中有一条输出argv
属性的语句,我们通过命令运行该文件并携带参数,即可输出命令的所有参数。
test.py 文件的内容如下:
## 导入 sys 标准库
import sys
print(sys.argv)
命令运行 test.py文件的结果如下:
D:\WorkSpace\PythonLearn>python test.py aaa bbb ccc
['test.py', 'aaa', 'bbb', 'ccc']
test.py aaa bbb ccc
都为 python
命令携带的参数,多个参数时间使用空格隔开。
2. sys.modules 属性
该属性用于存储当前程序中引入的所有模块,其是一个字典,key
是模块的名字,value
是模块对象。
import sys
print(sys.modules)
我们运行后可以发现,输出的字典可读性很差,此时我们可以使用 Python 提供的的一个pprint
模块的pprint()
函数输出简单格式化后的字典。
import sys
from pprint import pprint
pprint(sys.modules)
3. sys.path 属性
该属性存储的是模块的搜索路径,其是一个列表。当我们引入一个模块,在运行该文件时,解释器会按照sys.path
中存储的搜索路径依次搜索这个引入的模块。
import sys
from pprint import pprint
pprint(sys.path)
上述示例输出内容为:
['D:\\WorkSpace\\PythonLearn',
'D:\\SoftWareInstallation\\python36\\python36.zip',
'D:\\SoftWareInstallation\\python36\\DLLs',
'D:\\SoftWareInstallation\\python36\\lib',
'D:\\SoftWareInstallation\\python36',
'D:\\SoftWareInstallation\\python36\\lib\\site-packages']
也就说说,Python 解释器会现在当前运行文件所在目录寻找模块,若未寻到则按次序依次寻找。
4. sys.platform 属性
该属性用于存储当前 Python 运行的平台。可以在 Python 帮助文档中搜索该属性,查看其可能返回的值。
import sys
## 运行平台为windows
print(sys.platform) ## 输出:win32
5. sys.exit() 函数
该函数用来推出程序,且我们可以在括号内指定退出提示语。
import sys
sys.exit("exit") ## 退出程序并给出括号内指定的提示语
print("test") ## 该输出语句不会输出,因为上一行已退出程序
11.3.2 os 模块
该模块提供了访问操作系统的能力。
1. os.environ 属性
该属性可以获取到系统的环境变量。
import os
print(os.environ)
2. os.system() 函数
该函数用来执行操作系统的命令。
import os
## 执行windows系统的dir命令
os.system("dir")
12 异常
程序在运行过程中,不可避免的会出现一些错误,比如使用了不存在的索引、除数为零等。这些错误在程序中我们称之为异常。
在程序运行过程中,一旦出现异常,将会导致程序立即终止,异常后的代码都不会在执行。
异常的目的不是使程序停止,而是提醒此处可能存在的问题,在编写代码时考虑并处理这个可能存在的问题。
程序运行出现异常后,所有异常信息会被保存到一个专门的异常对象中,异常传播时,实际上就是将异常对象抛给了调用方。我们可以在 Python 帮助文档的The Python Standard Library -> 5. Built-in Exceptions
章节查看 Python 中提供的所有异常类信息。
12.1 异常的处理
我们可以使用try...except...
来处理异常,语法如下:
try:
pass ## 可能出现异常的代码块
except 异常类 as e: ## 捕获异常并使用as取别名,如果except后什么都不写,会捕获所有异常,except可以有多个用于捕获多个异常
raise ## 出现异常后的处理
except 异常类 as e1: ## except可以有多个用于捕获多个异常,还可以直接使用一个捕获所有异常的父类Exception
raise ## 出现异常后的处理
else:
pass ## 未出现异常执行的代码块
finally:
pass ## 无论有没有异常都执行的代码块
经过处理的异常不会导致程序的终止,这样我们定义异常处理规则后就可以保证程序正常执行下去。
print("aaa")
try:
10/0
except Exception as e: ## 捕获异常并取使用as取别名
print("异常了。。。", e)
else:
print("程序正常执行,无异常")
finally:
print("end")
print("bbb")
当函数中出现异常时,其会将异常抛给调用方,导致调用方出现异常,调用方又会继续向外抛出异常,直至传递到全局作用域,导致程序终止并显示异常信息。但如果我们使用try
对异常处理了,这个处理后的异常将不会再向外传播。
12.2 抛出异常
在 Python 中,我们可以使用 raise 抛出一个异常,其后面可以跟一个异常类或异常类的实例。我们还可以同通过继承一个异常类来自定义自己的异常类。
## 自定义异常类,这里只给出示例
class MyException(Exception):
pass
def testFun():
print("tesrFun 开始执行");
## 抛出异常异常,raise后跟一个异常实例
raise MyException("这是一个自定义异常")
print("tesrFun 执行结束");
testFun()
13 文件
13.1 文件的读取
1. 打开文件
在 Python 中我们可以使用 open()
函数来打开一个文件。该函数的详细介绍可以参看 Python 帮助文档的The Python Standard Library -> Built-in Functions -> open()
部分的内容。
## 文件名:和当前py文件在同一路径下可直接使用文件名字,否则需要指定路径
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
file_obj = open(file_name) ## 打开指定文件,返回值是文件对象
## <_io.TextIOWrapper name='D:\\WorkSpace\\PythonLearn\\demo.txt' mode='r' encoding='cp936'>
print(file_obj)
2. 关闭文件
打开后的文件一定要进行关闭,否则可能出现重复打开同一文件,导致资源浪费。关闭文件只需要使用文件对象调用close()
方法即可。文件关闭后将不能在针对该文件进行任何操作,如果需要操作需要重新打开。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
file_obj = open(file_name) ## 打开指定文件,返回值是文件对象
## 读取文件内容,read() 方法会将文件中的内容作为字符串返回
content = file_obj.read()
print(content)
## 关闭文件
file_obj.close()
## 关闭文件后再进行读取操作将报错
content = file_obj.read() ## ValueError: I/O operation on closed file.
上面关闭文件的方式存在一定的弊端,在实际开发中,我们可能会忘记这个关闭操作,为避免这一情况,我们可以使用with...as...
语句来打开、操作文件。当该语句结束后,文件会自动调用 close 进行关闭。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
## 打开文件,赋值给file_obj
with open(file_name) as file_obj:
## 该代码块用于操作文件
print(file_obj.read())
## with...as... 语句外操作文件报错,此时文件已关闭
content = file_obj.read() ## ValueError: I/O operation on closed file.
3. 文件简单读取
文件可以大致分为两种,分别是纯文本文件(使用utf8等编码编写的文本文件)和二进制文件(图片、MP3等)。在我们调用open()
打开一个文件时,默认以文本文件的形式打开,但其默认编码为None
,此时读取纯英文文件内容无问题,但读取有着较多编码格式的中文文件时就会出问题。为避免这一问题,我们在打开文件时应指定编码格式。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
try:
## 打开文件时指定编码
with open(file_name, encoding="utf-8") as file_obj:
content = file_obj.read()
print(content)
except FileNotFoundError as e:
print(f"{file_name}文件不存在!")
4. 读取大文件
使用read()
直接读取文件中的内容,会将所有内容全部读取出来加载到内存中,如果文件较大,会导致内存泄漏。因此,我们在使用read()
读取大文件时需要指定每次读取的字符长度,其默认值为-1
会读取所有内容。需要注意的是,指定每次读取的字符数量后其每次读取都是从上一次读取位置开始的,当剩余内容不够指定长度时会读取剩余的所有字符,如果已经读取到最后,将返回一个空串。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
try:
with open(file_name, encoding="utf-8") as file_obj:
while True:
## 指定每次读取一百个字符
content = file_obj.read(100)
## 当read()返回值为空串时读取到文件末尾,跳出循环
if not content:
break
print(content, end="")
except FileNotFoundError as e:
print(f"{file_name}文件不存在!")
5. readline() 方法
该方法可以读取文件中的一行并以字符串的形式返回,同样在读取到文件末尾时返回空串。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
try:
with open(file_name, encoding="utf-8") as file_obj:
while True:
## 读取一行
content = file_obj.readline()
## 当返回值为空串时读取到文件末尾,跳出循环
if not content:
break
print(content, end="")
except FileNotFoundError as e:
print(f"{file_name}文件不存在!")
6. readlines() 方法
该方法同样按行读取文件内容,但其会把一次读取所有行并放到列表中。
from pprint import pprint
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
try:
with open(file_name, encoding="utf-8") as file_obj:
## 读取文件中的所有行
pprint(file_obj.readlines())
except FileNotFoundError as e:
print(f"{file_name}文件不存在!")
12.2 文件的写入
类似文件的读取,我们可以通过write()
方法来向文件中写入内容。这里需要注意的是,open()
方法打开文件时默认的操作是读取,不允许进行写操作。如果我们想要写入则需要在第二个位置参数处传入对应的操作符。
write()
方法如果操作的是一个文本文件,入参必须是一个字符串。我们可以多次调用该方法向文件中写入数据,在同一个打开的文件中,多次调用该方法会按照调用顺序向后追加内容。写入完成后返回写入字符的个数。
open()
方法第二个位置参数代表打开文件要进行的操作,范围如下:
r
:只读
w
:可写,如果不存在会创建文件,如果存在会截断文件(覆盖原内容)。
a
:追加内容,如果文件不存在会创建文件,存在则会向文件中追加内容。
x
:用来新建文件,如果文件不存在,进行新建,存在则报错。
r+
:在可读的基础上追加写的权限,但文件不存在不会新建会抛出异常。
w+
:在可写的基础上追加读权限。
a+
:在可写的基础上追加读权限。
t
:读取文本文件(默认)。
b
:读取二进制文件。
file_name = "D:\\WorkSpace\\PythonLearn\\demo.txt"
try:
with open(file_name, "a", encoding="utf-8") as file_obj:
## 为文件追加内容
file_obj.write("\nhahaha")
except FileNotFoundError as e:
print(f"{file_name}文件不存在!")
12.3 二进制文件
read()
方法在读取文本文件时,按字符读取,在读取二进制文件时,按字节读取。
old_file_name = "D:\\CloudMusic\\鞠婧祎 - 叹云兮.mp3"
new_file_name = "鞠婧祎 - 叹云兮.mp3"
## r 读取 b 二进制文件
with open(old_file_name, "rb") as old_file_obj:
## 定义每次读取的字节个数
read_size = 1024*100
## w 写入 b 二进制文件
with open(new_file_name, "wb") as new_file_obj:
while True:
## 读取旧文件
content = old_file_obj.read(read_size)
## 读取内容为空时,读取到文件末尾,跳出循环
if not content:
break
## 将读取的内容写到新文件里
new_file_obj.write(content)
12.3 seek()和tell()
tell()
方法用于获取当前文件的读取位置。
seek()
方法可以修改当前文件的读取位置,比如修改当前文件读取位置为读取到第35个字节。该方法需要两个位置参数,第一个是要切换到的位置,第二个是位置计算方式。第二个参数的可选值如下:
0
:从头计算向后移动指定个字节,默认值
1
:从当前位置计算向后移动指定字节
2
:从最后位置开始计算,第一个参数如果是正数,向后移动指定字节(已经是结尾了),负数则从后向前移动指定字节。
file_name = "D:\\CloudMusic\\鞠婧祎 - 叹云兮.mp3"
with open(file_name, "rb") as file_obj:
## 修改读取位置为第10个字节
file_obj.seek(10,0)
## 输出当前读取位置
print(file_obj.tell()) ## 输出:10
这两个方法在读取文本文件时也可以使用,但是需要注意文本文件中有中文时,一个汉字是三个字节,因此使用seek()
方法修改读取位置可能会造成汉字被切割,从而导致异常。
更多文件操作,可以看 Python 帮助文档的 os 模块的内容。