high-school-it-p3~> python基础语法-part1
<~~ 发表日期:2022-09-12 | 本文词数:5047 | 预计阅读时间:26分钟 ~~>

正式的对python语法的学习, 包含常见库函数的使用

有关python的基本介绍, 比如编译/解释/面向对象, 怎么自己下载python, 请看 p2: 基础科普与环境搭建
感谢:
本文的切片部分来源于 python切片完全指南(语法篇), 已经过原作者准许

变量与赋值

编程不就是为了模拟世界, 求解问题吗? 求解问题都需要什么? 需要数据, 还需要存放数据的地方, ++变量++, 就是这么一个存放数据的地方

变量变量, 顾名思义, 指的是可变的量 (有些语言严格区分可变性, 但python里不用在意这么多)
你可以理解为, 变量, 是一个箱子/寄存器, 装着一些可以运算的数值, 用来求解问题, 模拟现实

有两个概念:

  • 声明变量: 创建变量 (声明一个变量出现了)
  • 赋值: 把一个值装到箱子(变量)里

比如, 一个很简单的例子:

a = 1

这里, 我们声明了一个叫做 a 的变量, 然后把值装到了 a 中, 这就是 声明+赋值
右边是数值, 左边是变量名, 等号表示把右边的值赋给左边的变量

变量名是随便你取的, 但必须遵守命名规范, 只能由字母, 下划线开头, 不能由数字开头, 中间不能有空格分割, 不能与关键字相同

你也可以这样:

a = 1
b = a

我们先声明了一个变量a, 值是1, 然后把a放在右边传给了左边的b
第二行表示, 把a里面的数值取出来, 传给了b

python支持也支持这样的赋值操作, 甚至可以继续长下去

a = b = c = 1
d = e = f = 2

a,b,c = d,e,f

上面的代码中, a,b,c都被初始化为1, def则为2, 然后赋值, 把abc的值变成了2

你还可以这样, 表示把b中的值给a, 把a中的值给b, 达到交换值的效果:

a = 1
b = 2
a,b = b,a

你可能会疑惑:
当我把b中的值给了a时, a的值不就是2了吗, 此时再把a赋值给b, 那b不还是2吗?
该想法对应的代码是这样的:

a = 1
b = 2

a = b
b = a

但在交换数值的代码中, 你应该这样理解:

a = 1
b = 2

_t1 = b
_t2 = a
a = _t1
b = _t2

这表示, 进行 a,b = b,a 时, 会先把右边的变量给复制(赋值给新的变量)一遍, 再分别赋给a,b
只不过, 只要我们按照py的语法写, 我们就不需要考虑这么多了, py为我们隐藏了_t1, _t2, 你只需要可以这么写就行了
现在有没有明白, 为什么大家都说py语法简单呢? 之后还会有很多像这样甜的地方 :)

另外 选择题会出现这样的常见选项:

A.  a = 1, b = 2, c = 3
B.  a, b, c = 1, 2, 3
C.  a = 1; b = 2; c = 3

其中 A 是错的, 别问为什么, 因为 python 的作者这样设计 python 的而已


变量命名规范

python中, 变量的命名必须符合规范, 不然直接报错
在符合规范的同时, 你也应当尽可能地, 让变量名更加清洗直观, 比如年龄用age, 而不是a

命名规范如下:

  • 变量名由字母, 数字, 下划线组成
  • 不能由数字开头
  • 中间不能有空格分割
  • 不能与python中的关键字重名

以下都是合法的变量名

a
abada
asd111231
ad190123kkad
asd_asd1_asd2
_123daa
_as11

以下是不合法的变量名:

def
not
in
lambda

1123
123adad
sad;;-``
``?/
',

关键字:
关键字/保留字, 是python语法中具有特殊含义的东西, 比如for/while/and/or/not/in
这些都被称作关键字, 一般出现了关键字, 就能用对应的语法, 实现一些效果
比如for就对应for循环, not就对应逻辑取反, 具有特殊作用

你怎么知道哪些是关键字呢? 慢慢看吧, 像 def/import/for/if/break 这种经常使用在特殊位置, 有特殊功能的字符, 自然就是关键字了, 又不会考你超纲的关键字


注释

作用

注释, 是以py规定的特殊字符, 而开头的语句, 解释器会无视解析到的注释, 只解析代码
注释, 能够为阅读代码的人提供思路, 迅速明白这段代码做了什么, 而不用一行一行读代码来明白代码做了什么

举个例子:

a = 1  此时 a 的值为 1
a = 1  # 此时 a 的值为 1

前者会报错, 因为 "此时 a 的值为 1" 也被解释器当作代码而进行解析, 自然就会报错了
后者不会报错, 因为解释器解析到 # 开头的那段文字后, 会无视/跳过这段注释

再举个例子:

