Skip to content

Latest commit

 

History

History
4807 lines (3708 loc) · 171 KB

python笔记.md

File metadata and controls

4807 lines (3708 loc) · 171 KB

Python 笔记

一、计算机基础知识

1、计算机是什么

计算机就是一个用来计算的机器。目前来讲,计算机只能根据人的指令来完成各种操作,人让他干嘛他就干嘛,所以我们学习计算机就是学习如何控制计算机。

2、计算机的组成

计算机由两部门组成:硬件和软件

硬件包含:键盘、鼠标、显示器(外部设备)、CPU、主板、内存(内部设备)

软件包含:系统软件(windows、macOS、Linux)和应用软件(office、QQ)

3、计算机的使用方式

我们必须要通过软件来对计算机完成各种操作,但是注意软件中不是所有的功能都是会对用户进行开放,用户需要调用软件提供的接口(Interfac 交互界面)来操作计算机

用户界面分成两种:TUI(文本交互界面)和 GUI(图形化交互界面)

补充小知识1:

path 环境变量:

1、path 环境变量中保存的是一个一个的路径

2、当我们在命令行中输入一个命令(或访问一个文件)时,系统会首先在当前目录下寻找,如果找到了则直接执行或打开,如果没有找到,则会依次去 path 环境变量的路径中寻找,直到找到为止。如果 path 环境变量中的路径都没有找到,则报错。

3、我们可以将一些经常要访问的文件或程序的路径,添加到 path 环境变量中,这样我们就可以在任意位置访问到这些文件了。

注意:

1️⃣ 如果环境变量中没有 path 可以手动添加

2️⃣ path 环境变量不区分大小写,可以写 PATH Path path

3️⃣ 修改完环境变量后,必须重新启动命令行窗口

补充小知识2:

文本文件和字符集:

1、文本分成两种,一种叫做纯文本,还有一种叫做富文本

2、纯文本中只能保存单一的文本内容,无法保存内容无关的东西

3、富文本可以保存文本以外的内容(比如像 word 文档,就是富文本)

4、纯文本在计算机底层也会转换为二进制保存

  • 将字符转换为二进制编码的过程,我们称之为编码
  • 将二进制编码转换为字符的过程,我们称之为解码
  • 编码的解码所遵守的规则,我们称之为字符集

Unicode:

万国码,包含世界上所有的语言和符号,编写程序时一般都会使用 Unicode 编码。Unicode 编码会有多种实现,比如:UTF-8 UTF-16 UTF-32

说一下这三个的区别:

UTF-32 是固定使用 4 个字节来表示一个字符,UTF-16 是使用 2-4 个字节来表示一个字符,UTF-8 是使用 1-5 个字节来表示一个字符

二、Python 入门

1、计算机语言简介

计算机就是一台用来计算的机器,人让计算机干什么,计算机就干什么。我们需要通过计算机的语言来控制计算机(编程语言)。

计算机语言发展经历了三个阶段:

  • 机器语言

机器语言通过二进制编码来编写程序。这种语言它的好处就是,因为计算机它本身就只认识二进制,而你程序又是用二进制写的,所以你这个程序交给计算机去执行速度是非常非常快的。但是它的缺点也比较明显,你用 101010 去编程,虽然说计算机认识,但人不认识,可读性非常差。

  • 汇编语言
    • 汇编语言使用符号来代替机器码。
    • 我们在编写程序时,不需要使用二进制,而是直接编写符号
    • 编写完成后需要将符号转换为机器码,然后再由计算机执行。==符号转换为机器码的过程称为汇编。==
    • ==将机器码转换为符号的过程,称为反汇编==
    • 汇编语言一般只适用于某些硬件,兼容性比较差。(但是汇编语言今天其实也会用,像一些计算机底层的驱动就是用汇编去写的)
  • 高级语言
    • 高级语言的语法基本和现代英语语法类似,并且和硬件的关系没有那么紧密了
    • 也就是说我们通过高级语言开发的程序可以在不同的硬件系统中执行

说一下汇编语言的特点:

汇编语言跟硬件关联太紧密了,比如说我现在有个 ABC 它对应的机器码是 1010,那么这个东西其实是各个硬件厂商规定的,比如说你的 CPU 用的事 Intel 的,那么 ABC 可能对应 1010,要是你的 CPU 用的是 AMD 的,那么可能就是 BCD 对应 1010 。也就是说你如果编程,你在 Intel 的 CPU 里面运行,但是换了一个 CPU 可能就不能运行了,所以说汇编语言和硬件的关联太紧密了。

2、编译型语言和解释型语言

计算机只能识别二进制编码(机器码),所以任何语言在交由计算机执行时必须先转换为机器码,也就是像 「print('hello')」这种必须要转换为类似 101010 这种机器码,计算机才能够执行。

所以根据转换的时机不同,语言分为了两大类:

  • 编译型语言

==编译型语言,会在代码执行前将代码编译为机器码,然后将机器码交由计算机执行。==

像 C C++都属于编译型语言,编译型语言的特点就是:

①执行速度特别快

②跨平台性比较差

  • 解释型语言

==解释型语言,不会在执行前对代码进行编译,而是在执行的同时,一边执行一边编译。==

像 Python JS Java 都属于解释型语言,解释型语言的特点就是:

①执行速度比较慢

②跨平台性比较好


所以总结一下就是:

像 C 语言这种编译型语言,它会将整个代码都编译成机器码,然后交给系统去执行。而像 Python 这种解释型语言,它不需要编译,你写完之后直接把代码交给解释器,由解释器去解释执行。

这里说一下 Java 是编译型语言还是解释型语言:

首先说 Java 这个语言很非凡

一、你可以说它是编译型语言。因为所有的 Java 代码都是要编译的,「.java 」后缀的文件如果不经过编译就什么用都没有。

二、你也可以说它是解释型语言。因为 java 代码编译后不能直接运行,它是解释运行在 JVM 上的,所以它是解释运行的,也就算解释型语言了。

像 C、C++ 他们经过一次编译之后直接可以编译成操作系统了解的类型,操作系统可以直接执行。所以他们是编译型语言。没有经过二次处理。但是 Java 不一样,它首先由编译器编译成 「.class 」文件,这个是 Java 自己类型的文件,然后通过虚拟机(JVM)从 「.class 」文件中读一行解释执行一行,所以它是解释下语言。而且由于 Java 对于不同操作系统有不同的 JVM 所以 Java 实现了真正意义上的跨平台。

所以个人认为 Java 是解释型语言,因为虽然 java 也需要编译,编译成 「.class 」文件,但是并不是机器可以识别的,而是字节码最终还是需要 JVM 的解释,才能在各个平台上执行。所以可以说 java 既是编译型语言,也是解释型语言,但是如果非要归类,归类到解释型语言中比较好。

三、Python 简介及与 Sublime 整合

1、Python 简介

Python 是一种广泛使用的高级编程语言,Python 的设计哲学强调代码的可读性和简洁的语法。相比于 C++ 或 Java ,Python 让开发者用更少的代码表达想法。

Python 用途:

1、开发 web 应用

2、爬虫程序

3、科学计算

4、大数据

5、人工智能

2、Python 开发环境的搭建

开发环境搭建就是安装 Python 的解释器。

Python 解释器分类:

  • CPython(官方)

用 C 语言编写的 Python 解释器,这个东西其实就是龟叔为了打发时间开发出的东西,所以这也是官方的解释器。

  • PyPy

用 Python 语言编写的 Python 解释器,但是注意 PyPy 也需要运行在 CPython 中。一些特殊情况,我们需要用到 PyPy。

  • IronPython

用 .net 编写的 Python 解释器,也就是说通过这个解释器我们可以将编写好的 Python 代码放到 .net 中运行

  • Jython

用 Java 编写的 Python 解释器

注:

这么多解释器其实都是为了让 Python 可以在不同的环境中运行。

安装步骤:

1、下载安装包(https://www.python.org)

2、运行安装程序安装(傻瓜式安装),windows 下,选择自定义安装,这样可以指定 python 的安装路径,以及在安装的时候可以顺便把 python 添加到环境变量中(会有个地方让你勾选)

3、安装完之后在命令行输入 python 进行验证,如果可以进入 python 的 shell 则证明安装成功。(mac 下输入 python3 进行验证)

3、Python 的交互界面

当我们通过命令行来输入 python ,所进入到的界面就是 Python 的交互界面

结构:

版本和版权声明:
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

命令提示符:

>>>

在命令提示符后可以直接输入 Python 指令,输入完的指令将会被 Python 解释器立即执行。

注:

  • 在安装 Python 的同时,会自动安装一个 Python 的开发工具 IDLE,通过 IDLE 也可以进入到交互模式,但是不同的是,在 IDLE 中可以通过 TAB 键来查看语句的提示。IDLE 实际上就是一个交互界面,但是它可以有一些简单的提示,并且可以将代码保存(你可以在 IDLE 中 new File ,它会给你新开一个窗口,你在这个窗口中开发完后可以保存成一个文件,然后在这个窗口按 F5 或 Fn + F5 来运行代码)
  • 交互模式只能你输入一行代码,它就执行一行,所以它并不适用于我们日常的开发,仅仅可以用来做一些日常的简单测试。
  • 我们一般会将 Python 代码编写到一个 py 文件中,然后通过 python 指令来执行文件中的代码。(如 「python hello.py」)

4、Python 和 Sublime 的整合

1️⃣ 我们可以在 Sublime 中直接执行 Python 代码,使用 「ctrl + b 」快捷键,这样就可以自动在 Sublime 内置的控制台中执行。但是这种执行方式,在某些版本的 Sublime 中对中文支持不好,并且不能使用 input() 函数。

2️⃣ 使用 SublimeREPL 来运行 Python 代码。所以首先你需要在 Sublime 中安装 SublimeREPL 插件

1、点击 首选项 ---> Package Control 调出 Sublime 的包管理器(或者直接快捷键 ctrl + shift + p)
2、选择 install Package ---> 输入 SublimeREPL 进行安装

安装完成之后,你在 工具 —---> SublimeREPL ——> Python ——> Python-RUN current file 就可以运行你在 Sublime 中写的文件了。如果你要是觉得这样运行代码麻烦,你也可以设置快捷键,设置方式就是:

点击 首选项---> 快捷键设置
把 { "keys": ["f5"], "caption": "SublimeREPL:Python","command": "run_existing_window_command", "args":{"id": "repl_python_run","file": "config/Python/Main.sublime-menu"}} 直接放在中括号里面就可以了
然后你就可以按 f5 来执行 python 代码了,有的电脑 f5 可能是功能键,这时候你使用 fn + f5 即可。

3️⃣ 你安装好 SublimeREPL 后执行 Python 代码,发现它默认是用 Python2 去执行的,如果想改用 Python3 执行,需要使用如下方法:

vim /Users/shenheping/Library/Application Support/Sublime Text 3/Packages/SublimeREPL/config/Python/Main.sublime-menu

"cmd": ["python3", "-i", "-u"],
"cmd": ["python3", "-i", "-u", "-m", "pdb", "$file_basename"],
"cmd": ["python3", "-u", "$file_basename"],
"cmd": {
    "osx": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "linux": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "windows": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"]
},

将该文件中调用 python 命令的地方全部改成 python3 即可。

四、Python 中基本概念及基本语法

1、几个基本的概念

1️⃣ 表达式

表达式就是一个类似与数学公式的东西,像 「10 + 9 」、「8 - 5」。表达式一般仅仅用来计算一些结果,不会对程序产生实质性的影响。如果在交互模式中输入一个表达式,解释器会自动将表达式的结果输出。

2️⃣ 语句

在程序中语句一般需要完成某种功能,比如打印信息、获取信息、为变量赋值等。例: 「print()」、「a = 5」,语句的执行一般会对程序产生一定的影响。在交互模式中,不一定会输出语句的执行结果。比如说:你在命令行输入一个 a = 5 回车之后不会给你输出 a 的结果,只有你去打印 a 的信息,才会去输出 a 的结果。

3️⃣ 程序(program)

程序就是由一条一条的语句和一条一条的表达式构成的。

4️⃣ 函数(function)

函数就是一种语句,函数专门用来完成某种特定的功能。

函数的分类:

  • 内置函数:由 Python 解释器提供的函数,可以在 Python 中直接使用。
  • 自定义函数:由程序员自主创建的函数

当我们需要完成某种功能的时候,就可以调用内置函数或自定义函数。

函数的两个要素:

  • 参数

()中的内容就是函数的参数,函数中可以没有参数,也可以有多个参数,多个参数用逗号分隔

  • 返回值

返回值是函数的返回结果,不是所有的函数都有返回值。

2、Python 的基本语法

1、在 Python 中严格区分大小写

2、在 Python 中每一行就是一条语句,每条语句以换行结束

3、在 Python 中每一条语句不要过长(规范中建议每行不要超过 80 个字符)

你使用 sublime 编辑器,可以在 80 个字符处加个尺子,方法是:
「首选项」 ---->  「设置」   然后添加下面的配置

"rulers":
	[
		80
	]
加完之后在编译器里面就会出现一个竖着的虚线,这个竖着的虚线就是 80 个字符的位置

4、在 Python 中一条语句可以分多行编写,在语句后面以 "" 结尾

print('helloworld\
	Jimmy')

5、Python 是缩进严格的语言,所以在 Python 中不要随便写缩进

6、在 Python 中使用 # 号来表示注释,注释的内容将被解释器所忽略,我们一般习惯在 # 后面会跟上一个空格

3、字面量和变量

  • 字面量就是一个一个的值,比如:1,2,3,4,"hello",字面量所表示的意思就是它的字面的值,在程序中可以直接使用字面量
  • 变量(variable)可以用来保存字面量

一般我们在开发中很少直接使用字面量,都是将字面量保存到变量中,通过变量来引用字面量。

4、变量和标识符

4.1、变量

1、Python 中使用变量不需要声明,直接赋值即可

2、不能使用没有进行过赋值的变量

3、Python 是一个动态类型的语言,可以为变量赋任意类型的值,也可以任意修改变量的值

a = 10      # 一开始我给 a 赋值个 10
a = 'hello'  # 然后我又把 a 的值改成 hello,这样是一点问题都没有的
print(a)
4.2、标识符
  • 在 Python 中所有可以自主命名的内容都属于标识符。比如:变量名、函数名、类名

  • 标识符必须遵循标识符的规范

    • 标识符中可以含有字母、数字、下划线,但是不能使用数字开头
    • 标识符不能是 Python 中的关键字和保留字,也不建议使用 Python 中的函数名作为标识符,因为这样会导致函数被覆盖
    print = 'hello'     #这里我给 print 这个变量赋值为 hello
    print(print)        #我想打印下这个变量,这样是有问题的,这里的 print 函数被上面的 print 变量覆盖了

==Python 中的命名规范:==

在 Python 中有以下两种命名规范:

1️⃣ 下划线命名法

所有字母小写,单词之间用下划线分隔。

如:max_length、min_length

2️⃣ 帕斯卡命名法(大驼峰命名法)

首字母大写,每个单词开头字母大写,其余字母小写

如:MaxLength、MinLength

五、Python 中数据类型

1、数值

在 Python 中,数值分成了三种,整数、浮点数、复数

1.1、整数

1、在 Python 中,所有的整数都是 int 类型

2、Python 中整数的大小没有限制,可以是一个无限大的整数

b = 999999999999999 ** 100  # Python 中两个 * 表示多少次幂
print(b)    #结果也是可以正常打印的

3、如果数字的长度过长,可以使用下划线作为分隔符

c = 232_55_7776
print(c)  #结果是 232557776,也就是说它会把下划线给自动忽略掉

4、其他进制的整数,只要是数字,打印时一定是以十进制的形式显示的

# 二进制数以 0b 开头
d = 0b10
print(d)  #结果是 2

# 八进制数以 0o 开头
d = 0o10
print(d)  #结果是 8

# 十六进制数以 0x 开头
d = 0x10
print(d)   #结果是 16

5、可以通过运算符来对数字进行运算,并且可以保证整数运算的精确

c = 12
c = c + 3
9.2、浮点数

1、在 Python 中,所有的小数都是 float 类型

2、对浮点数进行运算时,可能会得到一个不精确的结果

c = 0.1 + 0.2
print(c)  #结果是 0.30000000000000004

2、字符串

字符串用来表示一段文本信息,字符串是程序中用的最多的数据类型,在 Python 中字符串需要使用引号括起来。引号可以是单引号也可以是双引号。

字符串注意事项:

1、单引号和双引号不能跨行使用,要想跨行必须使用反斜杠""

2、长字符串

  • 使用三重引号来表示一个长字符串 『""" 或 '''』
  • 三重引号可以换行,并且会保留字符串中的格式

3、转义字符

  • 可以使用 \ 作为转义字符。通过转义字符可以在字符串中使用一些特殊的内容
a = "hello"  # 字符串必须要使用引号引起来
a = 'hello'  # 引号可以是单引号也可以是双引号

# 单引号和双引号不能跨行使用,要想跨行必须使用反斜杠"\"
str="锄禾日当午,汗滴禾下土。\
谁知盘中餐,粒粒皆辛苦"

# 长字符串
# 使用三重引号来表示一个长字符串 """ 或 '''
# 三重引号可以换行,并且会保留字符串中的格式
str = """ 锄禾日当午,汗滴禾下土。
谁知盘中餐,粒粒皆辛苦"""

# 转义字符
# 可以使用 \ 作为转义字符。通过转义字符可以在字符串中使用一些特殊的内容
# 例如:
# \' 表示 '
# \" 表示 ”
# \t 表示制表符
# \n 表示换行符
# \\ 表示反斜杠(两个斜杠表示反斜杠,你要是想写两个反斜杠,就需要些四个斜杠)
# \uxxxx 表示 unicode 编码
s = '\u2250'    #这个就会打印出 2250 这个 unicode 码对应的字符

3、格式化字符串

1、字符串之间也可以进行加法运算,如果将两个字符串进行相加,则自动将两个字符串拼接为一个

a = 'hello'

# 字符串之间也可以进行加法运算,如果将两个字符串进行相加,则自动将两个字符串拼接为一个
a = a + 'world'    # 这种写法在 Python 中不常见

# 注意:字符串只能和字符串进行拼接,字符串不能和其他类型做加法运算,如果做了会出现异常
a = a + 10     #这种写法是错误的

注意:

==字符串只能和字符串进行拼接,字符串不能和其他类型做加法运算,如果做了会出现异常==

所以拼接字符串时,有没有什么比较通用的方式?

方式一:

利用 print 函数去进行拼接

# 我们可以借助 print 函数来完成,print 是一个函数,它里面可以传两个参数,传两个参数就会把这
#两个参数拼接起来
a = 10
print('a = ', a)   #好处就是无论 a 是什么类型都可以拼接

方式二:

利用占位符去进行拼接

# 在创建字符串时,可以在字符串中指定占位符
# %s 在字符串中表示任意字符
# %f 浮点数占位符
# %d 整数占位符
b = 'hello %s'   #这里的意思就是 hello 后面 %s 这个位置可以是任意字符
print(b)   #打印的是 hello %s,%s 直接被输出了,因为我们并没有对 %s 这个位置进行填充

#对占位符进行填充
b = 'hello %s'%'Jimmy'  #这句话的意思就是 hello %s 这个字符串里面有个 %s 占位符,然后我用 Jimmy 去填充这个占位符
print(b)   #结果就是 「hello Jimmy」

b = 'hello %s 你好 %s'%('Jimmy','kelly')
#这句话的意思就是,'hello %s 你好 %s' 里面有两个占位符,我后面填充的时候就需要加个括号,括号里面
#第一个参数填充第一个占位符,第二个参数填充第二个占位符

b = 'hello%3s'%'沈和平'  # 这个 %3s 的意思就是用 3 个字符长度去填充占位符,'沈和平' 正好占三个字符长度
# 所以打印结果为 「hello沈和平」

b = 'hello%4s'%'沈和平'  # 这个 %4s 的意思就是用 4 个字符长度去填充占位符,'沈和平' 只占三个字符长度
# 少一个字符长度就补空格,所以打印结果为 「hello 沈和平」
# 综上,少几个字符长度就补几个空格

b = 'hello%1.2s'%'沈和平'  # 这个 %1.2s 的意思就是用 1 到 2 个字符长度去填充占位符,最少一个字符
#填充,最多两个字符填充,所以打印结果为 「hello沈和」

b = 'hello%f'%12.5
print(b)   #结果是 「hello12.500000」

b = 'hello %.2f'%12.5     # 这个 %.2f 的意思就是保留两位小数
print(b)   # 结果是 「hello12.50」

b = 'hello%d'%12.95      # 这个 %d 是整数的占位符,只去整数,舍掉小数
print(b)   #结果是 「hello12」

# 案例:
c = 123
print('c = %s'%c)   #这个意思就是我用 c 的值去填充 %s 这个占位符

方式三:

利用格式化字符串

# 格式化字符串,可以通过在字符串前添加一个 f 来创建一个格式化字符串
d = f'hello'  # 加了一个 f 就变成了格式化字符串,这里的 f 大小写都可以
print(d)  # 结果是 「hello」

#在格式化字符串中可以直接嵌入变量
a = 'Jimmy'
b = 'kelly'
d = f'hello {a} {b}'    # {} 表示里面的内容是一个变量
# 结果是 「hello Jimmy kelly」

# 案例:
print(f'a = {a}')   # 结果是 「a = Jimmy」

4、字符串的复制(将字符串和数字相乘)

如果将字符串和数字相乘,则解释器会将字符串重复指定的次数并返回

a = 'abc'
# 「* 号」在语言中表示乘法,如果将字符串和数字相乘,则解释器会将字符串重复指定次数并返回
a = a * 3
print('a = ',a)  # 结果是 「abcabcabc」

5、布尔值和空值

1、布尔值主要用来做逻辑判断,布尔值一共有两个 True 和 False,True 表示真,False 表示假

2、布尔值实际上也属于整型,True 相当于 1,False 相当于 0

3、None(空值),None 专门用来表示不存在

# 布尔值主要用来做逻辑判断,布尔值一共有两个 True 和 False
# True 表示真,False 表示假
a = True
a = False
print('a = ',a)

# 布尔值实际上也属于整型,True 相当于 1,False 相当于 0
print(1 + True)   # 结果是 2
print(1 + False)  # 结果是 1

# None(空值),None 专门用来表示不存在
b = None
print('b = ',b)   # 结果是 b =  None

6、类型检查

通过类型检查,可以检查指定值(变量)的类型

a = 123
b = '123'

# type() 用来检查值的类型
# 该函数会将检查的结果作为返回值返回
c = type(a)
print(c)    # 结果是 <class 'int'>
c = type(b)
print(c)    # 结果是 <class 'str'>

六、对象

1、对象介绍

1、Python 是一门面向对象的语言

2、一切皆对象

3、程序运行当中,所有的数据都是存储到内存中然后再运行的

4、对象就是内存中专门用来存储指定数据的一块区域

5、对象实际上就是一个容器,专门用来存储数据

6、像我们之前学习的数值、字符串、布尔值、None 都是对象

2、对象的结构

每个对象中都保存了三种数据

  • id(标识)
    • id 用来标识对象的唯一性,每一个对象都有唯一的 id

    • 对象的 id 就像人的身份证一样,可以==通过 id() 函数==来查看对象的 id

    • id 是由解析器生成的,在 CPython 中,id 就是对象的内存地址

    • 对象一旦创建,则它的 id 永远不能再改变

    • >>> id(123)
      4410018624
      >>> id(124)
      4410018656
  • type(类型)
    • 类型用来标识当前对象所属的类型。比如:当前对象是:int、 float、str、bool 等等

    • 类型决定了对象有哪些功能

    • 可以通过 type() 函数来查看对象的类型

    • Python 是一门强类型的语言,对象一旦创建类型便不能修改

    • >>> type(123)     # type 函数查看对象的类型
      <class 'int'>
  • value(值)
    • 值就是对象中存储的具体数据
    • 对于有些对象值是可以改变的
    • 对象分成两大类:
      • 可变对象:可变对象的值可以改变
      • 不可变对象:不可变对象的值不能改变
      • 我们之前学的 整数、浮点数、字符串、布尔值都是不可变对象

对象的结构

3、变量和对象

1)、对象并没有直接存储到变量中,在 Python 中变量更像是给对象起了一个别名

2)、变量中存储的不是对象的值,而是对象的 id(内存地址)。当我们使用变量时,实际上就是在通过对象的 id 查找对象

3)、变量中保存的对象,只有在为变量重新赋值后才会改变

假如现在我定义一个变量, a = 123 ,那么它在内存中结构是怎么样的?

1、a = 123 ,那么他首先肯定会在内存中开辟一块空间来存储 123 这个对象,至于 123 这个对象在内存中怎么存储的参考上面的第 2 点《对象的结构》,其次就是 a 是一个变量,在内存中也会有一个专门的区域来存储变量,这个区域会有变量名和变量值,变量名就是 a ,但是变量值存的是对象的 id ,因为在 python 中对象的 id 就是对象的内存地址,当我们使用变量的时候会根据变量值(内存地址)去内存中找到这个对象使用。

2、假如现在我让 b = a,那么其实就是把 a 的变量值赋给 b,而 a 的变量值存的是 123 的内存地址,所以也就是把 123 的内存地址赋给了 a ,这时候 a 和 b 都指向了同一个对象

>>> a = 123
>>> id(a)
4410018624
>>> b = a
>>> id(b)
4410018624       # 发现 a 和 b 的 id 值都是一样的,说明 a 和 b 现在指向的是同一个内存空间

3、假如现在我让 a = 456,那么这时候会不会对 b 产生影响?答案肯定是不会的。a = 456 ,这时候又会在内存中开辟一块空间用来存 456,然后把 a 的变量值改成 456 的内存地址,这时候 a 就指向了 456 这块空间,但是 b 的指向并没有改变,还是指向 123 这块空间

变量和对象

4、类型转换

1)、所谓的类型转换,就是将一个类型的对象转换为其他对象

2)、类型转换不是改变对象本身的类型,而是根据当前对象的值创建一个新的对象

类型转换的四个函数 int(),float(),str(),bool()

1️⃣ int() 可以将其他对象转换成整型

规则:

  • 布尔值:True -> 1 False -> 0
  • 浮点数:直接取整,省略小数点后面的内容
  • 字符串:合法的字符串直接转换为对应的数字,对于其他不可转换为整型的对象,直接抛出异常 ValueError

2️⃣ float()int() 基本一致,不同的是它会将对象转换为浮点数

3️⃣ str() 可以将对象转换为字符串

例:True -> 'True' 123 -> '123'

4️⃣ bool() 可以将对象转换为布尔值,任何对象都可以转换为布尔值

规则:

  • 对于所有表示空性的对象都会转换为 False ,其余的转换为 True
  • 哪些表示空性:0,None,'' 等等
a = True
# int() 函数不会对原来的变量产生影响,它是将对象转换为指定的类型并将其作为返回值返回
# 如果希望修改原来变量的值,则需要对变量进行重新赋值
a = int(a)
print("a =",a)  # a = 1
print(type(a))  # <class 'int'>

a = 10.7
a = int(a)
print("a =",a)  # a = 10
print(type(a))  # <class 'int'>

a = True
a = float(a)
print("a =",a)  # a = 1.0
print(type(a))  # <class 'float'>

七、运算符

运算符可以对一个值或多个值进行运算或各种操作

运算符的分类:

1、算数运算符

2、赋值运算符

3、比较运算符(关系运算符)

4、逻辑运算符

5、条件运算符(三元运算符)

1、算数运算符

  • 「+」加法运算符

如果两个字符串之间做加法操作,会进行拼串操作

  • 「-」减法运算符
  • 「*」乘法运算符

如果字符串和数字相乘,则会对字符串进行复制操作,将字符串重复指定次数。

  • 「/」除法运算符

运算时结果总会返回一个浮点类型

  • 「//」整除

只会保留计算后的整数位

  • 「**」幂运算

求一个数的多少次幂

  • 「%」取模

求两个数相除的余数

a = 1 + 2  # 直接做计算
print('a =',a)  # a = 3

a = 'hello' + 'world'  # 拼串
print('a =',a)  # a = helloworld
 
a = 10 -3  # 直接做计算
print('a =',a) # a = 7

a = 10 - True    #布尔类型其实也算数值类型
print('a =',a) # a = 9

a = 5 * 5
print('a =',a) # a = 25
a = 'Jimmy' * 3  # 字符串和数字相乘,则会对字符串进行复制操作
print('a =',a) # a = JimmyJimmyJimmy

a = 10 / 2    # --------------运算时结果总会返回一个浮点类型--------------------
print('a =',a) # a = 5.0

#a = 10 / 0
#print('a =',a)   #ZeroDivisionError: division by zero

a = 10 / 3
print('a =',a)  # a = 3.3333333333333335  得到的值是一个近似的值

# 「//」 表示整除,只会保留计算后的整数位
a = 10 // 3
print('a =',a)  #a = 3

# 「**」 表示幂运算,求几次幂
a = 2 ** 3
print('a =',a)  # a = 8   求 2 的 3 次幂

#求 16 的平方根
a = 16 ** 0.5   
print('a =',a)  # a = 4.0 

2、赋值运算符

# 赋值运算符
# = 可以将等号右侧的值赋给等号左侧的变量
# +=   a += 5  <---->  a = a + 5
# -=   a -= 5  <---->  a = a - 5
# *=   a *= 5  <---->  a = a * 5
# **=  a **= 5  <---->  a = a ** 5
# /=   a /= 5  <---->  a = a / 5
# //=  a //= 5  <---->  a = a // 5
# %=  a %= 5  <---->  a = a % 5

a = 10
a += 5  # 就相当于 a = a + 5
print('a =',a)  # a = 15

a /= 5    # 我们之前说了做除法运算时,结果总会返回一个浮点数
print('a =',a)  # a = 3.0

a //= 2     # ----- 在对浮点数进行算数运算时,结果也会返回一个浮点数 -------
print('a =',a)  a = 1.0

3、关系运算符

关系运算符用来比较两个值之间的关系,总会返回一个布尔值。如果关系成立,返回 True,否则返回 False

  • 「>」 比较左侧值是否大于右侧值
  • 「>=」 比较左侧值是否大于或等于右侧值
  • 「<」 比较左侧值是否小于右侧值
  • 「<=」 比较左侧值是否小于或等于右侧值
  • 「==」 比较两个对象的值是否相等
  • 「!=」 比较两个对象的值是否不等

==相等和不等比较的是两个对象的值,而不是 id==

  • 「is」 比较两个对象是否是同一个对象,比较的是对象的 id
  • 「is not」 比较两个对象是否不是同一个对象,比较的是对象的 id
result = 10 > 20  # False
result = 30 < 40  # True
result = 20 >= 20 # True

# 比较的其实是这两个字符串的 Unicode 值,第一位如果已经比较出大小了,后面的就不看了
result = '2' > '1'  # True
result = '2' > '11' # True

# 在 Python 中可以对两个字符串进行大于或小于的运算
# 当对字符串进行比较时,实际上比较的是字符串的 Unicode 编码
# 比较两个字符串的 Unicode 编码时,是逐位比较的,比如说第一位已经比出结果了那么后面的都不看了
# 利用该特性可以对字符串按照字母顺序进行排序,但是对中文来说意义不是特别大 
# 如果不希望比较两个字符串的 Unicode 编码,则需要将其转换为数字然后再进行比较
result = 'a' > 'b'  # False 

result = 1 == 1   # True
result = 'hello' == 'hello'  # True
result = 'abc' == 'adc'   # False
result = 'abc' != 'adc'   # True
result = 1 == True   # True
result = 1 is True   # False   is 比较的是两个对象的 id
print('result =',result)

4、逻辑运算符

逻辑运算符主要用来做一些逻辑判断

  • not 逻辑非
    • not 可以对符号右侧的值进行非运算
    • 对于布尔值,非运算会对其进行取反操作,True 变 False ,False 变 True
    • 对于非布尔值,非运算会先将其转换为布尔值,然后再取反
  • and 逻辑与
    • and 可以对符号两侧的值进行与运算
    • 只有在符号两侧的值都为 True 时,才会返回 True,只要有一个 False 就会返回 False
    • 与运算是找 False 的,在 Python 中与运算是短路与,如果第一个值是 False ,则不再看第二个值
  • or 逻辑或
    • or 可以对符号两侧的值进行或运算
    • 或运算两个值中只要有一个是 True 就会返回 True
    • 或运算时找 True 的,在 Python 中或运算时短路或,如果第一个值是 True ,则不再看第二个值
a = True
a = not a   # 对 a 进行非运算
print('a =',a)   # a = False

a = 1
a = not a  # 对非布尔值进行非运算,先进行类型转换,然后再取反
print('a =',a)  # a = False

a = ''    # 前面我们说过了一些表示空性的值,像 0、None、‘’ 等转换成布尔值会转换成 False ,其他都转换成 True
a = not a
print('a =',a)  # a = True

a = True and True     # 结果是 True
a = True and False    # 结果是 False
a = False and True    # 结果是 False
a = False and False   # 结果是 False
print('a =', a)


a = True or True    # 结果是 True
a = True or False   # 结果是 True
a = False or True   # 结果是 True
a = False or False  # 结果是 False
print('a =',a)

#--------------逻辑运算符可以连着使用--------------------
result = 1 < 2 < 3   # 这个就相当于 1 < 2 and 2 < 3
print(result)   # 打印 true

result = 10 < 20 > 15   # 这个就相当于 10 < 20 and 20 > 15
print(result)   # 打印 true

5、非布尔值的逻辑运算符

当我们对非布尔值进行与或运算时,Python 会将其当成布尔值运算,最终会返回==原值==

与运算的规则

  • 与运算是找 False 的,如果第一个值是 False, 则不看第二个值
  • 如果第一个值是 False ,则直接返回第一个值,否则返回第二个值

或运算的规则

  • 或运算时找 True 的,如果第一个值是 True ,则不看第二个值
  • 如果第一个值是 True ,则直接返回第一个值,否则返回第二个值
result = 1 and 2   # 结果是 2
result = 1 and 0   # 结果是 0
result = 0 and 1   # 结果是 0
result = 0 and None  #结果是 0

result = 1 or 2   # 结果是 1
result = 1 or 0   # 结果是 0
result = 0 or 1   # 结果是 1
result = 0 or None # 结果是 None
print('result =',result)

6、条件运算符(三元运算符)

1️⃣ 语法:

语句1 if 条件表达式 else 语句2

2️⃣ 执行流程:

条件运算符在执行时,会先对条件表达式进行求值判断。① 如果判断结果为 True ,则执行语句1,并返回==执行结果==。② 如果判断结果为 False,则执行语句2,并返回==执行结果==

print('你好') if True else print('hello')   # 打印 你好

a = 20
b = 10
print('a 的值比 b 的值大') if a > b else print('a 的值比 b 的值小')  #a 的值比 b 的值大

# 获取 a 和 b 之间的较大值
max = a if a > b else b   #如果判断结果为 True,会执行语句1 ,并返回执行结果。如果判断结果为 False,会执行语句2,并返回执行结果。所以最终 a 或 b 的值会被赋值给 max
print(max)   # 结果是 20


# -------------------------------------------------------------------------------------
a = 10
b = 50
c = 30

#求三个数中的较大值
max = a if a > b else b
max = max if max > c else c
print(max)

#两行代码合并成一行代码写(但是这种写法不易阅读)
max = a if(a > b and a > c) else (b if b > c else c)  # 不推荐这么使用
print(max)

7、运算符的优先级

1、和数学一样,在 Python 中运算符也有优先级,比如,先乘除后加减。运算符的优先级可以根据优先级的表格来查询,在表格中位置越靠下的运算符优先级越高,优先级越高的越优先计算。如果优先级一样,则自左向右计算。

2、在开发中如果遇到优先级不清楚的,则可以通过小括号来改变运算顺序

Python 运算符优先级

运算符 描述
lambda Lambda表达式
or 布尔“或”
and 布尔“与”
not x 布尔“非”
in,not in 成员测试
is,is not 同一性测试
<,<=,>,>=,!=,== 比较
` `
^ 按位异或
& 按位与
<<,>> 移位
+,- 加法与减法
*,/,% 乘法、除法与取余
+x,-x 正负号
~x 按位翻转
** 指数
x.attribute 属性参考
x[index] 下标
x[index:index] 寻址段
f(arguments...) 函数调用
(experession,...) 绑定或元组显示
[expression,...] 列表显示
{key:datum,...} 字典显示
'expression,...' 字符串转换
# and 的优先级比 or 高,先计算 and 后计算 or  
# 2 and 3 结果是 3   1 or 3 结果是 1,所以最终结果 a 的值为 1
a = 1 or 2 and 3
print(a)    # 结果是 1

八、流程控制语句

简介:

Python 代码在执行的时候是按照自上而下的顺序执行的。通过流程控制语句,可以改变程序的执行顺序,也可以让指定的程序反复执行多次。

流程控制语句分成两大类:

1、条件判断语句

2、循环语句

1、条件判断语句(if 语句)

1️⃣ 语法:

if 条件表达式 :

​ 代码块

2️⃣ 执行流程:

1)、if 语句在执行时,会先对条件表达式进行求值判断,如果为 True ,则执行 if 后的语句,如果为 False ,则不执行

2)、默认情况下,if 语句只会控制紧随其后的那条语句,如果希望 if 可以控制多条语句,则可以在 if 后跟着一个代码块

3️⃣ 代码块

  • 代码块中保存着一组代码,同一个代码块中的代码,要么都执行要么都不执行
  • 代码块就是一种为代码分组的机制
  • 如果要编写代码块,语句就不能紧随在 「:」后面,==而是要写在下一行==
  • 代码块以缩进开始,直到代码块恢复到之前的缩进级别时结束

注:

Python 是一门缩进严格地语言,在 Python 中不要乱写缩进

a = 10
if a > 5 : print('a 的值大于 5')  # 默认情况下,if 语句只会控制它后面的一条语句,条件成立执行,条件不成立,不执行

num = 10
if num > 5 :
	print('num的结果大于5') # 注意:这里写的语句和上一行的 if 语句之间要有缩进
	print('hello')
    
    
num = 25
# 可以使用逻辑运算符来连接多个条件
if num > 20 and num < 35 :
	print('num 的值大于 20 小于 35')

if 20 < num < 35 :      # 这种写法和上面的那种写法是等价的,前面我们也说过,逻辑运算符连接写就表示 and 的意思
	print('num 的值大于 20 小于 35')

2、input 函数

  • input() 函数用来获取用户输入
  • input() 调用后,程序会立即暂停,等待用户输入。用户输入完内容后,点击回车程序才会继续向下执行
  • 用户输入完成以后,其所输入的内容会以返回值的形式返回
  • input() 的返回值是一个==字符串==
  • input() 函数中可以设置一个字符串作为参数,这个字符串将会作为一个提示文字显示
  • input() 也可以用于暂时阻止程序结束(见下面 额外小知识)
a = input('请输入内容:')
print('用户输入的内容是',a)   # 用户输入的内容是 hello

#判断用户名是否是 admin
username = input('请输入用户名:')
if username == 'admin':
	print('欢迎管理员进入!!')    # 欢迎管理员进入!!

额外小知识:

其实你写一个 .py 后缀名的文件,在 windows 下只要安装了 python 的环境双击即可执行。但是由于执行的速度太快乐,窗口一闪而过就退出了,这时候你要是想让窗口不退出,就可以借用 input() 函数来实现。

print('hello')
input('按回车键退出程序!!')

#这样你在双击这个文件运行的时候,窗口就不会马上退出,而是等你敲完回车键再退出

3、if-else 语句

1️⃣ 语法:

if 条件表达式 :

​ 代码块

else :

​ 代码块

2️⃣ 执行流程:

1)、if-else 语句在执行时,先对 if 后的条件表达式进行求值判断,如果为 True,则执行 if 后的代码,如果为 False,则执行 else 后的代码

age = 18
if age >= 18:
	print('你已经成年了')
else :
	print('你还没有成年')

4、if-elif-else 语句

1️⃣ 语法:

if 条件表达式 :

​ 代码块

elif 条件表达式 :

​ 代码块

elif 条件表达式 :

​ 代码块

else

​ 代码块

2️⃣ 执行流程:

1)、if-elif-else 语句在执行时,会自上向下依次对条件表达式进行求值判断,如果表达式的结果为 True,则执行当前代码块,然后语句结束。如果表达式的结果为 False ,则继续向下判断,直到找到 True 为止,如果所有的表达式都是 False ,则执行 else 后的代码块

2)、if-elif-else 中只会有一个代码块会执行

# if-elif-else 语句

age = 7
if age > 200 :
	print('我已经 200 多岁了')
elif age > 60 :
	print('你已经步入老年了')
elif age > 40 :
	print('你已经步入中年了')
elif age > 18 :
	print('你刚成年')
else :
	print('你还是个孩童')

# 注意:写代码中要避免死代码
age = 30      # 只要你 age 大于 10 后面的 大于 20 的代码块永远执行不到,这称为死代码
if age > 10 :
	print('你已经超过 10 岁了')
elif age > 20 :
	print('你已经超过 20 岁了')

小练习:

# 练习2:编写一个程序,获取用户输入狗的年龄,然后通过程序显示其相当于人类的年龄
age = int(input('请输入狗的年龄:\n'))
# 在 if 中也可以去嵌套 if ,代码块是可以嵌套的,每增加一个缩进的级别,代码块就低一级
# 检查用户的输入是否合法
if age > 0 :
	if age == 1 :
		print('相当于人类年龄的',10.5,'岁')
	elif age == 2 :
		print('相当于人类年龄的',21,'岁')
	elif age > 2 :
		age = (age -2) * 4 + 21
		print('相当于人类年龄的',age,'岁') 
else :         # else 对应哪个 if 靠缩进来区分
	print('你输入的不合法')

5、while 语句

循环语句可以使指定的代码重复指定的次数,循环语句分成两种,while 循环和 for 循环。

先说 while 循环

1️⃣ 语法:

while 条件表达式 :

​ 代码块

else :

​ 代码块

2️⃣ 执行流程:

1)、while 语句在执行时,会先对 while 后的条件表达式进行求值判断,如果判断结果为 True,则执行循环体(代码块),循环体执行完毕,继续对条件表达式进行求值判断,以此类推,直到判断结果为 False ,则循环终止。

2)、如果循环后有 else 语句,循环终止后会执行 else 后的代码块

# 循环的三个要件
# 初始化表达式,通过初始化表达式去初始化一个变量
i = 0
# 条件表达式,条件表达式用来设置循环执行的条件
while i < 10 :
	# 更新表达式,用来修改初始化变量的值
	i += 1
	print(i)
else :
	print('这是 while 循环里面的 else 语句')

while 语句练习

# 求 100 以内所有的奇数之和
ji_num = 0
i = 0
while i < 100:
	if i % 2 != 0 :
		ji_num += i
	i += 1
else :
	print('while 循环执行完成,100 以内奇数之和为:',ji_num)

# 求 100 以内所有 7 的倍数之和,以及个数
i = 7    #求 7 的倍数,我先给 i 赋值一个 7 倍数的最小值
result = 0
# 创建一个计数器,用来记录循环执行的次数
count = 0
while i < 100 :
	result += i
	i += 7   #每次自增 7 个,就是 7 的倍数
	count += 1
print('100 以内 7 的倍数之和为:',result,'总共有',count,'个')

6、循环嵌套

# 创建一个循环来控制图形的高度
i = 0
while i < 3 :            # 高度是 3
	# 创建一个循环来控制图形的宽度
	j = 0
	while j < 4 :        # 宽度是 4
		print('*',end='')  # 打印 * 后不换行
		j += 1
	i += 1
	print()  # 打印换行# 创建一个循环来控制图形的高度
    
#----------------打印结果----------
****
****
****
#----------------打印结果----------
i = 0
while i < 5 :
	# 创建一个循环来控制图形的宽度
	j = 0
	while j < i + 1 :
		print('*',end='')  # 打印 * 后不换行
		j += 1
	i += 1
	print()  # 打印换行
#----------------打印结果-----------
*
**
***
****
*****
#----------------打印结果-----------

注意:

#print 语句每输出一次都会在末尾默认输出一个换行
print('a')
print('b')   # 这样 a 和 b 会换行输出

#如果想让它不换行
print('c',end='')   # 在 print 语句中加一个参数,end='',在末尾输出一个空串意思就是不换行
print('d')   # 这样就会输出 cd

#如果你只写一个空的 print(),默认就打印一个换行
print()   #结果就是打印一个换行

7、break 和 continue

  • break 可以用来立即退出循环语句(包括 else 语句)
  • continue 可以用来跳出当前循环
  • break 和 continue 都只对离它最近的那个循环起作用
  • pass 用来在判断或循环语句中占位的
i = 0
while i < 5 :
	if i == 3 :
		break
	print(i)
	i += 1
else :
	print('循环执行结束')   # 循环中写了 break ,这里的 else 是不会执行的

i = 0
while i < 5 :
	i += 1
	if i == 3:
		continue
	print(i)
else :
	print('循环执行结束')    # 这里 else 里面的语句是会执行的

i = 0
if i < 5 :   # 因为我们有时候写 if 或者 循环的时候,具体功能可能还没想好怎么写,就可以先写个 pass 占位,这样运行的时候不会报错 
	pass     #这个 pass 只是单纯用来占位的,没有实际意义

九、序列

1、列表

1️⃣ 总览

  • 列表是 python 中的一个对象
  • 对象(object)就是内存中专门用来存储数据的一块区域
  • 之前我们学习的对象,像数值,它只能保存一个单一的数据
  • 列表中可以保存多个有序的数据
  • 列表是用来存储多个对象的对象
  • 列表的使用
    • 列表的创建
    • 操作列表中的数据

2️⃣ 列表介绍

  • 创建列表,通过 [ ] 创建列表
my_list = []    # 创建一个空列表
my_list = [10]  # 创建一个只含一个元素的列表
  • 当向列表中添加多个元素时,多个元素之间使用逗号 "," 隔开
  • 列表中可以保存任意的对象(但是我们一般不这么用,一般列表中放的都是同一类型的对象)
my_list = [10,20,30,40]  # 多个元素用逗号隔开
my_list = [10,'abc',True,None,[20],print]   # 列表中可以保存任意对象
print(my_list)  # 打印 [10, 'abc', True, None, [20], <built-in function print>]
  • 列表中的对象都会按照插入的顺序存储到列表中

  • 我们可以通过索引(index)来获取列表中的元素

    • 语法:my_list[索引]

    • my_list = [10,20,30,40]
      print(my_list[2])   # 打印 30
      # 如果使用的索引超出了最大范围,会抛出异常
      #print(my_list[4])  # 抛出异常
  • len() 函数,通过该函数可以获取到列表的长度

print(len(my_list))   # 打印 4
# 创建列表,通过[] 来创建列表
my_list = []    # 创建一个空列表
print(my_list)  # 打印 []
type(my_list)  # 打印 <class 'list'>

# 列表中存储的数据,我们称为元素
# 一个列表中可以存储多个元素,也可以在创建列表时,来指定列表中的元素
my_list = [10]  # 创建一个只含一个元素的列表

# 当向列表中添加多个元素时,多个元素之间使用逗号 "," 隔开
my_list = [10,20,30,40]

#列表中可以保存任意的对象(但是我们一般不这么用,一般列表中放的都是同一类型的对象)
my_list = [10,'abc',True,None,[20],print]
print(my_list)  # 打印 [10, 'abc', True, None, [20], <built-in function print>]

# 列表中的对象都会按照插入的顺序存储到列表中
# 我们可以通过索引(index)来获取列表中的元素
# 索引是元素在列表中的位置,列表中的每一个元素都有一个索引
# 索引是从 0 开始的整数

# 通过索引获取列表中的元素
# 语法:my_list[索引]
my_list = [10,20,30,40]
print(my_list[2])   # 打印 30
# 如果使用的索引超出了最大范围,会抛出异常
#print(my_list[4])  # 抛出异常

# 获取列表的长度
# len() 函数,通过该函数可以获取到列表的长度
print(len(my_list))   # 打印 4

2、切片

1)、基本介绍

切片指从现有列表中,获取一个子列表

2)、语法

列表[起始索引:结束索引:步长]

其中步长可以省略,默认步长为 1

3)、重要讲解

  • 通过切片获取元素时,结果包括起始索引元素,不包括结束索引元素(前包括后不包括)
  • 做切片操作时,总会返回一个新的列表,不会影响原来的列表
  • 起始和结束位置的索引都可以不写,如果省略结束位置,则会一直截取到最后,如果省略开始位置,则会从第一个元素开始截取
  • 如果起始位置和结束位置全部省略,则相当于创建了一个列表副本
  • 步长表示,每次获取元素的间隔,默认值是 1
  • 步长不能是 0 ,但可以是负数,如果是负数,则会从列表的后部向前边取元素
# 切片
# 切片指从现有列表中,获取一个子列表

# 创建一个列表,一般创建列表时,变量的名字会使用复数
stus = ['孙悟空','猪八戒','沙和尚','唐僧','白骨精','蜘蛛精']
print(stus)  # 结果是,['孙悟空', '猪八戒', '沙和尚','唐僧','白骨精','蜘蛛精']

#列表的索引可以是负数
# 如果索引是负数,则从后向前获取元素,-1 表示倒数第一个
print(stus[-2])  # 打印 白骨精

# 通过切片来获取指定元素
# 语法:
#	列表[起始索引:结束索引]	
print(stus[0:2])  # 打印 ['孙悟空', '猪八戒']

# 通过切片获取元素时,结果包括起始索引元素,不包括结束索引元素
# 做切片操作时,总会返回一个新的列表,不会影响原来的列表
new_stus = stus[0:3]  # 这里会返回一个新的列表
print(new_stus)  # 打印 ['孙悟空', '猪八戒', '沙和尚']

# 起始和结束位置的索引都可以不写,如果省略结束位置,则会一直截取到最后,如果省略开始位置,则会从第一个元素开始截取
# 如果起始位置和结束位置全部省略,则相当于创建了一个列表副本
print(stus[2:])  # 打印 ['沙和尚', '唐僧', '白骨精', '蜘蛛精']
print(stus[:2])  # 打印 ['孙悟空', '猪八戒']
print(stus[:])   # 打印 ['孙悟空', '猪八戒', '沙和尚', '唐僧', '白骨精', '蜘蛛精']

# 语法:列表[起始索引:结束索引:步长]
# 步长表示,每次获取元素的间隔,默认值是 1
print(stus[0:5:2])  # 打印 ['孙悟空', '沙和尚', '白骨精']

# 步长不能是 0 ,但可以是负数,如果是负数,则会从列表的后部向前边取元素
print(stus[::-1])  # 起始索引和结束索引都不写,表示获取所有元素,然后步长是 -1 ,表示从后往前取元素
# 打印 ['蜘蛛精', '白骨精', '唐僧', '沙和尚', '猪八戒', '孙悟空']

3、列表中一些通用方法

  • 加号 + 可以将两个列表拼接为一个列表
  • 星号 * 可以将列表重复指定的次数
  • in 用来检查指定元素是否存在于列表中,如果存在返回 True,否则返回 False
  • not in 用来检查指定元素是否不在列表中,如果不在,返回 True,否则返回 False
  • min() 获取列表中的最小值
  • max() 获取列表中的最大值
  • s.index() 获取指定元素在列表中第一次出现的索引,如果你获取列表中没有的元素会抛出异常
  • s.index() 的第二个参数,表示查找的起始位置,第三个参数表示查找的结束位置
  • s.count() 统计指定元素在列表中出现的次数
# + 和 *
# + 可以将两个列表拼接为一个列表
my_list = [1,2,3] + [4,5,6]
print(my_list)  # 打印 [1, 2, 3, 4, 5, 6]

# * 可以将列表重复指定的次数
my_list = [1,2,3] * 2
print(my_list)  # 打印 [1, 2, 3, 1, 2, 3]

# 创建一个列表
stus = ['孙悟空','猪八戒','沙和尚','唐僧','白骨精','蜘蛛精']

# in 和 not in
# in 用来检查指定元素是否存在于列表中,如果存在返回 True,否则返回 False
# not in 用来检查指定元素是否不在列表中,如果不在,返回 True,否则返回 False
print('猪八戒' in stus)  # 打印 True
print('abc' not in stus)  # 打印 True

# min() 获取列表中的最小值
# max() 获取列表中的最大值
arry = [4,5,2,9]
print(min(arry))  # 打印 2
print(max(arry))  # 打印 9

# 方法和函数基本上是一样的,只不过方法必须通过 对象.方法() 的形式调用
# s.index() 获取指定元素在列表中第一次出现的索引,如果你获取列表中没有的元素会抛出异常
print(stus.index('猪八戒'))  # 打印 1
# s.index() 的第二个参数,表示查找的起始位置,第三个参数表示查找的结束位置
strs = ['a','b','c','a']
print(strs.index('a',1))  # 这个就表示从索引为 1 开始找,一直找到结尾
# 打印 3

# s.count() 统计指定元素在列表中出现的次数
strs = ['a','b','a']
print(strs.count('a'))   # 打印 2

4、遍历列表

在开发中我们通常通过 for 循环来遍历列表

语法:

for 变量 in 序列 :

​ 代码块

# 遍历列表,指的就是将列表中所有元素都取出来
# 创建列表
stus = ['孙悟空','猪八戒','沙和尚','唐僧','白骨精','蜘蛛精']

# 通过 for 循环来遍历列表
# 语法:
#	for 变量 in 序列 :
#		代码块
# for 循环的代码块会执行多次,序列中有几个元素就会执行几次,每执行一次就会将序列中的一个元素赋值给变量,所以我们可以通过变量来获取列表中的元素
for s in stus :
	print(s)       # 这样就会把 stus 中所有元素都取出来

5、range()

range() 是一个函数,可以用来生成一个自然数的序列

range() 函数需要三个参数:

​ 1.起始位置(可以省略,默认是 0)

​ 2.结束位置

​ 3.步长(可以省略,默认是 1)

注:range() 可以用来配合 for 循环使用,通过 range() 可以创建一个执行指定次数的 for 循环

# range() 是一个函数,可以用来生成一个自然数的序列
r = range(5) 
print(r)  # 打印 range(0, 5),其实这个 r 就是 [0,1,2,3,4] 这 5 个自然数组成的序列
print(list(r))  # 将 r 转换成一个 list ,打印 [0, 1, 2, 3, 4]

# range() 函数需要三个参数:
#      1.起始位置(可以省略,默认是 0)
#      2.结束位置
#      3.步长(可以省略,默认是 1)
r = range(1,6)
print(list(r))  # 打印 [1, 2, 3, 4, 5]
r = range(1,6,2)  
print(list(r))  # 打印 [1, 3, 5]
r = range(10,0,-1)
print(list(r))  # 打印 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

# 所以综上,我们发现使用 range() 可以很方便的帮我们生成一个自然数序列

# range 可以用来配合 for 循环使用,通过 range() 可以创建一个执行指定次数的 for 循环
# for 循环除了创建方式之外,其余的都和 while 一样,包括 break 和 continue 都可以在 for 循环中使用,并且 for 循环相对于 while 循环使用起来也更加简单
for i in range(3) :
	print(i)   # 换行打印 0 1 2

# 所有的序列 for 循环都能遍历
for s in 'hello' :
	print(s)  # 换行打印 h e l l o

6、元组(tuple)

1️⃣ 介绍:

1)、元组是一个不可变的序列,它的操作方式基本上和列表是一致的,所以你在操作元组时,把元组当成一个不可变的列表就可以了

2)、一般当我们希望数据不改变时,就使用元组,其余情况都使用列表

2️⃣ 创建元组:

使用 () 来创建元组

my_tuple = ()   # 创建一个空元组
my_tuple = (1,2,3,4,5)  # 创建了一个有 5 个元素的元组

3️⃣ 应用:

  • 元组是不可变对象,不能尝试为元组中的元素重新赋值
my_tuple = (1,2,3,4,5)
my_tuple[2] = 10   # 这种写法是错误的
  • 当元组不是空元组时,括号可以省略
my_tuple = 10,20,30,40
print(my_tuple,type(my_tuple))  # 打印 (10, 20, 30, 40) <class 'tuple'>
  • 当元组不是空元组,它里面至少要有一个逗号
my_tuple = 10,     # 这表示这是一个元组,元组里面只有 10 这一个元素,这里的逗号千万不能少,有括号情况下逗号也不能少
print(my_tuple,type(my_tuple))  # 打印 (10,) <class 'tuple'>
# 假如少了逗号
my_tuple = 10
my_tuple = (10)
print(my_tuple,type(my_tuple))  # 它就变成了一个 int 类型, 打印 10 <class 'int'>
  • 元组的解包(解构),解包指的就是将元组当中每一个元素都赋值给一个变量
my_tuple = 2,4,6,8
a,b,c,d = my_tuple
print(a)   # 打印 2
print(b)   # 打印 4
print(c)   # 打印 6
print(d)   # 打印 8
  • 在对一个元组进行解包时,变量的数量必须和元组中元素的数量一致,也可以在变量前边添加一个 * ,这样该变量将会获取元组中所有剩余的元素
my_tuple = 1,2,3,4,5,6,7
a,b,*c = my_tuple     # 这里的意思就是,把 1 赋值给 a 把 2 赋值给 b ,剩下的存到一个列表里面赋值给 c 
print(a,b)  # 打印 1 2
print(c,type(c))  # 打印 [3, 4, 5, 6, 7] <class 'list'>
a,*b,c = my_tuple  # 这里的意思就是,把 1 赋值给 a 把 7 赋值给 c,中间的存到一个列表里面赋值给 b
print(a,c)
print(b)  # 打印 1 7
print(c)  # 打印 [2, 3, 4, 5, 6]
*a,b,c = my_tuple  # 这里的意思就是,把 6 赋值给 b 把 7 赋值给 c ,前面的存到一个列表里面赋值给 a
print(b,c)   # 打印 6 7
print(a)     # 打印 [1, 2, 3, 4, 5] 
  • 但是要注意的是,你不能同时出现两个或两个以上的 * 号变量
  • 除了元组能解包,像列表、字符串等序列都能解包
a,b,c = [1,2,3]
print(a,b,c)  # 打印 1 2 3
a,b,*c = 'hello'
print(a,b,c)  # 打印 h e ['l', 'l', 'o']
# 元组 tuple
# 元组是一个不可变的序列,它的操作方式基本上和列表是一致的,所以你在操作元组时,把元组当成一个不可变的列表就可以了
# 一般当我们希望数据不改变时,就使用元组,其余情况都使用列表

# 创建元组
# 使用 () 来创建元组
my_tuple = ()   # 创建一个空元组
print(my_tuple)  # 打印 ()
print(type(my_tuple))  # 打印 <class 'tuple'>

my_tuple = (1,2,3,4,5)  # 创建了一个有 5 个元素的元组
# 这种写法是错误的,元组是不可变对象,不能尝试为元组中的元素重新赋值
#my_tuple[2] = 10
print(my_tuple[2])  # 打印 3

# 当元组不是空元组时,括号可以省略
my_tuple = 10,20,30,40
print(my_tuple,type(my_tuple))  # 打印 (10, 20, 30, 40) <class 'tuple'>
# 当元组不是空元组,它里面至少要有一个逗号
my_tuple = 10,     # 这表示这是一个元组,元组里面只有 10 这一个元素,这里的逗号千万不能少,有括号情况下逗号也不能少
print(my_tuple,type(my_tuple))  # 打印 (10,) <class 'tuple'>

# 假如少了逗号
my_tuple = 10
my_tuple = (10)
print(my_tuple,type(my_tuple))  # 它就变成了一个 int 类型, 打印 10 <class 'int'>

# 元组的解包(解构),解包指的就是将元组当中每一个元素都赋值给一个变量
my_tuple = 2,4,6,8
a,b,c,d = my_tuple
print(a)   # 打印 2
print(b)   # 打印 4
print(c)   # 打印 6
print(d)   # 打印 8

a = 100
b = 200
# 我们常常有交换 a b 两个变量值的需求,这时我们就可以利用元组的解包
a,b = b,a     # 这里 b,a 相当于就是一个元组,元组解包就会把 a 的值赋值给 b ,b 的值赋值给 a 
print(a,b)  # 打印 200 100

# 在对一个元组进行解包时,变量的数量必须和元组中元素的数量一致
# 也可以在变量前边添加一个 * ,这样该变量将会获取元组中所有剩余的元素
my_tuple = 1,2,3,4,5,6,7
a,b,*c = my_tuple     # 这里的意思就是,把 1 赋值给 a 把 2 赋值给 b ,剩下的存到一个列表里面赋值给 c 
print(a,b)  # 打印 1 2
print(c,type(c))  # 打印 [3, 4, 5, 6, 7] <class 'list'>
a,*b,c = my_tuple  # 这里的意思就是,把 1 赋值给 a 把 7 赋值给 c,中间的存到一个列表里面赋值给 b
print(a,c)
print(b)  # 打印 1 7
print(c)  # 打印 [2, 3, 4, 5, 6]
*a,b,c = my_tuple  # 这里的意思就是,把 6 赋值给 b 把 7 赋值给 c ,前面的存到一个列表里面赋值给 a
print(b,c)   # 打印 6 7
print(a)     # 打印 [1, 2, 3, 4, 5] 

# 但是要注意的是,你不能同时出现两个或两个以上的 * 号变量
# 除了元组能解包,像列表、字符串等序列都能解包
a,b,c = [1,2,3]
print(a,b,c)  # 打印 1 2 3
a,b,*c = 'hello'
print(a,b,c)  # 打印 h e ['l', 'l', 'o']

7、可变对象

之前我们就说过每个对象中都保存了三个数据:

  • id(标识)
  • type(类型)
  • value(值)

一个对象一旦创建,那么它的 id 和 type 就是固定的不可改变,唯一可变的就是 value 值,而这个 value 值也仅限于可变对象,像 int 、string、bool 它们的 value 值是不可变的。

==列表就是一个可变对象==

可变对象

简单解释一下上面这个图:

首先我有一个列表 a = [1,2,3,4],那么在内存中就会有一个变量 a,然后开辟一块空间去保存这个对象,变量 a 中存的就是这块空间的地址,我们说列表是一个可变对象,怎么体现出它可变呢?其实就是内存中这个==对象的 value 值可变==。

现在我做一个操作就是让 a[0] = 10,这个操作的意思就是通过变量 a 找到这个内存对象,修改了对象索引为 0 的值,所以这个时候如果你打印 a 的话,结果就是 [10,2,3,4]

现在我再做一个操作就是让 a = [4,5,6],那么这实际上就相当于是一个新的对象,新的对象我就要在内存中再开辟一块新的空间去保存这个变量,变量 a 中存的就是这个新的变量的地址,相当于把指针的指向改了一下,原来指向旧的内存空间,现在指向新的内存空间

所以总结一下:

  • a[0] = 10(改对象)

1、这个操作是通过变量去修改对象的值

2、这种操作不会改变变量所指向的对象

3、当我们去修改变量时,如果有其他变量也指向了该对象,则修改也会在其他变量中体现

  • a = [4,5,6](改变量)

1、这个操作是在给变量重新赋值

2、这种操作会改变变量所指向的对象

3、为一个变量重新赋值时,不会影响其他的变量

a = [1,2,3,4]
print("修改前:",a,id(a))  # 修改前: [1, 2, 3, 4] 4383756168

# 通过索引修改列表
a[0] = 10
print("修改后:",a,id(a))  #修改后: [10, 2, 3, 4] 4383756168
# 我们发现修改前和修改后对象的 id 都没有发生变化,说明通过索引去修改这种方式,它是在修改对象

# 为变量重新赋值
a = [4,5,6]
print("修改后:",a,id(a))  # 修改后: [4, 5, 6] 4386496328
# 通过这种方式我们发现对象的 id 值变了,说明这又是一个新的对象

a = [1,2,3]
b = a   # 变量 a 中存的是对象的地址,这里相当于把对象地址赋值给 b ,那么 b 也就会指向这个内存空间
b[0] = 10   # 所以在这里通过 b 修改了对象的值,相应的 a 指向的值也就会修改了
print(a)  #[10, 2, 3]

8、== 和 is

  • == 和 != 比较的是对象的值是否相等
  • is 和 is not 比较的是对象的 id 是否相等(比较两个对象是否是同一个对象)
a = [1,2,3]
b = [1,2,3]
print(a == b)  # True, a 和 b 的值相等,使用 == 会返回 True
print(a is b)  # False, a 和 b 不是同一个对象,内存地址不同,使用 is 会返回 False

9、字典

1️⃣ 基本介绍

  • 字典属于一种新的数据结构,称为映射(mapping)
  • 字典的作用和列表类似,都是用来存储对象的容器
  • 列表存储数据的性能很好,但是查询数据的性能很差
  • 在字典中每一个元素都有唯一一个名字,通过这个唯一的名字可以快速查找到指定的元素
  • 在查询元素时,字典的效率是非常快的
  • 在字典中可以保存多个对象,每个对象都会有一个唯一的名字
    • 这个唯一的名字,我们称之为键(key),通过 key 可以快速的查询 value
    • 这个对象,我们称之为值(value)
    • 所以字典,我们也称为叫键值对(key-value)结构
    • 每个字典中都可以有多个键值对,而每一个键值对我们称其为一项(item)

2️⃣ 语法

  • 使用 {} 来创建字典
  • 使用 {key:value,key:value,key:value}来创建一个包含数据的字典

3️⃣ 重要讲解

  • 字典的值可以是任意对象
  • 字典的键可以是任意的不可变对象(int、str、bool、tuple 等等),但是我们一般都会使用 str
  • 字典的键是不能重复的,如果出现重复的,后面的会替换前面的
# 字典
# 使用 {} 来创建字典
d = {}   # 创建一个空字典
print(type(d))   # 打印 <class 'dict'>

# 创建一个包含数据的字典
d = {'name':'Jimme','age':44,'gender':'男'}
print(d)  # 打印 {'name': 'Jimme', 'age': 44, 'gender': '男'}

# 需要根据键来获取值
print(d['name'],d['age'])  # 打印 Jimme 44

# 如果使用了字典中不存在的键,会报错
print(d['hello'])  # 字典中没有这个键,你使用就会报错

4️⃣ 字典的使用

1)、创建字典

① 使用 {},{k1:v1,k2:v2,k3:v3}的方式来创建字典,这种方式前面已经有过介绍,这里不做过多介绍

② 使用 dict() 函数来创建字典。函数里面每一个参数都是一个键值对,参数名就是键,参数值就是值(这种方式创建的字典,key 都是字符串)

# 使用 dict() 函数来创建字典
# 每一个参数都是一个键值对,参数名就是键,参数值就是值(这种方式创建的字典,key 都是字符串)
d = dict(name='Jimmy',age = 18,gender = '男')
print(d,type(d))   # {'name': 'Jimmy', 'age': 18, 'gender': '男'} <class 'dict'>

③ 也可以将一个包含有双值子序列的序列转换为字典。

解释一下双值子序列:

  • 双值序列,序列中只有两个值,如:[1,2]、('a',3)、'ab'
  • 子序列,如果序列中的元素也是序列,那么我们就称这个元素为子序列。比如:[(1,2),(3,4)] 我们在列表中放了两个元组作为元素,那么这两个元组就作为列表的子序列
# 也可以将一个包含有双值子序列的序列转换为字典
# 双值序列,序列中只有两个值,[1,2],('a',3),'ab'
# 子序列,如果序列中的元素也是序列,那么我们就称这个元素为子序列
# 比如:[(1,2),(3,4)]  我们在列表中放了两个元组作为元素,那么这两个元组就作为列表的子序列
d = dict([('name','Jimmy'),('age',44)])
print(d,type(d))   # {'name': 'Jimmy', 'age': 44} <class 'dict'>

2)、len() 获取字典中键值对的个数

d = dict([('name','Jimmy'),('age',44)])
# len() 获取字典中键值对的个数
print(len(d))   # 2 

3)、in 检查字典中是否包含指定的键,not in 检查字典中是否不包含指定的键

d = dict([('name','Jimmy'),('age',44)])
print('name' in d)  # True
print('hello' in d)  # False

4)、获取字典中的值

① 根据键来获取值,语法:d[key]

d = dict([('name','Jimmy'),('age',44)])
# 获取字典中的值,根据键来获取值
# 语法:d[key]
print(d['age'])  # 44,这里注意 d['age'] 中的引号一定要加,因为原来的 key 就是带引号的,如果你不加引号,它会把 age 当成一个变量处理
n = 'name'
print(d[n])  # Jimmy,这种情况不需要加引号,因为这里的 n 就是一个变量

② 通过 get(key[, default]) 来获取值

d = dict([('name','Jimmy'),('age',44)])
# 通过 [] 来取值时,如果键不存在,会抛出异常 KeyError
# get(key[, default])  该方法用来根据键获取字典中的值,如果获取的键在字典中不存在,会返回 None,也可以指定一个默认值来作为第二个参数,这样获取不到值时会返回第二个参数
print(d.get('name'))  # Jimmy
print(d.get('name','这是默认值'))  # Jimmy,能够获取到值的话,就用获取到的值
print(d.get('hello','这是默认值'))  # 获取不到值的话,用默认值

5)、修改字典

① 通过 d[key]=value 如果 key 存在则覆盖,不存在则添加

d = dict([('name','Jimmy'),('age',44)])
# d[key]=value   如果 key 存在则覆盖,不存在则添加
d['name'] = '林志颖'
print(d)  # {'name': '林志颖', 'age': 44}
#假如字典中没有这个 key(不存在则添加)
d['address'] = '台湾'
print(d)  # {'name': '林志颖', 'age': 44, 'address': '台湾'}

② 通过 setdefault(key[, default]) 可以用来向字典中添加 key-value

d = {'name': '林志颖', 'age': 44, 'address': '台湾'}
# setdefault(key[, default])  可以用来向字典中添加 key-value
# 如果 key 已经存在于字典中,则返回 key 对应的 value 值,不会对字典做任何操作
# 如果 key 不存在,则向字典中添加这个 key,并设置 value ,这个 value 就是你写的默认值,并返回默认值
result = d.setdefault('name','kelly')  # 字典中已经存在了 name 这个 key ,不对字典做任何操作,返回 key 对应的 value 值
print(result) # 林志颖
print(d)  # {'name': '林志颖', 'age': 44, 'address': '台湾'}
result = d.setdefault('son','kimi')  # 字典中没有 son 这个 key ,向字典中添加这个 key 并设置 value 值,返回你设置的 value 值
print(result)   # kimi
print(d)   # {'name': '林志颖', 'age': 44, 'address': '台湾', 'son': 'kimi'}

③ 通过 update([other]) 将其他字典中的 key-value 添加到当前字典中,如果有重复的 key ,后面的会替换前面的

# update([other])  将其他字典中的 key-value 添加到当前字典中,如果有重复的 key ,后面的会替换前面的
d2 = {'a':1,'b':2,'c':3}
d3 = {'d':4,'e':5,'f':6}
d2.update(d3)
print(d2)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
d4 = {'d':4,'e':5,'a':6}  # d4 中有一个 key 和 d2 中重了
d2.update(d4)   # 后面的会覆盖前面的
print(d2)  # {'a': 6, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}

6)、删除字典

① 使用 del 来删除字典中 key-value

# 删除,可以使用 del 来删除字典中 key-value
d = {'a':1,'b':2,'c':3,'d':4}
del d['a']
print(d)  # {'b': 2, 'c': 3, 'd': 4}
# del d['aa']  如果你删除一个不存在的 key 也会报错 

② 使用 popitem() 随机删除字典中的一个键值对,一般都会删除最后一个键值对,删除之后会将删除的 key-value 作为返回值返回

# popitem() 随机删除字典中的一个键值对,一般都会删除最后一个键值对,删除之后会将删除的 key-value 作为返回值返回。
# 这个返回值是一个元组,元组中有两个元素,第一个元素是删除的 key ,第二个元素是删除的 value
d = {'a':1,'b':2,'c':3,'d':4}
result = d.popitem()
print(d)    # {'a': 1, 'b': 2, 'c': 3}
print(result,type(result))   # ('d', 4) <class 'tuple'>   返回值是一个元组
#d = {}
#d.popitem()   # 当使用 popitem() 去删除一个空字典时,会报错

pop(key[, default]) 根据 key 删除字典中的 key-value ,会将被删除的 value 返回

# pop(key[, default])  根据 key 删除字典中的 key-value 会将被删除的 value 返回
d = {'a':1,'b':2,'c':3,'d':4}
result = d.pop('b')
print(d)   # {'a': 1, 'c': 3, 'd': 4}
print(result)  # 2    会将被删的 key 对应的 value 值返回
#d.pop('aa')   注意,如果你删除一个不存在的 key 就会报错
result = d.pop('aa','这是一个默认值')
print(result)  # 这是一个默认值   我们说如果你删除一个不存在的 key 会报错,但是如果你指定的默认值,不会报错,会返回这个默认值

clear() 用来清空字典

# clear() 用来清空字典
d = {'a':1,'b':2,'c':3,'d':4}
d.clear()
print(d)  # {} 字典中的元素会被全部清空

7)、拷贝字典

copy() 该方法用于对字典进行浅复制

# copy() 该方法用于对字典进行浅复制
# 你复制以后的对象和原对象是独立的,你修改一个不会影响另一个
d = {'a':1,'b':2,'c':3,'d':4}
d2 = d.copy()
# d2 = d 如果你这么写的话,你会发现 d 和 d2 是同一个对象,因为两个变量都指向同一块内存地址
print(d,id(d))   # {'a': 1, 'b': 2, 'c': 3, 'd': 4} 4539581856
print(d2,id(d2)) # {'a': 1, 'b': 2, 'c': 3, 'd': 4} 4539909248

# 下面解释一下什么是浅复制?
# 浅复制就是它只会复制对象里面的值,如果对象里面还是一个可变对象,那么这个可变对象不会被复制
d = {'a':1,'b':{'name':'kimi','age':10},'c':3}
d2 = d.copy()
d['b']['name'] = 'Jimmy'
print(d,id(d))   # {'a': 1, 'b': {'name': 'Jimmy', 'age': 10}, 'c': 3} 4413117352
print(d2,id(d2)) # {'a': 1, 'b': {'name': 'Jimmy', 'age': 10}, 'c': 3} 4413117856
# 上面我们也说了,copy() 是拷贝一个和原对象独立的一个对象,但是在这里我们把 d 中的属性给改了,发现 d2 中的属性也
# 被改了,原因就是 copy() 是一个浅拷贝,如果你对象里面还有一个可变对象,这个可变对象是不会被拷贝的
# 而且我们一般都会使用浅复制,深复制会把所有东西都复制,这样就会导致性能会很差,所以深复制我们用的都比较少,我们用的比较多的就是浅复制

8)、遍历字典

keys() 该方法会返回字典中所有的 key(这个所有的 key 你就可以把它看成一个序列)

# keys() 该方法会返回字典中所有的 key(这个所有的 key 你就可以把它看成一个序列)
d = {'name':'Jimmy','age':44,'gender':'男'}
print(d.keys(),type(d.keys()))  # dict_keys(['name', 'age', 'gender']) <class 'dict_keys'>

# 通过遍历 keys() 来获取所有的键
for k in d.keys() :
	print(k,"--",d[k])   # 会把所有的 key 和 value 值打印出来

values() 该方法会返回字典中所有的 value(这个所有的 value 你也可以把它看成一个序列)

d = {'name':'Jimmy','age':44,'gender':'男'}
# values() 该方法会返回字典中所有的 value(这个所有的 value 你也可以把它看成一个序列)
for v in d.values() :
	print(v)     # 会把所有的 value 值给打印出来

items() 该方法会返回字典中所有的项,它会返回一个序列,序列中包含有双值子序列。双值分别是字典中的 key 和 value

# items() 该方法会返回字典中所有的项,它会返回一个序列,序列中包含有双值子序列。双值分别是字典中的 key 和 value
print(d.items())  # dict_items([('name', 'Jimmy'), ('age', 44), ('gender', '男')])
# 我们遍历的时候,每次他都会返回一个元组,所以我们只需要对元组进行解包即可
for k,v in d.items() :
	print(k,'=',v)

#name = Jimmy
#age = 44
#gender = 男

10、集合

  • 集合和列表非常相似
  • 不同点:
    • 集合中只能存储不可变对象
    • 集合中存储的对象是无序的(不是按照元素的插入顺序保存)
    • 集合中不能出现重复的元素

1)、创建集合

① 使用 {} 来创建集合

# 使用 {} 来创建集合
s = {4,2,5,6,1,1,1}   # 集合中的元素是不能重复的,如果你插入重复的,只能保存进去一个
print(s,type(s))  # {1, 2, 4, 5, 6} <class 'set'>

# 如果你想要创建一个空集合(需要使用 set() 函数来创建)
s = {}   # 这种写法是错误的,这样表示 s 是一个字典

② 使用 set() 函数来创建集合

s = set()   # 空集合
print(s,type(s))   # set() <class 'set'>

# 可以使用 set() 来将序列和字典转换为集合
s = set([1,2,4,3,1]) 
print(s)  # {1, 2, 3, 4}
s = set('hello')
print(s)   # {'l', 'e', 'h', 'o'}
s = set({'a':1,'b':2})   # 使用 set() 将字典转换为集合时,只会包含字典中的键
print(s)  # {'a', 'b'}

2)、向集合中添加元素

① 使用 add() 向集合中添加元素

s = {'a','d',2,1}
# add() 向集合中添加元素
s.add(66)
print(s)   # {1, 66, 'd', 2, 'a'}

update() 将一个集合中的元素添加到当前集合中。update() 中可以传递序列或者字典作为参数,字典只会使用键

s = {2,4,6,5}
s.update(set('hello'))
print(s)   # {'e', 2, 4, 5, 6, 'o', 'l', 'h'}
s = {2,4,6,5}
s.update({'a':1,'c':4})
print(s)  # {2, 4, 5, 6, 'c', 'a'}

3)、删除集合中的元素

pop() 随机删除集合中的一个元素,并返回被删的这个元素

# pop() 随机删除集合中的一个元素,并返回被删的这个元素
s = {2,4,6,5}
result = s.pop()
print(s)  # {4, 5, 6}
print(result) # 2

remove() 删除集合中的指定元素

# remove() 删除集合中的指定元素
s = {2,4,6,5}
s.remove(6)
print(s)   # {2, 4, 5}

clear() 清空集合

# clear() 清空集合
s = {2,4,6,5}
s.clear()
print(s)  # set()

4)、常用方法

① 使用 innot in 来检查集合中的元素

# 使用 in 和 not in 来检查集合中的元素
s = {'a','d',2,1}
print('a' in s)  # True

② 使用 len() 来获取集合中元素的数量

s = {'a','d',2,1}
# 使用 len() 来获取集合中元素的数量
print(len(s))  # 4

11、集合的运算

# 在对集合做运算时,不会影响原来集合,而是返回一个元素结果
# 创建两个集合
s1 = {1,2,3,4,5}
s2 = {3,4,5,6,7}

# 使用 & 做交集运算
result = s1 & s2
print(result)  # {3, 4, 5}

# 使用 | 做并集运算
result = s1 | s2
print(result)  # {1, 2, 3, 4, 5, 6, 7}

# 使用 - 做差集运算
result = s1 - s2
print(result)  # {1, 2}

# 使用 ^ 做异或集运算(获取只在一个集合中出现的元素)
result = s1 ^ s2
print(result)  # {1, 2, 6, 7}

# <= 检查一个集合是否是另一个集合的子集
# 如果 a 集合中的元素全部在 b 集合中出现,那么 a 集合就是 b 集合的子集,b 集合时 a 集合的超集
a = {1,2,3}
b = {1,2,3,4,5}
result = a <= b
print(result)  # True
a = {1,2,3}
b = {1,2,3}
result = a <= b
print(result)  # True

# < 检查一个集合是否是另一个集合的真子集
# 如果超集 b 中含有子集 a 中的所有元素,并且 b 中还有 a 中没有的元素,则 b 就是 a 的真超集,a 是 b 的真子集
result = {1,2,3} < {1,2,3}
print(result)  # False
result = {1,2,3} < {1,2,3,4}
print(result)  # True

# >= 检查一个集合是否是另一个集合的超集
# > 检查一个集合是否是另一个的真超集

十、函数

1、函数简介

  • 函数也是一个对象
  • 对象是内存中专门存储数据的一块区域
  • 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行调用
  • 函数中的代码不会立即执行,需要调用函数才会执行
  • 定义函数一般都是要实现某种功能的

定义函数:

def 函数名([形参1,形参2,…形参n]) :

代码块

这里函数名必须要符合标识符规范(字母、数字、下划线)

调用函数:

函数名()

# 定义一个函数
def  fn():
	print('这是我的第一个函数')

print(fn)  # <function fn at 0x10c6d61e0> 这里打印的是函数地址
print(type(fn)) # <class 'function'>

# 调用函数
fn()   # 这是我的第一个函数

2、函数参数

  • 在定义函数时,可以在函数名后的括号中,定义数量不等的形参,多个形参之间使用逗号隔开
  • 形参,定义形参就相当于在函数内部声明了变量,但是并不赋值
  • 实参,如果函数定义时,指定了形参,那么在调用函数时,必须传递实参,实参将会赋值给对应的形参,简单来说有几个形参,就有几个实参

实参的传递方式:

① 位置参数

位置参数就是将对应位置的实参赋值给对应位置的形参,第一个实参赋值给第一个形参,第二个实参赋值给第二个形参

② 关键字参数

关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数

# 定义函数时指定形参
def sum(a,b) :
	print(f'a + b = {a + b}') 

# 调用函数时,传递实参
sum(2,3)  # a + b = 5

# 定义形参时,可以为形参指定默认值
# 指定了默认值以后,如果用户传递了参数,则默认值没有任何作用,如果用户没有传递,则默认值生效
def aa(a,b,c = 10):
	print(f'a = {a},b = {b}, c = {c}')


aa(2,3,4)  # a = 2,b = 3, c = 4
aa(2,3)  # a = 2,b = 3, c = 10  这里默认值生效

# 实参的传递方式
# 位置参数
# 位置参数就是将对应位置的实参赋值给对应位置的形参
# 第一个实参赋值给第一个形参,第二个实参赋值给第二个形参

# 关键字参数
# 关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
aa(b =2,a=1,c=3)
# 比如我们之前使用过的 print 函数,这个 end 就是关键字参数
print('hello',end='')
# 位置参数和关键字参数可以混合使用
# 混合使用关键字参数和位置参数时,必须将位置参数写到前面
aa(1,2,c = 6)  # a = 1,b = 2, c = 6

注:

① 位置参数和关键字参数可以混合使用

②位置参数和关键字参数混合使用时,必须将位置参数写在前面

3、实参的类型

函数在调用时,解析器不会检查实参的类型,==实参可以传递任意类型的变量==

def fn(a):
	print('a =',a)

# 函数在调用时,解析器不会检查实参的类型
# 实参可以传递任意类型的变量
fn(10) # a = 10
fn(True) # a = True
fn([1,2,3]) # a = [1, 2, 3]
print('-----')

def fn2(a):
	# 在函数中对形参进行重新赋值,不会影响其他的变量
	a = 20   # 这里对 a 进行重新赋值,并不影响外边的变量
	print('a =',a)   # a = 20

c = 10
fn2(c)
print('c =',c)   # c = 10

def fn3(a):
	# 这里 a 变成一个列表了,我们尝试修改列表中的元素
	# 如果形参是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量
	a[0] = 10   # 这里我通过形参去修改对象的值,发现外面的实参也被改变了
	print('a =',a)   # a = [10, 2, 3]

c = [1,2,3]
fn3(c)   # 这里我把 c 作为实参传递到函数里,当我在函数里通过形参去修改对象的值,发现 c 也被改变了
print('c =',c)   #c = [10, 2, 3]

# 所以当你传的是一个可变对象的时候,而你又不希望在函数内部的操作影响到函数外部的可变对象,这时候就可以考虑传递一个对象的副本
def fn4(a):
	a[0] = 15  # 这里通过形参去修改可变对象,发现外面的实参并没有改变,因为传递的是一个对象的副本
	print('a =',a)   # a = [15, 2, 3]
c = [1,2,3]
# 这里传递一个 c 的副本
fn4(c.copy())
# 或者这么写,c[:] 这就是切片,省略的起始位置和结束位置,就会返回一个新的副本
#fn4(c[:])
print('c =',c)   # c = [1, 2, 3]

4、不定长参数

1)、在定义函数时,可以在形参前边加一个「 * 」号,这样这个形参将会获取到所有的实参。并且它会把所有的实参保存到一个元组中。

# 在定义函数时,可以在形参前边加一个 * 号,这样这个形参将会获取到所有的实参
# 它会把所有的实参保存到一个元组中
def fn(*a):
	# *a 会接收所有的位置实参,并将这些实参统一保存到一个元组中(装包)
	print('a =',a,type(a))  # a = (1, 2, 3) <class 'tuple'>

fn(1,2,3)

# 装包:将一个一个零散的参数统一装到一个元组中

# 定义一个函数可以求任意个数字的和
def sum(*a):
	result = 0
	for n in a:
		result += n
	print(result)

sum(1,2,3,4,5)  # 15
sum(1,2,3)   # 6

2)、带星号的形参只能有一个。带星号的参数可以和其他参数配合使用

# 带星号的形参只能有一个
# 带星号的参数可以和其他参数配合使用
# 这里就表示,第一个参数给 a,第二个参数给 b,剩下所有参数都保存到 c 的元组中
def fn2(a,b,*c):
	print('a =',a)
	print('b =',b)
	print('c =',c)
fn2(1,2,3,4)  # 这样就会把 1 赋值给 a ,2 赋值给 b,3和4赋值给 c
fn2(1,2,3,4,5)# 这样就会把 1 赋值给 a ,2 赋值给 b,3和4和5赋值给 c

3)、可变参数不是必须写在最后,但是注意:带 「*」 的参数后的所有参数,必须以关键字参数的方式传递

# 可变参数不是必须写在最后,但是注意:带 * 的参数后的所有参数,必须以关键字参数的方式传递
# 这里就表示,第一个参数给 a ,剩下的位置参数给 b 的元组,c 必须使用关键字参数
def fn3(a,*b,c):
	print('a =',a)  # a = 1
	print('b =',b)  # b = (2, 3, 4)
	print('c =',c)  # c = 5
fn3(1,2,3,4,c = 5)

# 这里就表示,所有的位置参数都给 a, b 和 c 必须使用关键字参数
def fn4(*a,b,c):
	print('a =',a)  
	print('b =',b)  
	print('c =',c)  
fn4(1,2,3,b = 4,c = 5)

4)、如果在形参的开头直接写一个 「*,」,则要求我们的所有参数必须以关键字参数的形式传递

# 如果在形参的开头直接写一个 「*,」,则要求我们的所有参数必须以关键字参数的形式传递
def fn5(*,a,b,c):
	print('a =',a)  
	print('b =',b)  
	print('c =',c)
fn5(a=1,b=2,c=3)    # 这里要求传递的参数都必须以关键字参数的方式传递

5)、「*」形参只能接收位置参数,而不能接收关键字参数

# 「*」形参只能接收位置参数,而不能接收关键字参数
def fn6(*a):
	print('a =',a)

#fn6(a = 1)   # 这种写法错误,执行会报错
fn6(1,2,3)  # 这种写法正确

6)、「**」形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中。字典的 key 就是参数的名字,字典的 value 就是参数的值。「**」形参只能有一个,并且==只能写在所有参数的最后==

# 「**」形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
# 字典的 key 就是参数的名字,字典的 value 就是参数的值
# 「**」形参只能有一个,并且只能写在所有参数的最后
def fn7(a,b,**c):
	print('a =',a)  # a = 1
	print('b =',b)  # b = 2
	print('c =',c)
fn7(1,2,d = 5,e = 6)  # c = {'d': 5, 'e': 6}

5、参数解包

1)、对序列类型的实参进行解包

我们在传递参数时,可以在序列类型的参数前面添加 「*」号,这样就会自动把序列中的元素进行解包作为参数传递

但是**注意:**序列中元素的个数必须和形参的个数一致

# 参数的解包(拆包)
def fn8(a,b,c):
	print('a =',a)
	print('b =',b)
	print('c =',c)

# 创建一个元组
t = (10,20,30)

# 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递
# 注意:这里要求序列中元素的个数必须和形参的个数一致
fn8(*t)  # 这样就会自动把元组解包,然后把参数传递给形参

2)、对字典进行解包

可以通过 「**」来对字典进行解包操作

def fn8(a,b,c):
	print('a =',a)
	print('b =',b)
	print('c =',c)

# 创建一个字典
d = {'a':100,'b':200,'c':'300'}    # 字典的 key 要和形参对应上
# 通过 「**」 来对一个字典进行解包操作
fn8(**d)

6、返回值

  • 返回值就是函数执行以后返回的结果
  • 可以通过 return 来指定函数的返回值
  • return 后面可以跟任意的对象,返回值甚至可以是一个函数
  • 在函数中,如果仅仅写一个 return 或者不写 return ,则相当于 return None
# 返回值就是函数执行以后返回的结果
# 可以通过 return 来指定函数的返回值
# 可以通过一个变量来接收函数的返回值

# return 跟什么值,函数就会返回什么值
# return 后面可以跟任意的对象,返回值甚至可以是一个函数
def fn():
	return 'hello'

result = fn()
print(result)   # 打印 hello

# 返回值是一个函数
def f2():
	def f3():
		print('我是 f3 函数')
	return f3
result = f2()   # 这个 result 就是 f3 函数
print(result)  # <function f2.<locals>.f3 at 0x10fc930d0>
result()   # 我是 f3 函数

# 如果仅仅写一个 return 或者不写 return ,则相当于 return None
def f4():
	return
result = f4()
print(result)   # None

# 在函数中,return 后的代码都不会执行,return 一旦执行函数自动结束
def f5():
	print('hello')
	return
	print('abc')
f5()   # 发现只打印了 hello

# f6 和 f6() 的区别
def f6():
	return 10

print(f6)   # f6 是函数对象,打印 f6 实际上是在打印函数对象
print(f6()) # f6() 是在调用函数,打印 f6() 实际上是在打印 f6() 函数的返回值

7、文档字符串

1)、help()函数

help() 是 python 中的内置函数,通过 help() 函数可以查询 python 中函数的用法

语法:

help(函数对象)

2)、文档字符串

  • 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明
  • 当我们编写了文档字符串,就可以通过 help() 函数来查看函数的说明
  • 文档字符串非常简单,直接在函数的第一行写一个字符串就是文档字符串
  • 写文档字符串时,我们一般使用三个引号,因为三个引号可以跨行
# help() 是 python 中的内置函数,通过 help() 函数可以查询 python 中函数的用法
# 语法:help(函数对象)
help(print)  # 获取 print() 函数的使用说明

# 文档字符串(doc str)
# 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明
# 当我们编写了文档字符串,就可以通过 help() 函数来查看函数的说明
# 文档字符串非常简单,直接在函数的第一行写一个字符串就是文档字符串

def fn(a,b,c):    # 写文档字符串时,我们一般使用三个引号,因为三个引号可以跨行
	'''
		这是一个文档字符串示例
		函数的作用:
		函数的参数:
			a,作用,类型,默认值
			b,作用,类型,默认值
			c,作用,类型,默认值
	'''
	return 10
help(fn)

# 说到文档字符串,在这里多说一下,我们在调用 fn2 函数时,函数的形参我们不知道传什么类型,所以我们可以在形参中说明一下,这样方便我们自己理解也方便他人阅读
def fn2(a:int,b:bool,c:str='hello') -> int:
	return 10

# 上面就表示,a 需要 int 类型的参数,b 需要 bool 类型的参数,c 需要字符串类型的参数(需要些默认值的话直接在后面写等于多少即可),返回值是 int 类型
# 但是注意这些都不是强制的,只做展示使用(就是说你要是非传别的类型的参数也可以)

8、作用域

作用域指的是变量生效的区域

在 Python 中一共有两种作用域:

  • 全局作用域
    • 全局作用域在程序执行时创建,在程序执行结束销毁
    • 所有函数以外的区域都是全局作用域
    • 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
  • 函数作用域
    • 函数作用域在函数调用时创建,在调用结束时销毁
    • 函数每调用一次就会产生一个新的函数作用域
    • 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
  • 变量的查找
    • 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,如果没有则继续去上一级作用域中寻找,以此类推,直到找到全局作用域,如果依然没有找到,则会抛出异常

小贴士:

① 在函数中为变量赋值时,默认都是为局部变量赋值

② ==如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量==

def fn2():
	a = 5
	def fn3():
		a = 10
		print('fn3 中 a=',a)  # 先会在 fn3 中找 a 这个变量,如果没有则会去上一级作用域寻找,直到找到全局作用域,如果都没有,抛异常
	fn3()
fn2()

c = 5
def fn3():
	c = 10   # 在函数中为变量赋值时,默认都是为局部变量赋值
	print('函数内部 c =',c)
fn3()
print('函数外部 c =',c)

def fn4():
	# 如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量
	global c   # 声明在函数内部使用的这个变量时全局变量,如果你此时再去修改变量 c ,就是在修改全局变量
	c = 20
	print('函数内部 c =',c)
fn4()
print('函数外部 c =',c)

9、命名空间(namespace)

  • 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中
  • 每一个作用域都会有一个它对应的命名空间
  • 全局命名空间,用来保存全局变量。函数命名空间,用来保存函数中的变量
  • 命名空间实际上就是一个字典,是一个专门用来存储变量的字典

说两个函数:

  • locals() 用来获取当前作用域的命名空间
    • 如果在全局作用域中调用 locals() 则获取全局命名空间,如果在函数作用域中调用 locals() 则获取函数命名空间
  • globals() 函数可以在任意位置获取全局命名空间
    • 这个适用于我在函数内部想获取全局命名空间

注意:

你在函数作用域中可以获取到全局命名空间,但是在全局作用域获取不到函数命名空间。这就像你定义了一个全局变量

你在函数作用域中是可以访问这个全局变量的,但是你在函数中定义一个变量,你在全局作用域中是访问不到的

# 命名空间(namespace)
# 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中
# 每一个作用域都会有一个它对应的命名空间
# 全局命名空间,用来保存全局变量。函数命名空间,用来保存函数中的变量
# 命名空间实际上就是一个字典,是一个专门用来存储变量的字典


# locals() 用来获取当前作用域的命名空间
# 如果在全局作用域中调用 locals() 则获取全局命名空间,如果在函数作用域中调用 locals() 则获取函数命名空间
scope = locals()  # 当前命名空间
print(type(scope))   # <class 'dict'>  这个 scope 就是一个字典

a = 100
print(a)
print(scope['a']) # 这两种方式获取 a 效果上是一样的,首先 a 是一个全局变量,那么它就保存在全局命名空间中,命名空间实际上又是一个字典,所以我当然就可以使用字典获取值的方式来获取 a

# 上面我们既然说了所有的变量都保存在这个字典中,那么我们向字典中添加一个 key 是不是就相当于定义一个变量了呢
# print(c)  c 这个变量我们没有定义过,所以你在这里打印肯定会报错
scope['c'] = 1000   # 向字典中添加一个变量 c,这样就相当于定义了一个全局变量(一般不建议这么做)
print(c)  # 1000


def fn5():
	scope = locals()   # 在函数内部调用 locals() 会获取到函数的命名空间
	# 这就相当于在函数中声明了一个变量 b
	scope['b'] = 20   # 可以通过 scope 来操作函数的命名空间(不建议这么做)
	print(scope)

fn5()   #{'b': 20}

# 对于 scope 这个对象它的主要作用不是让我们去改它,而是它就相当于一个容器替我们去存储变量(你只要知道变量时存储在这里面的就 ok 了)


def fn6():
	# 假如我想在函数内部获取全局命名空间
	# globals() 函数可以在任意位置获取全局命名空间
	global_scope = globals()
	global_scope['d'] = 30   # 这就相当于定义了一个全局变量,你在全局作用域是可以使用这个全局变量 d 的
fn6()

# 注意:你在函数作用域中可以获取到全局命名空间,但是在全局作用域获取不到函数命名空间。这就像你定义了一个全局变量
# 你在函数作用域中是可以访问这个全局变量的,但是你在函数中定义一个变量,你在全局作用域中是访问不到的

小说明:

这个命名空间它的主要作用不是让我们去改它,而是它就相当于一个容器替我们去存储变量(你只要知道变量时存储在这里面的就 ok 了)

10、递归

  • 递归简单理解就是自己去引用自己
  • 递归式函数,在函数中自己调用自己

递归是解决问题的一种方式,它和循环很像,它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题

递归式函数的两个条件:

  • 基线条件
    • 问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了
  • 递归条件
    • 将问题继续分解的条件
# 创建一个函数可以求任意数的阶乘
def factorial(num):
	'''
		该函数用来求任意数的阶乘
		参数:
			num 要求阶乘的数字
	'''
	result = num
	for i in range(1,num):
		result *= i
	return result
a = factorial(3)
print(a)

# -------------------------------递归实现-----------------------------------------------
# 这是一个无穷递归,如果这个函数被调用,内存会溢出,效果类似于死循环
def fn():
	fn()

# 10! = 10 * 9!
# 9! = 9 * 8!
def factorial(num):
	'''
		该函数用来求任意数的阶乘
		参数:
			num 要求阶乘的数字
	'''
	# 基线条件 判断 num 是否为 1,如果为 1 则此时不能再继续递归
	if num == 1:
		# 1 的阶乘就是 1 ,直接返回 1
		return 1
	# 递归条件
	return num * factorial(num - 1)
	
print(factorial(4))

递归练习:

# 创建一个函数 power 来为任意数字做幂运算 
# 10 ** 3 = 10 * 10 ** 2
# 10 ** 2 = 10 * 10 ** 1 
def power(num, n):
	'''
		该函数用来求任意数字的任意次幂
		参数:
			num  哪个数字
			n  多少次幂
	'''
	# 基线条件 判断 n 是否等于 1
	if n == 1:
		return num
	if n == 0 :
		return 1
	return num * power(num, n - 1)

print(power(6,0))	

# 创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回 True,否则返回 False
# 回文字符串,字符串从前往后念和从后往前念是一样的
# abcdefgfedcba
# 先检查第一个字符和最后一个字符是否一致,如果不一致则不是回文字符串,如果一致看剩余部分是否是回文字符串
# 检查 abcdefgfedcba 是不是回文字符串
# 检查 bcdefgfedcb 是不是回文字符串
# 检查 cdefgfedc  是不是回文字符串
# 检查 defgfed  是不是回文字符串
# 检查 efgfe  是不是回文字符串
# 检查 fgf  是不是回文字符串
# 检查 g 是不是回文字符串
def hui_wen(s):
	'''
		该函数用来检查指定的字符串是否是回文字符串,如果是返回 True,如果不是返回 False
		参数:
			s : 要检查的字符串
	'''
	# 基线条件
	if len(s) < 2:
		# 如果字符串长度小于 2,则字符串一定是回文
		return True
	elif s[0] != s[-1]:
		# 第一个字符和最后一个字符不相等,不是回文字符串
		return False
	# 递归条件
	return hui_wen(s[1:-1])  # 这里递归的时候做了一个切片,因为我要把第一个和最后一个去掉,所以正好从第二个位置开始截不包括最后一个位置

十一、函数式编程

在 Python 中,函数式一等对象。一等对象一般都会具有如下特点:

  • 对象是在运行时创建的
  • 能赋值给变量或作为数据结构中的元素
  • 能作为参数传递
  • 能作为返回值返回

1、高阶函数

高阶函数至少要符合一些特点中的一个:

  • 接收一个或多个函数作为参数
  • 将函数作为返回值返回
# 高阶函数
# 接收函数作为参数,或者将函数作为返回值的函数时高阶函数

# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]

# 定义一个函数判断数字是否大于 5
def fn2(num):
	if num > 5:
		return True
	return False

# 定义一个函数,可以将指定列表中所有的偶数,保存到一个新的列表中返回
def fn(func,lst):
	'''
		fn() 函数可以将指定列表中所有偶数取出来,并保存到一个新列表中返回
		参数:
			lst 要进行筛选的列表
	'''
	# 创建一个新列表
	new_list = []
	# 对列表进行筛选
	for n in lst:
		if func(n):
			new_list.append(n)
	return new_list

result = fn(fn2,l)
print(result)   # [6, 7, 8, 9, 10]

filter() 函数

  • filter() 是 python 的内置函数
  • filter() 可以从序列中过滤出符合条件的元素,保存到一个新的序列中
    • 参数:
      • 1.函数,根据该函数来过滤序列(可迭代的结构)
      • 2.需要过滤的序列(可迭代的结构)
    • 返回值
      • 过滤后的新序列
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]
# 定义一个函数判断数字是否大于 5
def fn2(num):
	if num > 5:
		return True
	return False
result = filter(fn2,l)  # 返回的是一个新序列
print(list(result))  # [6, 7, 8, 9, 10]

2、匿名函数

匿名函数又可以称作是 lambda 函数表达式,lambda 函数表达式专门用来创建一些简单的函数,它是函数创建的又一种方式

语法:

lambda 参数列表 : 返回值

注:

匿名函数一般作为参数使用,其他地方一般不会使用

def fn5(a,b):
	return a + b

# 这种方法定义函数是匿名的
lambda a,b : a + b
print(lambda a,b : a + b)  # <function <lambda> at 0x107b5e1e0>  发现它确实是一个函数

# 调用
result = (lambda a,b : a + b)(10,20)
print(result)  # 30

# 也可以将匿名函数赋值给一个变量,一般不会这么做(因为这么做你就不如使用 def 去定义一个函数)
fn6 = lambda a,b : a + b
print(fn6(10,11))   # 21

l = [1,2,3,4,5,6,7,8,9,10]
result = filter(lambda num : num > 5,l)
print(list(result))   # [6, 7, 8, 9, 10]

# 使用 lambda 函数表达式只能写一些简单的表达式,一些复杂的写不了

map() 函数

map() 函数可以对可迭代对象中的所有元素做指定操作,然后将其添加到一个新的对象中返回

l = [1,2,3,4,5,6,7,8,9,10]
result = map(lambda i : i + 1,l)     # 对列表中的每一个元素进行加1 ,然后返回新的列表
print(list(result))   # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

sorted() 函数

sorted()函数和 sort() 的用法基本一致,但是 sorted() 可以对任意的序列进行排序,并且使用 sorted() 排序不会影响原来的对象,而是返回一个新对象

# sort() 
# 该方法用来对列表中的元素进行排序
# sort() 方法默认是比较列表中元素的大小

l = ['aa','ccccc','bb','f','eee']
l.sort()
print(l)   # ['aa', 'bb', 'ccccc', 'eee', 'f']

# 在 sort() 中可以接收一个关键字参数 key
# key 需要一个函数作为参数,当设置了函数作为参数,每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小
l.sort(key = len)   # 这里也是高阶函数,把函数作为参数传递进去
print(l)   # ['f', 'aa', 'bb', 'eee', 'ccccc']

l = ['2',3,'1',5]
l.sort(key=int)  # 你如果直接使用 sort() 排序会报错,因为列表中有数字也有字符串,没办法比较大小,所以传一个函数作为参数,把他们都转成 int
print(l)   # ['1', '2', 3, 5]

# sorted() 
# 这个函数和 sort() 的用法基本一致,但是 sorted() 可以对任意的序列进行排序,并且使用 sorted() 排序不会影响原来的对象,而是返回一个新对象
l = ['2',3,'1',5]
result = sorted(l,key=int)
print(result)   # ['1', '2', 3, 5]

3、闭包

形成闭包的条件:

1、要有函数嵌套

2、将内部函数作为返回值返回

3、内部函数必须使用到外部函数的变量

# 将函数作为返回值返回也是一种高阶函数
# 这种高阶函数我们称为闭包,通过闭包可以创建一些只有当前函数能访问的变量
# 可以将一些私有数据藏到闭包中

def fn():
	a = 88     # 这个变量和下面的函数形成闭包
	# 在函数内部再定义一个函数
	def inner():
		print('我是 inner 函数',a)
	# 将内部函数 inner 作为返回值返回
	return inner

# result 是一个函数,是调用 fn 后返回的函数,这个函数是在 fn 内部定义的,并不是全局函数,所以这个函数总能访问到 fn() 函数内的变量
result = fn()

# 求多个数的平均值
# nums = [2,3,4]
# avg = sum(nums)/len(nums)
# print(avg)   # 3.0

def make_averager():
	# 创建一个列表,用来计算平均值
	nums = []
	def average(n):
		nums.append(n)
		# 求平均值
		return sum(nums)/len(nums)
	return average

aver = make_averager()
print(aver(10))  # 10.0
print(aver(2))   # 6.0
# 通过创建一个闭包,nums 这个列表,只有 average 这个函数可以看到,别的地方都访问不到,可以防止别的地方乱修改这个列表

4、装饰器

1)、装饰器引入

# 创建几个函数

def add(a, b):
	'''
		求两个函数的和
	'''
	return a + b

def mul(a, b):
	'''
		求两个函数的乘积
	'''
	return a * b

result = mul(10,20)
print(result)

# 假如这时候我希望函数可以在计算前打印 "开始计算" ,计算结束后,打印 "计算结束"
# 我们可以直接修改函数中的代码来完成这个需求,但是会产生以下几个问题:
# ① 如果要修改的函数过多,修改起来会比较麻烦
# ② 并且不方便后期维护
# ③ 并且这样做会违反开闭原则(程序的设计,要求开放对程序的扩展,要关闭对程序的修改)

# 我们希望在不修改函数的情况下,对函数进行扩展
def fn():
	print('我是 fn 函数')

def new_fn():
	print('函数开始执行')
	fn()
	print('函数执行结束')
new_fn()


def new_add(a, b):
	print('函数开始执行')
	r = add(a,b)
	print('函数执行结束')
	return r

r = new_add(10,20)
print(r)

上面的方式,已经可以在不修改源代码的情况下对函数进行扩展了。但是这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦,为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮我们生产函数。

2)、装饰器的使用

def begin_end(old):
	'''
		用来对其他函数进行扩展,使其他函数可以在执行前,打印开始执行,执行后,打印执行结束
		参数:
			old :要扩展的函数对象
	'''
	# 创建一个新函数
	def new_function(*a,**b):
		print('函数开始执行')
		result = old(*a,**b)
		print('函数执行完成')
		return result
	return new_function

f = begin_end(add)
r = f(2,3)
print(r)

# 向 begin_end() 这种函数我们就称它为装饰器,通过装饰器,可以在不修改原来函数的情况下对函数进行扩展
# 在开发中,我们通过装饰器来扩展函数的功能。
# 其实装饰器很简单,就是你传一个旧函数作为参数,它给你返回一个新函数,这个新函数已经对我们的函数进行升级扩展了,你以后再调用就可以去调用这个新函数,功能和旧函数时一样的
# 在定义函数时,可以通过 @装饰器,来使用指定的装饰器,来装饰当前的函数
# 可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰

@begin_end     # 这就表示我用 begin_end 这个装饰器装饰 say_hello 这个函数
def say_hello():
	print('大家好')

say_hello()   # 这个 say_hello 就是不是之前的 say_hello 了,而是装饰过后的 say_hello

装饰器:

  • 在开发中,我们通过装饰器来扩展函数的功能
  • 其实装饰器很简单,就是你传一个旧函数作为参数,它给你返回一个新函数,这个新函数已经对我们的函数进行升级扩展了,你以后再调用就可以去调用这个新函数,功能和旧函数时一样的
  • 在定义函数时,可以通过 @装饰器,来使用指定的装饰器,来装饰当前的函数
  • 可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰

十二、面向对象

1、面向对象介绍

1)、什么是对象:

  • 对象是内存中专门用来存储数据的一块区域
  • 对象中可以存放各种数据(比如:数字、布尔值、代码)
  • 对象由三部分组成
    • 对象的标识(id)
    • 对象的类型(type)
    • 对象的值(value)

2)、面向对象

  • Python 是一门面向对象的编程语言
  • 所谓的面向对象的语言,简单理解就是语言中的所有操作都是通过对象来进行的
  • 面向过程的编程语言
    • 面向过程指的是我们将程序的逻辑分解为一个一个的步骤,通过对每个步骤的抽象,来完成程序
    • 面向过程的编程思想是将一个功能分解为一个一个小的步骤,我们通过完成一个一个小的步骤来完成程序
    • 这种编程方式符合我们人类的思维,编写起来相对简单。但是这种编程方式编写的代码往往只适用于一个功能,如果要实现别的功能,即使功能相差极小,也往往要重新编写代码,所以它可复用性较低,并且难以维护
  • 面向对象的编程语言
    • 面向对象的编程语言,关注的是对象,而不关注过程
    • 对于面向对象的语言来说,一切皆对象
    • 面向对象的编程思想,将所有的功能统一保存到对应的对象中。这种方式编写的代码,比较容易阅读,并且易于维护,容易维护。

2、类的简介

  • 我们目前所学习的对象都是 Python 的内置对象

  • 但是内置对象并不能满足所有的需求,所以我们在开发中经常需要自定义一些对象

  • 类,简单理解它就相当于一张图纸。在程序中我们需要根据类来创建对象

  • 我们也称对象是类的实例(instance)。如果多个对象是通过一个类创建的,我们称这些对象是一类对象。

  • 像 int() 、float() 、 str() 、bool() 、dict() 这些都是类

  • a = int(10) 创建一个 int 类的实例 等价于 a = 10

  • ==我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)==(就是首字母大写,每个单词首字母大写,剩下全部小写)

  • 类也是一个对象

  • 类就是一个用来创建对象的对象

  • 类是 type 类型的对象,定义类实际上就是定义一个 type 类型的对象

    • class MyClass():
      	pass
      print(id(MyClass),type(MyClass))   # 140394686907736 <class 'type'>

创建类:

使用 class 关键字来定义类,语法和函数很像

class 类名([父类]):

代码块

注:

使用 ==isinstance()== 用来检查一个对象是否是一个类的实例

a = int(10)  # 创建一个 int 类的实例
b = str('heping')  # 创建一个 str 类的实例
print(a,type(a))  # 10 <class 'int'>
print(b,type(b))   # heping <class 'str'>

# 定义一个简单的类
# 使用 class 关键字来定义类,语法和函数很像
# class 类名([父类]):
#	代码块

class MyClass():
	pass
print(MyClass)   # <class '__main__.MyClass'>

# 使用 MyClass 创建一个对象
# 使用类来创建对象,就像调用一个函数一样
mc = MyClass()    # mc 就是通过 MyClass 创建的对象,mc 是 MyClass 的实例
mc2 = MyClass()   # mc2 也是 MyClass 的实例

# isinstance() 用来检查一个对象是否是一个类的实例
flag = isinstance(mc,MyClass)
print(flag)  # True

3、对象的创建流程

对象的创建流程

首先我创建一个类,这个类它也是一个对象,在 Python 中一切皆对象。所以在内存中会开辟一块空间去存储这个对象,那么这块空间肯定会有 id type value 这三个属性,只不过你创建类,它的 type 有点特殊,是 type 类型。然后我创建一个类的实例,也会在内存中开辟一块空间,只不过 type 是类名。我往这个类的实例中添加属性,内存中的 value 值就是你添加的属性和值。

总结一下对象的创建流程:

1、创建一个变量 mc

2、在内存中创建一个新对象

3、将对象的 id 赋值给变量

4、类的定义

类和对象都是对现实生活中的事务或程序中的内容的抽象。

所有的事物都由两部分构成:

1、数据(属性)

2、行为(方法)

在类的代码块中,我们可以定义变量和函数:

  • 变量会成为该类实例的公共属性,所有的该类实例都可以通过,「对象.属性名」 的形式访问
  • 函数会成为该类实例的公共方法,所有该类实例都可以通过,「对象.方法名()」 的形式调用方法

注意:

==调用方法时,第一个参数由解析器自动传递,所以定义方法时,至少要定义一个参数==

说一下 python 中方法和函数的区别:

1、在类中定义一个函数,我们一般把这个函数叫做方法

2、方法的调用是通过 对象.方法名()来调用的,函数的调用直接通过函数名即可

3、如果是函数调用,则调用时传几个参数,就会有几个实参。==如果是方法调用,解析器会默认传递一个参数,所以方法中至少要定义一个形参==

# 尝试定义一个表示人的类
class Person :     # Person 后面的括号,如果有继承的话需要写,没有继承的话,可写可不写
	#在类的代码块中,我们可以定义变量和函数
	# 在类中我们定义的变量,将会称为所有的实例的公共属性
	# 所有类的实例都可以访问这些变量
	name = 'Jimmy'

	# 在类中也可以定义函数,类中定义的函数,我们称为方法
	# 这些方法可以通过该类的所有实例来访问
	def say_hello(a):
		print('hello')

p1 = Person()
print(p1.name)

# 调用方法:对象.方法名()
# 方法调用和函数调用的区别:
# 如果是函数调用,则调用时传几个参数,就会有几个实参
# 如果是方法调用,默认传递一个参数,所以方法中至少要定义一个形参
p1.say_hello()   # 假如类中的方法一个参数都没定义,你在这里这样调用会报错的,因为解析器会默认传一个参数,所以要求方法至少定义一个形参

5、属性和方法

说一下属性和方法的查找流程:

当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性,如果有则直接返回当前对象的属性值。如果没有,则去当前对象的类对象中寻找,如果有则返回类对象的属性值。如果没有则报错。

类的定义内存结构图

如上图,如果现在我要打印 p1 的 name 属性,那么会先在 p1 当前对象中找,看有没有 name 属性,如果有的话,直接返回。如果没有的话会去 p1 的类对象中找看有没有 name 属性,如果有的话,返回该属性值。没有就报错。很显然这里 p1 对象中有 name 属性,所以会返回 kelly

class Person():
	name = 'shp'
	def say_hello(self):
		# 方法每次被调用时,解析器都会自动传递第一个实参
		# 第一个参数,就是调用方法的对象本身
		# 如果 say_hello 方法是 p1 调用的,那么第一个参数就是 p1 对象,如果 say_hello 方法是 p2 调用的,那么第一个参数就是 p2 对象
		# 一般我们都会将这个参数命名为 self
		# 注意:在方法中不能直接使用类的属性(看不见)
		print('hello,%s'%self.name)

p1 = Person()
p2 = Person()
p1.name = '林志颖'
p2.name = '沈和平'
p2.say_hello()

注意:

1、类中的方法每次被调用,解析器都会自动传递一个实参,这个实参就是调用方法的对象本身

2、在方法中不能直接使用类的属性(在方法中是看不见类的属性的)

6、类的特殊方法 init 方法

在类中可以定义一些特殊方法(魔术方法)

  • 特殊方法都以 __ 开头,__ 结尾的方法 (双下划线)
  • 特殊方法不需要我们自己调用,不要尝试去调用特殊方法
  • 特殊方法将会在特殊的时刻自动调用

学习特殊方法:

1、特殊方法什么时候调用

2、特殊方法有什么作用

对象的创建流程:

1.创建一个变量

2.在内存中创建一个新对象

3.__init__(self) 方法执行

4.将对象的 id 赋值给变量

说一下 init 方法:

  • init 方法会在对象创建以后立刻执行
  • init 可以用来向新创建的对象中初始化属性
class Person :

	print('hello Person')
	#在类中可以定义一些特殊方法(魔术方法)
	# 特殊方法都以 __ 开头,__ 结尾的方法  (双下划线)
	# 特殊方法不需要我们自己调用,不要尝试去调用特殊方法
	# 特殊方法将会在特殊的时刻自动调用

	# init 方法会在对象创建以后立刻执行
	# init 可以用来向新创建的对象中初始化属性

	# 调用类创建对象时,类后边的所有参数都会依次传递到 init 方法中
	def __init__(self,name):
		# 通过 self 向新建的对象中初始化属性
		self.name = name

	def say_hello(self):
		print('hello,%s'%self.name)


# 目前来说,对于 Person 类来说,name 属性是必须的,并且每一个对象中 name 属性基本上都是不同的
# 而我们现在是将 name 属性,在定义好对象以后,手动添加到对象中,这种方式很容易出现错误(比如说你创建对象,然后没有添加 name 属性,你再去调用 say_hello 就会报错)
# 我们希望在创建对象时,必须设置 name 属性,如果不设置对象将无法创建。
# 并且属性的创建应该是自动完成的,而不是在创建对象以后手动完成 
p1 = Person('Jimmy')
print(p1.name)  # Jimmy

下面重点解释一下这个方法:

def __init__(self,name):
		# 通过 self 向新建的对象中初始化属性
		self.name = name
        
# 我们知道 self 表示的是调用方法的对象本身,所以这里的 self.name 表示的就是给调用这个方法的对象设置 name 属性,self.name=name   后面这个 name 表示的就是传进来的参数值。而 __init__ 方法又是在对象创建后自动调用,所以上面整段代码的意思就是,你创建对象的同时,传递一个 name 参数,我就把这个参数设置成你 对象的 name 属性

# 注意:这和在类中定义一个 name 属性,有本质区别。类中定义一个 name 属性,这个 name 是公共的,所有这个类的实例对象都能访问,但是在 __init__ 方法中通过 self.name = name 设置,只会给实例对象设置 name 属性。

类的基本结构:

class 类名([父类]):
    公共的属性....
    # 对象的初始化方法
    def __init__(self,....)
    	.....
    # 其他的方法
    def method_1(self,....)
    	.....
    def method_2(self,....)
    	..... 

7、练习

class Dog:
	'''
		表示狗的类
	'''
	def __init__(self,name,age,gender,height):
		self.name = name
		self.age = age
		self.gender = gender
		self.height = height
	def baking(self):
		'''
			狗叫的方法
		'''
		print('小狗汪汪叫~')

	def run(self):
		'''
			狗奔跑的方法
		'''
		print('%s,快乐的奔跑着。。'%self.name)


d1 = Dog('wc',4,'man',5)
# 目前我们可以通过 对象.属性 的方式来修改属性的值,这种方式导致对象中的属性可以随意修改
# 值可以随意修改,不论对错,非常的不安全
# 现在我们就需要一种方式来增强数据的安全性
#	1.属性不能随意修改(我让你改才能改,不让你改你就不能改)
#	2.属性不能修改为任意的值(比如说:年龄不能修改为负数)
d1.name = '旺财'
d1.run()

8、封装简介

封装是面向对象三大特征之一。封装指的是隐藏对象中一些不希望被外部所访问到的属性或方法

如何隐藏一个对象的属性?

答:将对象的属性名,修改为一个外部不知道的名字

如何获取(修改)对象中的属性?

  • 需要提供一个 getter 和 setter 方法使外部可以访问到属性
  • getter 获取对象中指定属性(get_属性名)
  • setter 用来设置对象的属性(set_属性名)

使用封装确实增加了类的定义的复杂度,但是它也确保了数据的安全性

  1. 隐藏了属性名,调用者无法随意修改对象中的属性
  2. 增加了 getter 和 setter 方法很好的控制了属性是否是只读的
    1. 如果希望属性是只读的,则可以直接去掉 setter 方法
    2. 如果希望属性不能被外部访问,则可以直接去掉 getter 方法
  3. 使用 setter 方法设置属性,可以增加数据的验证,确保数据的值是正确的
  4. 使用 getter 方法获取属性,使用 setter 方法设置属性,可以在读取和修改属性的同时做一些其他处理
class Dog:
	'''
		表示狗的类
	'''
	def __init__(self,name):
		self.hidden_name = name
	def say_hello(self):
		print('你好 %s'%self.hidden_name)

	def get_name(self):
		'''
			获取对象的 name 属性
		'''
		return self.hidden_name
	def set_name(self,name):
		'''
			用来设置对象的 name 属性
		'''
		self.hidden_name = name

d1 = Dog('旺财')
print(d1.get_name())    # 旺财
d1.set_name('阿黄')
print(d1.get_name())    # 阿黄

9、隐藏类中的属性

  • 可以为对象的属性使用双下划线开头,__XXX
  • 双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问
  • ==其实隐藏属性只不过是 Python 自动为属性改了一个名字。==实际上是将名字修改为了 「_类名__属性名」 比如: __name ---> _Person__name
class Person:
	def __init__(self,name):
		self.__name = name

	def get_name(self):
		return self.__name

	def set_name(self,name):
		self.__name = name

p1 = Person('Jimmy')
print(p1.get_name())
#print(p1.__name)    双下划线开头的属性是隐藏属性,无法通过对象访问
print(p1._Person__name)   #Jimmy,实际上 Python 把名字改成了 _类名__属性名

注意:

使用双下划线开头的属性,实际上依然可以在外部访问,所以这种方式我们一般不用

说明:

  • 一般我们会将一些私有属性(不希望被外部访问的属性)以 "_" 开头
  • ==一般情况下,使用 "_" 开头的属性都是私有属性,没有特殊需要不要修改私有属性==
class Person:
	def __init__(self,name):
		self._name = name

	def get_name(self):
		return self._name

	def set_name(self,name):
		self._name = name

p1 = Person('Jimmy')
print(p1.get_name())   # Jimmy
print(p1._name)   # Jimmy,_name 在外部是可以访问的,但是用 "_" 开头表示私有属性,没有特殊需要不要修改私有属性

10、property 装饰器

  • property 装饰器,用来将一个 get 方法,转换为对象的属性
  • 添加 property 装饰器以后,我们可以像调用属性一样使用 get 方法
  • 使用 property 装饰的方法名,必须和属性名是一样的
  • setter 方法的装饰器:@属性名.setter
class Person:
	def __init__(self,name):
		self._name = name

	# property 装饰器,用来将一个 get 方法,转换为对象的属性
	# 添加 property 装饰器以后,我们可以像调用属性一样使用 get 方法
	# 使用 property 装饰的方法名,必须和属性名是一样的
	@property
	def name(self):
		print('get 方法执行了。。')
		return self._name

	# setter 方法的装饰器:@属性名.setter 
	@name.setter
	def name(self,name):
		self._name = name

p1 = Person('Jimmy')
print(p1.name)   # 看似我是在调用一个属性,其实我调用的是 get_name 方法
p1.name = 'kelly'   # 看似我是在通过属性设置值,其实是在调用 set 方法设置值
print(p1.name) 

# 其实这个 @property 的作用就是让我们用操作属性的方式去操作方法 

11、继承简介

  • 继承也是面向对象的三大特性之一
  • 通过继承我们可以使一个类获取到其他类中的属性和方法
  • ==定义类时,可以在类名后的括号中指定当前类的父类==
  • 子类可以直接继承父类中的所有属性和方法
  • 通过继承可以直接让子类获取到父类的方法或属性,避免编写重复性的代码,并且也符合 ocp 原则,所以我们经常需要通过继承来对一个类进行扩展

注:

1、在创建类时,如果省略了父类,则默认父类是 object

2、object 是所有类的父类,所有类都继承自 objcet

# 继承

# 定义一个类 Animal(动物),这个类需要两个方法:run() 和 sleep()

class Animal:
	def run(self):
		print('动物会跑')

	def sleep(self):
		print('动物会睡觉')

# 现在我们定义类 Dog,要求这个类中要有三个方法:run()  sleep() 和 bark()

# 假如现在我们有一个类,可以实现上述我需要的大部分功能,但是不能实现全部功能
# 如何能让这个类实现全部功能呢?
# 	① 直接修改这个类,在这个类中添加我们需要的功能
#		但是这样做,修改起来比较麻烦,并且违反了 oop 原则
#	② 直接创建一个新的类
#		这样做也比较麻烦,并且需要大量的复制粘贴,会出现大量的重复性代码
#	③ 直接从 Animal 类中继承它的属性和方法
#		继承也是面向对象的三大特性之一
#		通过继承我们可以使一个类获取到其他类中的属性和方法
#		定义类时,可以在类名后的括号中指定当前类的父类
#		子类可以直接继承父类中的所有属性和方法
# 通过继承可以直接让子类获取到父类的方法或属性,避免编写重复性的代码,并且也符合 ocp 原则,所以我们经常需要通过继承来对一个类进行扩展
class Dog(Animal):
	def bark(self):
		print('狗汪汪叫。。')

d = Dog()
d.run()     # 动物会跑
result = isinstance(d,Animal)
print(result)   # True

# 在创建类时,如果省略了父类,则默认父类是 object
# object 是所有类的父类,所有类都继承自 objcet
class Person:
	pass

说两个函数:

  • issubclass
    • 这个函数用来检查一个类是否是另一个类的子类
  • isinstance
    • 这个函数用来检查一个类是否是另一个类的子类
    • 如果这个类是这个对象的父类,也会返回 True
    • 所有的对象都是 object 的实例
# issubclass 的作用是检查一个类是否是另一个类的子类
print(issubclass(Dog,Animal))  # True   检查 Dog 是不是 Animal 的子类
print(issubclass(Dog,object))  # True   检查 Dog 是不是 object 的子类

# isinstance() 用来检查一个对象是否是一个类的实例
# 如果这个类是这个对象的父类,也会返回 True
# 所有的对象都是 object 的实例
print(isinstance(d,object))    # True

12、重写

如果子类中有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们叫做方法的重写(override)

当我们调用一个对象的方法时

  • 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
  • 如果没有,则去当前对象的父类中寻找,如果父类中有,则直接调用父类中的方法
  • 如果没有,则去父类的父类中寻找,以此类推,直到找到 object ,如果依然没有找到,则报错
class A:
	pass

class B(A):
	def test(self):
		print('test...')
class C(B):
	pass

c = C()
c.test()  # test...

13、super

super 可以用来获取当前类的父类,并且通过 super 返回的对象调用父类的方法时,不需要传递 self

注:

==父类中的所有方法都会被子类继承,包括特殊方法==

class Animal:
	def __init__(self,name):
		self._name = name
	def run(self):
		print('动物在跑')
	def sleep(self):
		print('动物在睡觉')

	@property
	def name(self):
		return self._name
	
	@name.setter
	def name(self,name):
		self._name = name

# 父类中的所有方法都会被子类继承,包括特殊方法,因此我们也可以重写特殊方法
class Dog(Animal):

	def __init__(self,name,age):
		# 希望可以直接调用父类的 init 方法来初始化父类中定义的属性
		# 方法1:
		Animal.__init__(self,name)   # 这种方法过于耦合,并且需要把 self 传递过去,假如 Dog 的父类变了,就有问题了
		# 方法2:
		# super 可以用来获取当前类的父类,并且通过 super 返回的对象调用父类的方法时,不需要传递 self
		super().__init__(name)
		self._age = age

	@property
	def age(self):
		return self._age
	
	@age.setter
	def age(self,name):
		self.age = age

	def run(self):
		print('汪汪汪...')

#d = Dog()     # 如果你不重写 init 方法,这行代码是会报错的,因为 Animal 中的 init 方法也会被 Dog 继承,你在创建 Dog 实例的时候没有传参数,所以报错
d = Dog('旺财',10)
print(d.age)   # 这里看似我调用的属性,其实调用的是 get 方法(用了 @property 修饰,前面说过)
print(d.name)

14、多重继承

  • 在 Python 中是支持多重继承的,也就是我们可以为一个类同时指定多个父类
  • 可以在类名的() 中添加多个类,来实现多重继承
  • 多重继承会使子类同时拥有多个父类,并且会获取到所有父类中的方法
  • 在开发中没有特殊情况,应该避免使用多重继承,因为多重继承会让我们的代码过于复杂
  • 如果多个父类中有同名的方法,则会在第一个父类中寻找,然后找第二个,第三个,前面父类的方法会覆盖后面父类的方法

注:

类名.__bases__ 这个属性可以用来获取当前类的所有父类,返回的是一个元组

class A(object):
	def testA(self):
		print('AA 中的方法')

class B(object):
	def testA(self):
		print('B 中的方法')
	def testB(self):
		print('BB 中的方法')

# 在 Python 中是支持多重继承的,也就是我们可以为一个类同时指定多个父类
# 可以在类名的() 中添加多个类,来实现多重继承
# 多重继承会使子类同时拥有多个父类,并且会获取到所有父类中的方法
# 在开发中没有特殊情况,应该避免使用多重继承,因为多重继承会让我们的代码过于复杂
# 如果多个父类中有同名的方法,则会在第一个父类中寻找,然后找第二个,第三个,前面父类的方法会覆盖后面父类的方法
class C(B,A):
	pass

# 类名.__bases__ 这个属性可以用来获取当前类的所有父类
print(A.__bases__)   # (<class 'object'>,)  返回的是一个元组
print(C.__bases__)   # (<class '__main__.B'>, <class '__main__.A'>)

c = C()
c.testA()  # 如果多个父类中有同名的方法,会先在第一个父类中寻找,第一个父类中的方法覆盖后面的父类

解释一下多重继承的复杂情况:

继承关系

现在 A 和 B 继承 object 类,C 继承 A 和 B ,D 继承 A 和 B 和 C。

现在我想调用 D 中的 test 方法,那么它是怎么找的?

流程就是先看 D 自己中有没有 test 方法,如果有就 ok 了,调用自己的 test 方法,如果没有去它的父类中寻找,假设 D 继承的顺序是 A B C,那么就会先去 A 中找看有没有 test 方法,如果有就调用,如果没有就去 A 的父类中寻找 test 方法,如果有,调用,如果没有,这时候去 B 中找 test 方法,如果 B 中有,则调用,如果 B 中没有,则去 B 的父类中查找,因为 B 的父类是 object ,刚找 A 的时候找过了,这时候它就不会再去找 objcet 了,那么这时候就会去 C 中找看有没有 test 方法,如果有的话调用,如果没有的话,去 C 的父类中查找,C 的父类 A 和 B 都找过了,所以不会再去 A 和 B 中找。要是整个过程都没找到 ,肯定报错。

总结:

没有特殊情况尽量不要使用多重继承。

15、多态

  • 多态是面向对象三大特征之一
  • 多态从字面上理解是多种形态
  • 一个对象可以以不同的形态去呈现
# 定义两个类
class A :
	def __init__(self,name):
		self._name = name

	@property
	def name(self):
		return self._name

	@name.setter
	def name(self,name):
		self._name = name

class B :
	def __init__(self,name):
		self._name = name

	@property
	def name(self):
		return self._name
		
	@name.setter
	def name(self,name):
		self._name = name

class C :
	pass


a = A('孙悟空')
b = B('猪八戒')
c = C()
c.name = '沙和尚'

# 对于 say_hello 这个函数来说,只要对象中含有 name 属性,它就可以作为参数传递
# 这个函数并不会考虑对象的类型,只要有 name 属性即可
def say_hello(obj):
	print('你好:%s'%obj.name)

say_hello(c)     # 你好:沙和尚

# 在 say_hello2 函数中我们做了一个类型检查,也就是 obj 是 A 类型的对象时,才可以使用
# 其他类型的对象都无法使用该函数,这个函数就违反了多态
# 违反了多态的函数,只适用于一种类型的对象,无法处理其他类型的对象,这样导致函数的适应性非常差
# 注意,像 isinstance() 这种函数,在开发中一般不会使用
def say_hello2(obj):
	if isinstance(obj,A) :
		print('你好:%s'%obj.name)


# 鸭子类型
# 如果一个东西,走路像鸭子,叫声像鸭子,那么它就是鸭子

#len()
# 之所以一个对象能通过 len() 来获取长度,是因为对象中具有一个特殊方法 __len__
# 只要对象中具有 __len__ 方法,就可以通过 len() 来获取它的长度
l = [1,2,3]
print(len(l))
s = 'hello'
print(len(s))

注:

  • 所以一个对象能通过 len() 来获取长度,是因为对象中具有一个特殊方法 __len__
  • 只要对象中具有 __len__ 方法,就可以通过 len() 来获取它的长度

16、类中的属性和方法

  • 类属性
    • 直接在类中定义的属性是类属性
    • 类属性可以通过类或类的实例访问到
    • 但是类属性只能通过类对象来修改,无法通过实例对象修改
  • 实例属性
    • 通过实例对象去添加的属性属于实例属性
    • 实例属性只能通过实例对象来访问和修改,类对象无法访问和修改
  • 类方法
    • 在类内部使用 @classmethod 来修饰的方法属于类方法
    • 类方法的第一个参数是 cls,也会被自动传递,cls 就是当前的类对象
    • 类方法和实例方法的区别:实例方法的第一个参数是 self ,而类方法的第一个参数是 cls
    • 类方法可以通过类去调用,也可以通过实例调用,没有区别
  • 实例方法
    • 在类中定义,以 self 为第一个参数的方法都是实例方法
    • 实例方法在调用时,python 会将调用对象作为 self 传入
    • 实例方法可以通过实例和类去调用
    • 当通过实例调用时,会自动将当前调用对象作为 self 传入
    • 当通过类调用时,不会自动传递 self ,需要手动传递 self
  • 静态方法
    • 在类中使用 @staticmethod 来修饰的方法属于静态方法
    • 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用
    • 静态方法基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数
    • 静态方法一般都是一些工具方法,和当前类无关
# 定义一个类
class A(object):

	# 类属性,直接在类中定义的属性是类属性
	# 类属性可以通过类或类的实例访问到
	# 但是类属性只能通过类对象来修改,无法通过实例对象修改
	count = 10
	def __init__(self):
		# _name 也是实例属性,因为我们说 self 表示当前对象,当前对象的 _name 属性肯定也是实例属性
		self._name = 'swk'

	# 实例方法,在类中定义,以 self 为第一个参数的方法都是实例方法
	# 实例方法在调用时,python 会将调用对象作为 self 传入
	# 实例方法可以通过实例和类去调用
	#    当通过实例调用时,会自动将当前调用对象作为 self 传入
	#    当通过类调用时,不会自动传递 self ,需要手动传递 self
	def test(self):
		print('这是一个实例方法')

	# 类方法,在类内部使用 @classmethod 来修饰的方法属于类方法
	# 类方法的第一个参数是 cls,也会被自动传递,cls 就是当前的类对象
	#    类方法和实例方法的区别:实例方法的第一个参数是 self ,而类方法的第一个参数是 cls
	#    类方法可以通过类去调用,也可以通过实例调用,没有区别
	@classmethod
	def test_2(cls):
		print('这是一个类方法')


	# 静态方法,在类中使用 @staticmethod 来修饰的方法属于静态方法
	# 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例去调用
	# 静态方法基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数
	# 静态方法一般都是一些工具方法,和当前类无关
	@staticmethod
	def test_3(self):
		print('这是一个静态方法')

a = A()
print(a.count) # 10 类属性可以通过类或类的实例访问到
print(A.count) # 10
a.count = 20   #  这行代码意思是向 a 实例中添加 count 这个属性,这个属性会覆盖类对象的属性
print(a.count) # 20  这里只是改了类实例 a 中 count 属性值,类对象的属性并没有被修改
print(A.count) # 10 类对象的 count 属性还是 10

print('---------')
# 实例属性,通过实例对象去添加的属性属于实例属性
# 实例属性只能通过实例对象来访问和修改,类对象无法访问和修改
a.age = 18
print(a.test())   # 实例对象可以直接调用
print(A.test(a))   # 当通过类去调用的时候,需要手动传递一个 self

A.test_2()   # 通过类对象或者实例对象去调用类方法都是可以的

17、垃圾回收

  • 在程序中没有被引用的对象就是垃圾,这种垃圾对象过多以后会影响到程序的运行性能
  • 在 Python 中有自动的垃圾回收机制,它会自动将这些没有引用的对象删除,所以我们不用手动处理垃圾

注:

__del__ 是一个特殊方法,它会在对象被垃圾回收前调用

# 就像我们生活中会产生垃圾一样,程序在运行过程中也会产生垃圾
# 程序运行过程中产生的垃圾会影响程序的运行性能,所以这些垃圾必须被及时清理
# 在程序中没有被引用的对象就是垃圾,这种垃圾对象过多以后会影响到程序的运行性能
# 所以我们必须进行及时的垃圾回收,所谓垃圾回收就是将垃圾对象从内存中删除
# 在 Python 中有自动的垃圾回收机制,它会自动将这些没有引用的对象删除,所以我们不用手动处理垃圾


class A():

	def __init__(self):
		self._name = 'A'


	# del 是一个特殊方法,它会在对象被垃圾回收前调用
	def __del__(self):
		print('A() 对象被删除了')

a = A()   # 这里 a 变量会存一个 A 对象内存地址,会指向这块内存空间,这时候不是垃圾
a = None  # 这时候 a 变量不指向 A 对象的内存空间了(并且也没有其他变量执行 A 对象的内存空间),A 对象就会变成垃圾

18、特殊方法

  • 特殊方法也称为魔术方法
  • 特殊方法都是使用 __ 开头和结尾的(双下划线)
  • 特殊方法一般不需要我们手动调用,会在一些特殊情况下自动调用

1)、__str__()

  • 这个特殊方法会在尝试将对象转换为字符串的时候调用
  • 它的作用可以用来指定对象转换为字符串的结果

2)、__repr__()

  • 这个特殊方法会在对当前对象使用 repr() 函数时调用
  • 它的作用是指定对象在 ‘交互模式’ 中直接输出的效果

3)、__gt__()

  • 会在对象做大于比较的时候调用,该方法的返回值将会作为比较的结果
  • 它需要两个参数,一个 self 表示当前对象,other 表示和当前对象比较的对象

4)、__len__()

这个特殊方法用来获取对象的长度

5)、 __bool__()

这个特殊方法可以用来指定对象转换为 bool 值的情况

# 特殊方法也称为魔术方法
# 特殊方法都是使用 __ 开头和结尾的(双下划线)
# 特殊方法一般不需要我们手动调用,会在一些特殊情况下自动调用

# 定义一个 Person 类
class Person(object):
	"""人类"""
	def __init__(self, name, age):
		self._name = name
		self._age = age

	# __str__() 这个特殊方法会在尝试将对象转换为字符串的时候调用
	# 它的作用可以用来指定对象转换为字符串的结果
	def __str__(self):
		return 'Person[name=%s,age=%d]'%(self._name,self._age)


	# __repr__() 这个特殊方法会在对当前对象使用 repr() 函数时调用
	# 它的作用是指定对象在 ‘交互模式’ 中直接输出的效果
	def __repr__(self):
		return 'hello'

	# object.__lt__(self, other) 小于 <
	# object.__le__(self, other) 小于等于 <=
	# object.__eq__(self, other) 等于 ==
	# object.__ne__(self, other) 不等于 !=
	# object.__gt__(self, other) 大于 >
	# object.__ge__(self, other) 大于等于 >=

	# __gt__() 会在对象做大于比较的时候调用,该方法的返回值将会作为比较的结果
	# 它需要两个参数,一个 self 表示当前对象,other 表示和当前对象比较的对象
	def __gt__(self,other):
		return self._age > other._age

	# __len__() 这个特殊方法用来获取对象的长度

	# __bool__() 这个特殊方法可以用来指定对象转换为 bool 值的情况
	def __bool__(self):
		return self._age > 18


# 创建两个 Person 的实例
p1 = Person('Jimmy',44)
p2 = Person('kimi',11)
# 当我们打印一个对象时,实际上打印的是对象的特殊方法 __str__() 的返回值
print(p1)  # <__main__.Person object at 0x10945add8>

print(repr(p1))  # hello 这里我对 p1 使用 repr 函数,会调用 __repr__() 方法

print(p2 > p1)   # False  这时候 p2 就表示 self  p1 就表示 other

print(bool(p1))  # True   会调用 __bool__() 这个特殊方法

十三、模块化

1、模块简介

模块(moudle)

  • 模块化,指将一个完整的程序分解为一个一个小的模块。通过将模块组合,来搭建出一个完整的程序
  • 不采用模块化,就是统一将所有的代码编写到一个文件中
  • 采用模块化,将程序分别编写到多个文件中
  • 模块化的优点:
    • ① 方便开发
    • ② 方便维护
    • ③ 模块可以复用
  • 在 Python 中一个 py 文件就是一个模块,要想创建模块,实际上就是创建一个 Python 文件
  • 注意:模块名要符合标识符的规范

2、模块的创建

如何在一个模块中引入外部模块?

  • import 模块名 (模块名,就是 Python 文件的名字,注意不要 py)
  • import 模块名 as 模块别名
  • 可以引入同一个模块多次,但是模块的实例只会创建一个
  • import 可以在程序的任意位置调用,但是一般情况下,import 语句都会写在程序的开头
  • 在每一个模块内部都有一个 __name__ 属性,通过这个属性可以获取到模块的名字
  • __name__ 的属性值如果为 __main__ ,==证明这个模块是主模块,一个程序中只会有一个主模块==
  • 主模块就是我们直接通过 Python 执行的模块,你通过 Python 直接执行哪个模块,哪个模块就是主模块
import test_moudle as test
print(test.__name__)   # test_moudle

3、模块的使用

# m.py
# 可以在模块中定义变量,在模块中定义的变量,在引入模块后,就可以直接使用了
a = 10
b = 20

# 在模块中定义一个函数
def test1():
	print('我是模块中的 test1 函数')

# 也可以定义一个类
class Person:
	def __init__(self):
		self._name = 'Jimmy'
# 主模块.py
import m
print(m)  # <module 'm' from '/Users/shenheping/Desktop/pythoncode/lesson6/m.py'>
# 访问模块中的变量,使用:模块名.变量名
print(m.a,m.b)  #10 20

m.test1()  #我是模块中的 test1 函数

p1 = m.Person()
print(p1._name)   # Jimmy

假如我只想引入模块中的部分内容,应该怎么办?

语法:

from 模块名 import 变量1,变量2

为引入的变量使用别名:

from 模块名 import 变量1 as 别名

# 主模块.py
# 引入模块中的部分内容
from m import Person,test1    
# from m import *    引入模块中的所有内容,一般不建议使用,这个其实就是把模块中的内容全部复制一遍到主模块中,如果存在同名的变量或函数,会造成覆盖的情况
p1 = Person()
print(p1._name)  # Jimmy


# -------------------------------------
from m import test1 as t1     # 为引入的变量使用别名
t1()   # 我是模块中的 test1 函数,调用的时候使用别名调用,这样就可以防止重名覆盖的情况

模块中测试代码:

# m.py
a = 10
b = 20

# 在模块中定义一个函数
def test1():
	print('我是模块中的 test1 函数')

# 也可以定义一个类
class Person:
	def __init__(self):
		self._name = 'Jimmy'


# 编写测试代码,这部分代码只有当前文件作为主模块的时候才需要执行
# 而当模块被其它模块引入时,不需要执行,此时我们就需要检查当前模块是否为主模块
if __name__ == '__main__':
	test1()
	p1 = Person()
	print(p1._name)
 # 这样这个 m.py 在被其它模块引用的时候上面的测试代码就不会执行

4、包

  • 包也是一个模块
  • 当我们模块中代码过多时,或者一个模块需要被分解为多个模块时,这时候就需要使用到包
  • 普通的模块就是一个 py 文件,而包是一个文件夹
  • 包中必须要有一个 __init__.py 文件,这个文件中可以包含包中的主要内容

__pycache__ 文件夹

  • __pycache__ 是模块的缓存文件
  • py 代码在执行前,需要先被解析器转换为机器码,然后再执行
  • 所以我们在使用模块(包)时,也需要将模块的代码先转换为机器码然后再交由计算机执行
  • 而为了提高程序运行的性能,python 会在编译过一次后,将代码保存到一个缓存文件中,这样下次在加载这个模块(包)时,就可以不用重新编译而是直接加载缓存中编译好的代码即可

5、Python 标准库

  • python 的一个思想就是开箱即用
  • 为了实现开箱即用的思想,Python 中为我们提供了一个模块的标准库
  • 在这个标准库中,有很多很强大的模块我们可以直接使用,并且标准库会随着 Python 的安装一同安装

1)、sys 模块

它里面提供了一些变量和函数,使我们可以获取到 Python 解析器的信息,或者通过函数来操作 Python 解析器

sys.argv

  • 获取执行代码时,命令行中所包含的参数
  • 该属性值是一个列表,列表中保存了当前命令的所有参数

sys.modules

  • 获取当前程序中引入的所有模块
  • modules 是一个字典,字典的 key 是模块的名字,字典的 value 是模块对象

sys.path

  • 它是一个列表,列表中保存的是模块的搜索路径
  • 举个例子,比如我引入了 pprint 这个模块,那么它是先在当前目录下找,没有的话再去 python37.zip 下找,这样一层一层的往下找

sys.platform

  • 表示当前 python 运行的平台

sys.exit()

  • 这个函数用来退出程序
# 引入 sys 模块
import sys
# sys.argv 
# 获取执行代码时,命令行中所包含的参数
# 该属性值是一个列表,列表中保存了当前命令的所有参数
print(sys.argv)   # ['python标准库.py']
# sys.modules
# 获取当前程序中引入的所有模块
# modules 是一个字典,字典的 key 是模块的名字,字典的 value 是模块对象
print(sys.modules)

# sys.path
# 它是一个列表,列表中保存的是模块的搜索路径
# ['/Users/shenheping/Desktop/pythoncode/lesson6',
# '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
# '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
# '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
# '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
pprint.pprint(sys.path)
# 举个例子,比如我引入了 pprint 这个模块,那么它是先在当前目录下找,没有的话再去 python37.zip 下找,这样一层一层的往下找

# sys.platform
# 表示当前 python 运行的平台
print(sys.platform)   # darwin   这个表示 Mac OS

# sys.exit()
# 这个函数用来退出程序
sys.exit('出现异常,退出程序')   # 程序执行到这里就会退出

2)、pprint 模块

这个模块可以对打印的数据做简单的格式化

pprint()

  • 该方法可以用来对打印的数据做简单的格式化
import pprint
pprint.pprint(sys.path)   # 格式化打印搜索路径

3)、os 模块

os 模块让我们可以对操作系统进行访问

os.environ

  • 通过这个属性可以获取到系统的环境变量

os.system()

  • 这个函数可以用来执行操作系统的命令
# os 模块让我们可以对操作系统进行访问
import os

# os.environ
# 通过这个属性可以获取到系统的环境变量
pprint.pprint(os.environ['PATH'])

# os.system()
# 这个函数可以用来执行操作系统的命令
os.system('ls')   # 执行操作系统中 ls 命令

十四、异常和文件

1、异常简介

1)、异常介绍

  • 程序在运行过程当中,不可避免的会出现一些错误。比如:使用了没有赋值的变量,使用了不存在的索引,除以 0,这些错误在程序中,我们称其为异常。
  • 程序运行过程中,一旦出现异常将会导致程序立即终止,异常以后的代码全部都不会执行

2)、处理异常

程序运行时出现异常,目的并不是让我们的程序直接终止。Python 是希望在出现异常时,我们可以编写代码来对异常进行处理。

处理异常的 try 语句:

try:

代码块(可能出现错误的语句)

except 异常类型 as 异常名:

代码块(出现错误以后的处理方式)

except 异常类型 as 异常名:

代码块(出现错误以后的处理方式)

else:

代码块(没出错时要执行的语句)

finally:

代码块(无论是否出现异常,该代码块都会执行)

上面的语法:

try 是必须要有的,else 语句有没有都行,except 和 finally 至少有一个

说明:

可以将可能出现错误的代码放入 try 语句中,这样如果代码没有错误,则会正常执行。如果出现错误,则会执行 except 子句中的代码,这样我们就可以通过代码来处理异常,避免因为一个异常导致整个程序终止。

print('hello')
try:
	# try 中放置的是有可能出现错误的代码
	print(10/0)
except:
	# except 中放置的是出错以后的处理方式
	print('代码出现异常了..')
else:
	print('程序正常执行')
print('hi')

# 上面的代码,如果 try 语句中的代码有异常,则会执行 except 语句中的代码,如果 try 语句中的代码没有异常,则正常执行 try 语句中的代码,else 中的代码也会执行

2、异常的传播

  • 当函数中出现异常时,如果在函数中对异常进行了处理,则异常不会再继续传播,如果函数中没有对异常进行处理,则异常会继续向函数调用处传播。如果函数调用处处理了异常,则不再传播。如果没有处理则继续向调用处传播,直到传递到全局作用域(主模块)如果依然没有处理,则程序终止,并且显示异常信息
  • 当程序运行过程中出现异常以后,所有的异常信息会被保存在一个专门的异常对象中。而异常传播时,实际上就是异常对象抛给了调用处。
    • 如:ZeroDivisionError 类的对象专门用来表示除 0 的异常。NameError 类的对象专门用来处理变量错误的异常。
def fn():
	print(2/0)

def fn2():
	try:
		fn()
	except:
		print('调用 fn 出现异常')

fn2()

3、异常对象

print('hello')

try:
	print(a)
	print(10/0)
# 可以在异常类型后面跟一个 as XXX ,此时 XXX 就是异常对象
except NameError as n:     # 如果 except 后不跟任何内容,则此时它会捕获到所有的异常
	# 如果在 except 后跟着一个异常的类型,那么此时它只会捕获该类型的异常
	print('出现 NameError 异常',n)   # 这里的 n 就表示异常对象
except ZeroDivisionError:
	print('出现 ZeroDivisionError 异常')
# 这里的 Exception 写不写都行,不写也是捕获所有的异常
# Exception 是所有异常的父类,所以如果 except 后跟的是 Exception 它会捕获到所有的异常
except Exception:
	# 假如出现了上面两个异常之外的异常,走这里
	print('未知异常')
finally :
	print('无论是否出现异常,该代码总会执行')

print('异常出现后')

4、抛出异常

  • 可以使用 raise 语句来抛出异常
  • raise 语句后需要跟一个异常类或异常的实例
def add(a, b):
	# 如果 a 和 b 中有负数,就向调用处抛出异常
	if a < 0 or b < 0:
		# raise 用于向外部抛出异常,后面可以跟一个异常类,或者异常类的实例
		# 抛出异常的目的,是为了告诉调用者这里调用时出现问题,希望你自己去处理一下
		raise Exception('两个参数不能为负数')
	r = a + b
	return a + b

print(add(-20,30))

5、自定义异常类

自定义异常类,只需要创建一个类继承 Exception 即可

class MyError(Exception):
	# 里面可以什么都不用写,就继承 Exception 即可
	pass
def add(a, b):
	if a < 0 or b < 0:
		raise MyError('两个参数不能为负数')
	r = a + b
	return a + b

print(add(-20,30))

6、文件(打开)

操作文件的步骤:

① 打开文件

② 对文件进行各种操作(读、写),然后保存

③关闭文件

使用 open 函数来打开一个文件:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

参数:

​ file 要打开的文件名字(路径)

返回值:

​ 返回一个对象,这个对象就代表了当前打开的文件

说一个原始字符串的概念:

原始字符串就是在字符串的前面加一个 r,这个时候的字符串就变成了转义字符串,也就是说它不会把你字符串中的 \ 当成转义字符去使用

file_name = r'hello\demo.txt'      
# 比如说你在程序中写 \ 那么它就会把这个 \ 当成转义字符,这时候你在字符串前面加一个 r ,那么这时候这个字符串就变成了 原始字符串 ,字符串里面的东西该是什么就是什么,不会把 \ 理解为转义字符
#open(file, mode='r', buffering=-1, encoding_=None, errors=None, newline=None, closefd=True, opener=None)
# 使用 open 函数来打开一个文件
# 参数:
#	file 要打开的文件名字(路径)
# 返回值:
#	返回一个对象,这个对象就代表了当前打开的文件

# 创建一个变量,来保存文件的名字
# 如果目标文件和当前文件在同一级目录下,则直接使用文件名即可
# 在 windows 系统使用路径时,可以使用 / 来代替 \  (因为 \ 在程序中表示转义)
# 或者可以使用 \\ 来代替 \
# 或者也可以使用原始字符串 
file_name = 'demo.txt'
#file_name = r'hello\demo.txt'   # 原始字符串就是在字符串的前面加一个 r,这个时候的字符串就变成了转义字符串,也就是说它不会把你字符串中的 \ 当成转义字符去使用

# 表示路径可以使用 .. 来返回上一级
#file_name = '../hello/demo.txt'
# 如果目标文件距离当前文件比较远,此时可以使用绝对路径
# 绝对路径应该从磁盘的根目录开始书写
#file_name = '/Users/shenheping/Desktop/pythoncode/lesson6/demo.txt'
file_obj = open(file_name)  # 打开 file_name 对应的文件
print(file_obj)  # <_io.TextIOWrapper name='demo.txt' mode='r' encoding='UTF-8'>

7、文件(关闭)

# 打开文件
file_name = 'demo.txt'
file_obj = open(file_name)
# 当我们获取了文件对象以后,所有的对文件的操作都应该通过对象来进行
# 读取文件的内容
# read() 方法用来读取文件的内容,它会将内容全部保存为一个字符串返回
content = file_obj.read()
print(content)

# 关闭文件
# 调用 close() 方法来关闭文件
file_obj.close()    # 这样做会有一个问题,你一旦调用了 close()  方法,你后面再想对文件进行操作就不行了,文件已经被关闭了。所以你对文件操作的代码都要写在 close() 方法的前面

# with...as 语句
with open(file_name) as file_obj:  # 等价于 file_obj = open(file_name) 其实就是把 open(file_name) 的返回值赋值给了 file_obj
	# 在 with 语句中可以直接使用 file_obj 来做文件操作
	# 此时这个文件只能在 with 中使用,一旦 with 结束则文件会自动 close
	print(file_obj.read())

# 结合 try 语句使用:
try:
	with open(file_name) as file_obj:
		print(file_obj.read())
except FileNotFoundError:
	print(f'{file_name} 这个文件不存在')

8、简单读取

  • 使用 open() 去打开文件的时候,默认是以文本的方式打开的,open() 默认的编码格式是 None
  • 我们处理文本文件的时候必须指定编码
file_name = 'demo2.txt'

try:
	# 调用 open() 来打开一个文件,可以将文件分成两种类型:
	# 一种是纯文本文件(使用 utf-8 等编码格式编写的文本文件)
	# 一种是二进制文件(图片、mp3 、ppt 等这些文件)
	# open() 打开文件时,默认是以文本文件的方式打开的,open() 默认编码是 None
	# 所以处理文本文件时,必须要指定编码
	with open(file_name,encoding='utf-8') as file_obj:
		content = file_obj.read()
		print(content)
except FileNotFoundError as e:
	print(f'{file_name}文件不存在',e)

注:

  • 我们知道 read() 可以用来读取文件中的内容
  • 但是如果直接调用 read() 方法会将文本文件中的所有内容都读取出来
  • 如果要读取的文件较大的话,会一次性将文件中的内容加载到内存中,造成内存溢出。因此对于较大的文件不要直接调用 read()

9、读取大文件

再说 read() :

  • read() 可以接收一个 size 作为参数,该参数用来指定要读取的字符的数量
    • 默认值是 -1 它会读取文件中的所有字符
    • 可以为 size 指定一个值,这样 read() 会读取指定数量的字符
    • 每一次读取都是从上一次读取到的位置开始读取的
    • 如果字符的数量小于 size ,则会读取剩余所有的
    • 如果已经读取到文件最后了,则会返回空串
# 读取大文件的方式
file_name = 'demo.txt'
try:
	with open(file_name,encoding='utf-8') as file_obj:
		# 定义一个变量用来保存文件内容
		file_content = ''
		# 定义一个变量,用来指定每次读取的大小
		chunk = 100
		# 创建一个循环来读取文件的内容
		while True:
			content = file_obj.read(chunk)  # 这里会先读取 100 个字符,返回给 content,记录下当前的位置,然后下一次再进循环的时候,从上次记录的位置开始读,再读 100 个字符,返回给 content,记录下当前的位置,下一次再进循环的时候,假设只剩 60 个字符了,这时候就把 60 个字符全部读取,返回给 content,记录下当前的位置,下一次再进循环的时候,发现读取到的就是 空串,空串返回给 content ,对 content 进行判断,跳出循环
			file_content += content
			if not content:    # 空串在这里会转换成 false
				# 文件内容读取完成,跳出循环
				break
except FileNotFoundError as e:
	print(f'{file_name}文件不存在',e)
print(file_content)

10、readline 使用

  • readline() :该方法可以用来读取一行
  • readlines() :该方法用于一行一行的读取内容,它会一次性将读取到的内容封装到一个列表中返回
file_name = 'demo.txt'
try:
	with open(file_name) as file_obj:
		file_content = ''
		while True:
			# readline() :该方法可以用来读取一行
			content = file_obj.readline()   # 这里会每次读取一行放到 content 中
			if not content:
				break
			file_content += content

except FileNotFoundError as e:
	print(f'文件不存在{file_name}',e)
print(file_content)

file_name = 'demo.txt'
try:
	with open(file_name) as file_obj:
		# 该方法用于一行一行的读取内容,它会一次性将读取到的内容封装到一个列表中返回
		result = file_obj.readlines()   # 这个 result 是一个列表
		print(result[0])   # 获取读取到的第一行
except FileNotFoundError as e:
	print(f'文件不存在{file_name}',e)

11、文件写入

  • write() 用来向文件中写入内容
  • 如果操作的是一个文本文件的话,则 write() 需要传递一个字符串作为参数
  • 该方法可以分多次向文件中写入内容
  • 写入完成之后,该方法会返回写入的字符个数

注意:

向文件中做写操作时,使用 open() 方法打开文件时,必须要指定打开文件所要做的操作(读、写、追加),如果不指定操作类型,则默认是读取文件,而读取文件时是不能像文件中写入的

file_name = 'demo.txt'

try:
	# 使用 open() 打开文件时必须要指定打开文件所要做的操作(读、写、追加)
	# 如果不指定操作类型,则默认是读取文件,而读取文件时是不能像文件中写入的
	# r 表示只读
	# w 表示可写,使用 w 来写入文件时,如果文件不存在会创建文件,如果文件存在,则覆盖写
	# a 表示追加内容,如果文件不存在会创建文件,如果文件存在,会向文件中追加内容
	# + 为操作符增加功能
	# r+ 表示既可读,又可写,但是文件不存在怎会报错
	# w+ 同样在写的基础上,增加读
	# a+ 同样在追加写的基础上,增加读
	with open(file_name,'a',encoding='utf-8') as file_obj:
		# write 用来向文件中写入内容
		# 如果操作的是一个文本文件的话,则 write() 需要传递一个字符串作为参数
		# 该方法可以分多次向文件中写入内容
		# 写入完成之后,该方法会返回写入的字符个数
		file_obj.write('hello i am hepingfly')
		result = file_obj.write('hello i am Jimmy')
		print(result)   # 包括空格一共 16 个字符
except FileNotFoundError as e:
	print(f'文件不存在{file_name}')

12、二进制文件操作

  • 读取二进制文件,需要指定一下读取模式
  • 读取模式
    • t 表示读取文本文件
    • b 表示读取二进制文件

注意:

  • 读取文本文件时,read() 方法的 size 是以字符为单位的
  • 读取二进制文件时,read() 方法的 size 是以字节为单位的
file_name = '/Users/shenheping/Desktop/许嵩,何曼婷 - 素颜.mp3'

# 读取模式
# t 表示读取文本文件(默认值)
# b 表示读取二进制文件

with open(file_name,'rb') as file_obj:      # 指定读取二进制文件
	# 读取文本文件时,size 是以字符为单位的
	# 读取二进制文件时,size 是以字节为单位的

	# 将读取到的内容写入到一个新文件中
	new_name = '/Users/shenheping/Desktop/许嵩,何曼婷 - 素颜2.mp3'
	with open(new_name,'wb') as new_obj:
		while True:
			# 每次读取 1M
			chunk = 1024 * 1024
			content = file_obj.read(chunk)
			# 内容读取完毕,跳出循环
			if not content:
				break
			# 将读取到的数据库写入到新对象中
			new_obj.write(content)

13、seek() 和 tell()

1)、seek()

seek() 可以修改当前读取的位置

  • seek() 其实需要两个参数
    • 第一个参数:是要读取的位置
    • 第二个参数:计算位置方式。可选值:
      • 0 : 从头计算,默认值
      • 1 : 从当前位置开始计算
      • 2 : 从最后位置开始计算

2)、tell()

tell() 方法用来查看当前读取的位置

file_name = 'demo.txt'
with open(file_name,'rb') as file_obj:   # 这里我是把一个文本文件当成二进制文件去读
	content = file_obj.read(100)    # 这里返回一个字符串,这个字符串是二进制字符串
	content = file_obj.read(100)

	# seek() 可以修改当前读取的位置
	file_obj.seek(60)   # 这个就表示从第 60 个字节开始往后读
	file_obj.seek(70)   # 这个就表示从第 70 个字节开始往后读
	# seek() 其实需要两个参数
	# 第一个参数:是要读取的位置
	# 第二个参数:计算位置方式。可选值:
	# 0 : 从头计算,默认值
	# 1 : 从当前位置开始计算
	# 2 : 从最后位置开始计算
	file_obj.seek(10,1)   # 从第 80 个字节开始往后读,因为前面读到了第 70 个,再从当前位置往后读 10 个
	file_obj.seek(-10,2)  # 读取最后 10 个字节

	# tell 方法用来查看当前读取的位置
	print('当前读取到了---',file_obj.tell())  
	print(content)




file_name = 'demo2.txt'
with open(file_name,'rt',encoding='utf-8') as file_obj:   # 直接读文本文件(这个文本文件包含中文)
	# 假如这里我不写 3 的倍数,就有问题了,因为 utf-8 一个字符占三个字节,假如你写 4 ,你让他从第 4 个字节读就有问题了
	file_obj.seek(3)   # 这个就表示从第 3 个字节开始往后读
	print(file_obj.read())
	print('当前读取到了---',file_obj.tell())

14、文件的其他操作

  • os.listdir() 获取指定目录的目录结构
    • 需要一个路径作为参数,会获取到该路径下的目录结构,默认路径为 . 当前目录
    • 该方法会返回一个列表,目录中的每一个文件(夹)的名字都是列表中的一个元素
  • os.getcwd() 获取当前所在目录
  • os.chdir() 切换当前目录,作用相当于 cd
  • os.mkdir() 创建目录
  • os.rmdir() 删除目录
  • os.remove() 删除文件
  • os.rename('旧名字','新名字') 除了可以对一个文件进行重命名,也可以用来移动一个文件
import os
from pprint import pprint

# os.listdir() 获取指定目录的目录结构
# 需要一个路径作为参数,会获取到该路径下的目录结构,默认路径为 . 当前目录
# 该方法会返回一个列表,目录中的每一个文件(夹)的名字都是列表中的一个元素
result = os.listdir()
pprint(result)

# os.getcwd() 获取当前所在目录
result = os.getcwd()
pprint(result)   # '/Users/shenheping/Desktop/pythoncode/lesson6'

# os.chdir() 切换当前目录,作用相当于 cd
os.chdir('..')
result = os.getcwd()
pprint(result)   # '/Users/shenheping/Desktop/pythoncode'

# 创建目录
#os.mkdir('aa') # 在当前目录下创建一个 aa 目录

# 删除目录
#os.rmdir('aa')

open('aa.txt','w')
# 删除文件
os.remove('aa.txt')

# os.rename('旧名字','新名字') 除了可以对一个文件进行重命名,也可以用来移动一个文件
open('aa.txt','w')
os.rename('aa.txt','../aa.txt')