# 以下的代码能够获取a,b,c三个变量中的最大值, 并进行输出

a = 1
b = 2
c = 3
max = -1
if a > b > c:
  max = a
elif b > c:
  max = b
else:
  max = c
print(max)

瞧, 你看上面的代码时,不用一行一行地去理解这段代码到底是干啥的, 直接看别人给你写的注释, 就能大致明白这段代码的作用了

单/多行注释

python 的注释分为两种, 一种是以单行注释, 一种是多行注释, 直接看例子就明白了

# 12345
# 上山打老虎
# 老虎打不着
# 打到小松鼠

''' 
12345
上山打老虎
老虎打不着
打到小松鼠
'''

以井号开头的是单行注释, 通常用在注释仅仅是一两句话的时候
如果要注释有很多行, 用以三个引号开头, 三个引号结尾的多行注释更方便 (无论单双引号都可以)


基本数据类型

前面说了, 求解问题时, 你需要数据, 还有存放数据的地方
如何存放数据, 我相信你已经明白了, 现在就要讲数据本身了

为了模拟现实, py将数据进行了抽象与分类:

  • int: 对应整数
  • float: 对应实数
  • string: 对应文字
  • bool: 对应真与假

有了这些 ++基本数据类型++, 我们就能够模拟世界了, 进行抽象, 求解问题了

举些例子:

  • 我今天吃饭了吗: 用bool来抽象到底吃饭没, True就是吃了, False就是没吃
  • 我的年龄是16岁: 用int来表示 16 这个数字
  • 我的名字叫做: 用string来表示 "赵二狗", "Anasdpa" 这样的文字

同时, 我们还可以用这些 ++基本数据类型++, 构建出 ++复杂数据类型++, 比如, 我想创建一个类型, 叫 student, 表示学生
你可以这样抽象:

student {
  age:  int,
  height: float,
  name: string,
  fat_or_not: bool
}

py中也有语法, 支持创建自定义的类型, 但不是本系列的重点, 此处仅提一嘴而已


运算符与优先级

python 提供了一些运算符, 能够让你进行加减乘除, 逻辑运算之类的操作
这些运算符, 有各自的优先级, 决定了当运算符有多个时, 应该优先计算哪个
在以下的图表中, 优先级1是最高, 数字越大优先级越低

算术运算符

算术运算符, 可以让你对数字类型 (int, float) 进行计算, 得到新的数字

运算符描述例子优先级
**x的y次方x**y1
*x乘以yx*y2
/x除以y, 产生实数值x/y2
//x除以y, 产生整数值x//y2
%x除以y, 取余数x%y2
+x加yx+y3
-x减yx-y3

瞧, 非常符合小学数学的优先级概念, 乘法要比加法先算, 次方要比乘法先算 :)

算术运算符, 可以与赋值运算符相互结合:

a = a + 1

a += 1  # 是上面的等价物

类似的, 还有 -=, *=, %= 这种语法在变量名很长时, 会很有用, 不必把变量名写两遍

关系运算符

关系运算符, 若关系成立则返回 True, 不然返回 False, 如 1 < 2 是 True, 因此又称为 比较运算符
(关系运算符并不注重优先级, 谁先谁后一眼就看出来了)

运算符描述例子
>x 大于 yx > y
<x 小于 yx < y
>=x 大于等于 yx >= y
<=x 小于等于 yx <= y
==x 等于 yx == y
!=x 不等于 yx != y

注:
本博客使用了连体字特性, 因此你看见的>=其实是>号右边跟着=, !=其实是感叹号!后面跟着=, ==其实是两个=


基本数据结构

数据结构, 其实就是数据的存储结构, 根据场景与数据之间的逻辑关系, 设计出的不同复杂程度的结构

举个例子, 有种数据结构, 叫做 队列 (Queue), 其实模拟的就是日常生活中排队的场景, 对数据进行存储:
在排队时, 来得越早离开越早, 来得越晚离开越晚, 这不难理解
此时的队列, 就是一个 单向队列, 只允许在一端删除元素(排队的人买好东西走了), 另一端增加数据(新来个排队的)

根据不同的场景, 不同的逻辑关系, 需要使用不同的方式存储数据, 这种方式, 便是数据结构
当然, 我们此处仅学习基本数据结构

列表

列表(list) 仅表示装着一定数量元素的序列, 可以通过 索引(index) 访问元素

单索引

我们可以通过单个索引, 访问对应的单个元素

举个例子:

# 有着 3 个元素的列表
list = [300, 400, 500]

# 通过索引, 访问元素 (索引从 0 开始)
list[0]  # 300
list[1]  # 400
list[2]  # 500

扩展: 为何 index 从 0 开始?
因为, 所谓的index, 代表的概念, 其实是 偏移量
实际上, py中的列表, 其元素的内存地址是连续的, 创建一个新的列表时, 会先申请一块内存空间, 用来存放元素
用下标得到元素时, 实际是通过下标, 计算该元素的对应内存地址, 进行访问, 那么, 如何计算的呢?

你可以这样理解:
一个长度为 3(有 3 个元素)的列表, 拥有 3 块内存空间
你可以从左向右, 画 3 个紧挨在一起的格子, 列表本身, 就代表着这 3 个格子

列表本身的内存地址, 其实就相当于第一个元素的内存地址, 你可以想象为第一个格子
如何访问第 1 个格子? 第一个格子向右跳 0 格
如何访问第 2 个格子? 第二个格子向右跳 1 格
如何访问第 3 个格子? 第三个格子向右跳 2 格

现在懂了吧? 下标其实就是偏移量, 代表的是 相对于第 1 个格子偏移了多少格
要访问第几个格子, 直接加上偏移量即可 (比如 list[0] 偏移了0, 是第一个元素)

如果你非要把下标从 1 开始, 那就得这样:
list[1] 代表第 1 个元素, 但相对于第一个元素的偏移量还是0
于是在根据下标1, 求第一个元素时, 偏移量就是 1 - 1 = 0
同理, 根据下标2, 求第二个元素时, 偏移量就是 2 - 1 = 1
同理, 嗯举下标index, 求第 index 个元素时, 偏移量就是 index - 1

如果你每次计算偏移量, 都得计算一遍 index-1, 次数一多起来不就会降低效率?
现代计算机其实可以忽略这点影响, 但早期计算机可是一寸内存一寸金, 这个习惯自然而然地被保留下来了
(好吧, 原因其实真的只是因为习俗)

而且在某些情况下, 下标从0开始会很方便, 有相关论文, 但本菜鸡看不懂 :)

py 中还存在 负索引 的语法糖, 也就是用负数来充当索引, 此时, 下标从右往左, 从 -1 依次递减
比如 ls[-1], 就代表倒数第一个元素, ls[-2] 就代表倒数第二个元素

举个例子, 已知 a = [10, 20, 30, 40, 50] 让我们将其与图表进行对立:

元素1020304050
正索引01234
负索引-5-4-3-2-1

显而易见的, ls[-1], 就相当于 ls[len(ls) - 1] ( len(ls) 表示得到列表ls的长度 )

索引越界

于此同时, 通过下标/索引访问某个列表的元素时, 不应该超出范围:

demo.py
a = [10, 20, 30, 40, 50]
a[5]

当我们在命令行中执行 python demo.py (见 p2: 基础科普#环境搭建 ) 或直接使用图形化工具, 点击运行按钮

脑子想一想都知道肯定编译不过, 我们将得到以下报错:

Traceback (most recent call last):
  File "/home/jedsek/a.py", line 2, in <module>
    a[5]
    ~^^^
IndexError: list index out of range

看不懂的直接像做玩英语完形一样当作 "哔(屏蔽词)" 跳过就好, index 是 "索引/下标的意思", 这条报错表示, 下标越界了!
因为 a 是个长度为 5 的列表, 所以只能用 n ∈ [0, 5) ∩ N (区间[0, 5) 范围内的自然数) 来当下标, 即 0 到 4, 因此 5 是非法的
负索引也是一样的道理, 不能越界哟~~

切片

切片, 能通过索引与冒号, 创建一个区间, 访问一定范围内, 列表的多个元素

举个例子:

numbers = [201, 202, 203, 204]  # 定义列表
numbers[0:4]  # [201, 202, 203, 204]
numbers[0:2]  # [201, 202]
numbers[0:1]  # [201]

在上面的例子中, 我们通过索引+冒号, 创建了一个左开右闭的区间, 访问索引在该区间内的所有元素
我们通过 list[m:n], 得到了一个子列表, 只要索引在 [m, n) (左开右闭区间) 内, 就会被放入这个子列表

图表依然会较为直观:

numbers = [201, 202, 203, 204] |||| |--|--|--| |切片|对应索引|取出的子列表| |0:4|[0, 1, 2, 3]|[201, 202, 203, 204]| |0:2|[0, 1]|[201, 202]| |0:1|[0]|[201]| |-3:-1|[-3, -2]|[202, 203]|

默认的步长是1, 我们还可以再加一个冒号设置其步长, 比如 numbers[0:4:1] 就相当于 numbers[0:4]
numbers[0:4:2] 则相当于从 [0, 1, 2, 3] 中的第一个元素 0 开始, 作为下标取 numbers 的元素, 然后走两步到 2, 依次类推
步长不能设置为 0, 不然会报错

有个地方需要注意: 当切片对应的索引个数为 0 时, 子列表是个空列表, 即单个的 []
举个例子: numbers[3:2] 将会得到空列表, 因为步长默认是 1, 而索引越界会直接报错, 两者注意区分

省略与默认值

关于此处的知识点, 前面已经提到过一部分了, 即步长默认是1, 但现在还有更多更多滴细节 :)

已知 a = [10, 20, 30, 40, 50], 你可以使用 a[start:stop:step] 来表示 a 的切片, 其中 start/stop/step 都是可以省略的
当它们被省略时, 会有一个默认值作为替补登场, 比如步长被省略, 即 a[start:stop]/a[start:stop:] 的形式时, 步长会使用默认值1

而对于 start/stop 来讲, 当步长分别为正数或负数时, 默认值是不同的, 因为步长为正时, 切片是正着取元素的, 为负时自然是倒着取
举些例子, 已知 a = [10, 20, 30, 40, 50, 60, 70]:

  • a[2:5:1] 中, 2:4 对应的下标是 [2, 3, 4], 索引对应的元素是 [30, 40, 50], 因为步长为1, 所以结果直接就是 [30, 40, 50]
  • a[2::1] 中, 右边界为省略, 因为步长为正, 其默认值是该列表的长度, 结果是 [30, 40, 50, 60, 70]
  • a[:2:1] 中, 左边界忽略, 因为步长为正, 其默认值是0, 结果是 [10, 20]
  • a[2::-1] 中, 右边界忽略, 因为步长为负, 其默认值是0, 结果是 [30, 20, 10]
  • a[:2:-1] 中, 左边界忽略, 因为步长为负, 其默认值是该列表长度, 结果是 [70, 60, 50, 40]

什么鬼, 这是人能记得下来的? 而且这些还只是正索引, 对于负索引呢? 别慌, ***才直接背, 你只需要熟悉定义就行:

  • start 代表列表中某个元素的下标, 这个元素是切片的起头
  • stop 代表列表中某个元素的下标, 原列表中该元素附近的那个元素才是切片的结尾 (因为左闭右开, 且步长正负不知)
  • step 代表步长, 其正负决定正序还是倒序取, 其大小决定一次性跳过多少个元素后再取

要开始了哦, 超级直观的方法:

a[2:5:1] 为例:

  • 因为步长为正, 先画条从左往右的箭头: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 start2, 所以箭头的起点从 30 开始: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 stop5, 所以箭头的终点是 50 (箭头从 60 逆方向缩一格): [10, 20, 30, 40, 50>, 60, 70]

a[2::1] 为例:

  • 因为步长为正, 先画条从左往右的箭头: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 start2, 所以箭头的起点从 30 开始: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 stop 被省略, 所以箭头无终点, 一直延伸: [10, 20, 30, 40, 50, 60, 70]>

a[:2:1] 为例:

  • 因为步长为正, 先画条从左往右的箭头: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 start 被省略, 所以箭头的起点在左边的无限远处, 不用动: [10, 20, 30, 40, 50, 60, 70]>
  • 因为 stop2, 所以箭头的终点是 20 (箭头从 30 逆方向缩一格): [10, 20>, 30, 40, 50, 60, 70]

a[2::-1] 为例:

  • 因为步长为负, 先画条从右往左的箭头: <[10, 20, 30, 40, 50, 60, 70]
  • 因为 start2, 所以箭头的起点从 30 开始: <[10, 20, 30, 40, 50, 60, 70]
  • 因为 stop 被省略, 所以箭头无终点, 一直延伸: <[10, 20, 30, 40, 50, 60, 70]

a[:2:-1] 为例:

  • 因为步长为负, 先画条从右往左的箭头: <[10, 20, 30, 40, 50, 60, 70]
  • 因为 start 被省略, 所以箭头的起点在右边的无限远处, 不用动: <[10, 20, 30, 40, 50, 60, 70]
  • 因为 stop2, 所以箭头的终点是 40 (箭头从 30 逆方向缩一格): [10, 20, 30, <40, 50, 60, 70]

对于负索引也是一样的道理, 先确定好 startstop 的位置, 然后根据步长画对应方向的箭头
照这样算, 最后被覆盖的元素, 就是当步长大小为 1 时, 构成切片的元素, 逆序还是倒叙看箭头就行
最后根据步长大小, 决定跳过多少个元素后取一个, 将其作为切片的元素即可


字典

python 中的字典, 实际上就是一些键值对组成的集合
键值对, 指的就是根据某个键值, 获取其对应的另外一个值

举个例子:

  • 名字: "小明"
  • 年龄(year): 16
  • 身高(cm): 178

名字 -> "小明", 根据键, 得到对应的值, 这就是一对键值对, 由键值对组成的集合, 在 python 中便叫作字典了

在 python 中, 你可以用一对花括号来定义字典:

a = {'Shadd': 10, 'Lasd': 111, 'PPqwea': 899}

冒号前的就是键, 冒号后的就是这个键对应的值, 键值对之间用逗号进行分割, 比如: a['Shadd'] 将得到 10