Python笔记 #1 基础语法
本文最后更新于:2023年7月3日 中午
跟着贵系的暑培过了一遍语法,然而写代码时还是感到乏力,总觉得要打一遍才能记住,于是有了这篇博客。
本文部分内容参考了清华 AYF 同学的教程,部分参考了 Python 官方的一份 Tutorial,函数用法参考了 xyfJASON 的博客。本文将持续更新。
Python 特性
相比于 C 的编译型、弱类型、静态类型特点,Python 则是一种解释型、强类型、动态类型的语言。
交互式 vs 脚本
作为一种解释型语言,Python 不需要像 C 一样编译运行,它可以逐行运行代码,因此又分为交互式窗口运行与脚本运行两种模式:
- 交互式窗口:在 CLI 输入
python,即可进入交互式窗口,输入一行代码即可运行。本文使用的交互式环境是 IPython,输入ipython即可呼出。 - 脚本:在 CLI 输入
python xxx.py,就会依次执行整个脚本文件。本文使用的 IDE 是 PyCharm。
Hello World
1 | |
在交互式窗口,>>> 作为提示符,在 IPython 中则是 In [1]:。执行 exit() 或者按下 Ctrl+D 就能退出窗口。
注意到,这行代码中没有 ; 分号,可以直接运行而无需编译,字符串用了单引号,注释用 # 开始【与 C 不同】。
简单数据类型
变量类型
【与 C 不同】,Python 不用声明变量类型,解释器自动解释。
int:变长整数,默认是 4 字节,有需要时自动增长,用于高精度运算,还支持十六进制、八进制和二进制表示。complex:自带的复数类型,表示为real + imag*1j的形式,虚部为 1 的时候不可省略 1,可以用j也可以用J。实虚部分别为一个float。float:8 字节浮点数,【相当于 C 的 double】。bool:True 和 False,注意首字母大写,用作数值计算时与 C 一样视作 0 和 1。NoneType: None,空值,常用于返回值、特判。
需要单独说明的是,Python 会存储所有的 -5 到 256 的整数,其他任何变量是这些值时,会被指向这个预先开好的内存,因此任何两个值为 5 的 int 变量都指向同⼀内存地址。
尽量用小写变量名(下划线法),这是 Python3 的主流命名方式。
运算符
这里列出常见的运算符,运算符可以重载,重载需要修改其对应的定义函数。
算术运算符:
+-*【与 C 相同】%:35 % 4 == 3,-35 % 4 == 1,35 % -4 == -1,-35 % -4 == -3,【与 C 不同:负数对正数取模时返回正余数,而非像 C 那样返回负余数】/:__trudiv__,真除,得到float结果//:__floordiv__,除后向下取整(不是舍弃小数),得到int结果**:__pow__,幂运算
比较运算符:
<<=>>===!=【与 C 相同】
位运算符:
&|^~<<>>【与 C 相同】
赋值运算符:
=:赋值号,不能被重载+=-=*=/=%=【与 C 相同】- 注意 Python 中没有
++--的运算符,只能通过+= 1实现
逻辑运算符:
andornot【类似 C 中的&&||!,具有短路机制】- 对于
and和or,通常用于条件分支bool的判断,如果非要连接int变量,得到的结果不会直接转换为bool,而是返回能够得出结果的最后一个变量【与 C 中的短路类似】
三目运算符:
a if cond else b:相当于 C 中的cond ? a : b,注意其参数顺序【与 C 不同】,但更贴近自然语言
特殊条件运算符:
in:被包含于,详见下文「容器」,返回 boolnot in:in的否定,返回 boolis:判断两个变量的地址是否相同,不可重载,返回 boolis not:判断两个变量地址是否不同,不可重载,返回 bool
字符串
Python 将字符串封装成了基本类型并处理了多种运算,带来许多便利,注意基本类型本身是不可修改的,所谓修改其实是将重新生成另一个字符串,再将其赋值给目标。
此外,Python 中没有单独的字符类型,单个字符将被视为长度为 1 的字符串。【与 C 不同】,可以用 "" 或者 '' 括起字符串。
下面是一些常用函数,设 str 是一个字符串:
str.title():返回单词首字母大写,其余字母小写(不管以前是不是大写)的字符串str.upper()、str.lower():返回全大/小写的字符串str1 + str2:返回用加号拼接的字符串【与 C++ 的string类似】str * 3:返回重复三遍拼接的字符串- 制表符
\t,换行符\n【与 C 相同】 str.lstrip()、str.rstrip()、str.strip(): 返回删除开头/末尾/两端空白的字符串str.replace(str1, str2):将字符串中的单词str1全部替换成str2str.split(str1):以str1为分隔符把字符串拆成子串,并返回包含子串的列表,默认分隔符为空格str.zfill(n):将数字字符串补全前导零,n为总位数' '.join("a", "b", "c"):用' '作为连接符,拼接所有子串得到一个长字符串
此外还有一种跨行字符串,用 ’‘’ 或 ”“” 括起来,由于脚本中顺序执行时不会输出变量值,这类字符串常用于跨行注释,特别是函数头注释。
输入输出与编码
此处说明几个重要函数:
len(obj):获取obj的长度,常用于获取字符串(注意是 Unicode,因此中文和英文字符都占 1 位)、字节串、容器的长度str(a): 把数字(整型或浮点型)a转换成字符串chr(0x4f60):将整型变量i转化成单个字符ord('你'):获取单个字符的编码(Unicode),注意在 Unicode 中,英文字母的编码有意设置与 ASCII 码一致
最常用的输出语句 print,本质上是将变量转化为字符串输出,在末尾自动换行。该函数可以有多个变量,变量间用 , 分隔,输出时会用空格隔开。
如果要在一句话中插入许多变量,这条 print 语句可能会很丑陋,因此 Python 中有三种格式化字符串的方法。
%:如print('I am %d' % (age)),【与 C 类似】str.format():如print('hello, {}'.format(name))f-string:如print(f'hello,{name}.I am {age}')
其中 f-string 是 Python3.6 的新特性,最为直观便利。
此外,input('Press ENTER to continue') 是常见的一种输入语句,会显示对应的提示内容,读入内容以以字符串存储,可以用 int(input()) 或 map(int, input().split()) 处理。
字节串
bytes 即是 Python 中的字节串,它表示最纯粹的二进制数据,【类似 C 的 unsigned char *】,但是在显示上它仅支持 ASCII 显示,因此用肉眼看显得有些不伦不类,通常它只存在于数据的处理过程中。
bytes 的构造与字符串类似,但是要加一个 b 做前导,如 print(b'\x41')。
容器
Python 提供了一系列内置容器,它们如同 C++ 的 STL ⼀样,不过比 STL 的用法灵活得多。
同样先介绍几个重要函数:
type(obj):可以获取参数obj的类型isinstance(obj, class_or_tuple):可以判断obj是不是类的实例id(obj):获取obj的地址,a is b等价于id(a) == id(b)
列表 | List
列表(list)是很常用的容器,常被看作 Python 中的数组,但实际上【与 C++ 的 vector 类似】。设 lst 是一个列表:
基础操作
- 定义列表:
lst = [a, b, c, d],其中a,b,c,d等是列表的元素,类型可以不同 - 构造空列表:直接写
[]或list() - 打印列表:
print(lst)(会将列表中的元素列出,括在方括号里) - 访问元素:
lst[3], 下标从 0 开始。此外,还支持负数索引,-1表示倒数第一个,-2倒数第二个【与 C 不同】
修改、添加、删除元素
- 修改:直接访问元素并赋值
lst.append(x):在列表末尾添加元素xlst1.extend(lst2):在列表末尾拼接另一个列表,也可以用运算符+=lst.insert(idx, x):在列表索引idx处插入一个元素x(插入后,x的索引是idx,其后的元素后移一格)del lst[3]:删除指定元素(删除后,其后元素前移一格)lst.pop():弹出并返回最后一个元素lst.pop(idx):弹出并返回指定元素lst.remove(x):删除第一个值为x的元素
组织列表
lst.sort()、lst.sort(reverse = True):对列表排序,永久性修改顺序sorted(lst)、sorted(lst, reverse = True):返回排序后的列表,但不改变列表原有顺序lst.reverse():翻转列表,永久性修改顺序len(lst):返回列表长度,即元素个数(不论类型)
遍历列表
从头到尾遍历列表:
for i in lst:循环表达式,【与 C 不同】i是列表元素,不是索引;循环结束后i停留为最后一个元素遍历列表时同时遍历下标:
for idx, value in enumerate(lst):若要检查列表是否为空,可以用
if lst:条件表达式,返回 bool
列表切片
lst[l:r]:返回一个列表,元素依次是lst列表的索引在左闭右开区间内的元素,省略l或r则默认从头开始或到尾结束lst[l:r:step]:指定步长为step切片,step为 -1 时返回倒序,省略参数后写作lst[::-1]- 可以用循环遍历列表切片:
for i in lst[l:r]: - 复制列表:在切片中同时省略
l和r,即返回从头到尾的列表,如lst2 = lst1[:],而非lst2=lst1,后者lst1和lst2实质是同一个列表【类似 C 的引用】
元组 | Tuple
元组就是元素值不可修改(弱意义上的,其中元素的元素可以被修改)的列表。设 tpl 是一个元组:
基础操作
- 定义元组:
tpl = (a, b, c, d),把列表定义中的方括号改成圆括号()即可 - 定义时的小括号有时候可以省略,可以直接用
,逗号构造元组【与 C 不同,没有逗号运算符】 - 构造单元组:
(1)会被理解成表达式,要用(1,) - 构造空元组,直接写
()或tuple(),但(,)会报错 - 访问元素:
tpl[3], 下标从 0 开始。
遍历元组
for i in tpl: 和列表一样。
修改元组
元组中元素的值不能修改,但是元组变量本身可以被赋值,这点与字符串类似。此外,如果元组的中的元素是可修改的,如 List,则可以修改 List 内部的元素。
元组解包
经典的 Python 交换赋值代码:a, b = b, a,利用了解包和逗号构造。
其余的若干种解包方法待补充:https://zhuanlan.zhihu.com/p/351369448
元组打包
zip() 函数用于将若干个迭代容器打包为元组(通常是两个 List),返回一个 zip 对象。如果初始 List 的大小不同,则会向最小的对齐。初始的 zip 对象不占内存空间(Python 3.x 新特性),如果要展示,则需手动 list() 转换。
1 | |
集合 | Set
集合由一组无序、互不重复的元素构成(在数学上也是如此),在内部用哈希实现。设 st 是一个集合:
基础操作
- 定义集合:
st = {1, 2, 3},把列表定义的方括号改成花括号{}即可 - 构造空集合:只能用
set(),因为{}的表达被空字典占用了 - 由列表转集合:
st = set([1, 1, 1, 2, 2, 3]),会自动去重,常用于去重列表的遍历for i in set(lst): - 注意集合的元素必须是可 hash 的,不能为 list 这种可变容器变量
添加、删除元素
st.add(4):添加一个元素st.remove(2):删除一个元素
字典 | Dictionary
字典是一系列「键值对」,用于存储一组有穷映射,可将任何 Python 对象作为值,【类似于更高端版本的 C++ 的 map】。
基础操作
- 定义字典:
dic = {'name': '张三', 'age': 18},花括号括起一系列键值对,键与值之间冒号:分隔,键值对之间逗号,分隔。 - 访问元素:
d['name'],用键访问 - 注意字典的键必须是可 hash 的,不能为 list 或 set 这种可变容器变量,但可以是 tuple
添加、修改、删除
- 添加:直接赋值即可(即使键本来不存在),如:
dic['x'] = 0 - 修改:
dic['age'] = 18,直接赋值即可 - 删除:
del dic['age']
遍历字典
- 遍历所有键值对:
for k, v in dic.items():,其中items()返回键值对的列表 - 遍历所有键:
for k in dic.keys():,其中keys()返回键的列表,可省略 - 由于
keys()的本质是列表,各种对列表的操作也适用,如:for k in sorted(dic.keys()):或if 'age' in dic.keys(): - 遍历所有值:
for v in dic.values():,其中values()返回值的列表
迭代器
前文提到的所有容器,包括字符串、字节串都是可迭代的,这意味着它们可以用 for 循环来遍历,也可以用生成式构造(下面介绍)。
但最为特殊的迭代器是 range 类型,作为数值列表,与列表有相似之处,但它实际上不占用内存(用于大循环时很节省空间)。下面是一些常用方法:
range(l, r):依次生成左闭右开区间中的整数【类似于 C++ 的for(int i = l; i < r; i++)】,如果省略左端,会默认以 0 开始range(l, r, step):指定步长为step【类似于 C++ 的for(int i = l; i < r; i += step)】min(range(l, r))、max(range(l, r))、sum(range(l, r)):返回数值列表的最小值、最大值、总和- 在
for i in range(10):的循环中改变了i的值,如i += 1, 不会影响循环的次数,因为迭代器中的数值列表是不可修改的【与 C 不同】
生成式
使用生成式构造容器是非常常见、高效的操作,下面举几个例子:
a = list(range(10)):直接转化数值列表为基本列表lst = [i ** 2 for i in a]:列表生成式st = {x % 7 for x in a}:集合生成式dic = {x ** 2: x for x in a}:字典生成式,保留:来分隔键与值tpl = tuple(x + 1 for x in a):元组生成式,直接用()会被当成表达式
综上所述,生成式表达可以用三段式:表达式 for 循环变量 in 迭代对象 if 筛选条件,其中最后的筛选条件不一定要。
此外,还可以用更简单的 map() 函数构造容器,其内置了一系列映射句柄,如 map(int, ['1', '2', '3']),或 map(square, [1, 2, 3])。
流程控制
Python 中不用大括号来显式地分块,而是用冒号配合缩进(Indent)。代码块与代码块之间至少隔着一个空行表示结束。当一个代码块必须存在,又不想写任何语句的时候,可以写一个 pass 作为占位符。
条件分支
1 | |
注意,elif 和 else 后面也要有冒号配合缩进,如果有多个条件,用 and 和 or 逻辑运算符连接。
for 循环
前面所列的容器、数值列表都可以用于 for 循环迭代,比较特别的是字符串也可以迭代:
1 | |
此外,如果在数值列表的迭代中用不到迭代变量 i,仅作为迭代次数使用,可以用 _ 变量表达。
while 循环
1 | |
跳出循环可以用 break 和 continue【与 C 相同】
异常控制
在 Python 中,我们可以使用 try 和 except 语句来捕获和处理异常。异常处理使我们能够在程序出现错误时执行一些特定的操作,而不是让程序崩溃。下面是基本用法:
1 | |
注意,如果这里存在 Exception 还无法捕获的异常,例如 KeyboardInterrupt(在命令提示符中如果按下 Ctrl+C 结束终止的键),则可以换成 BaseExpection,这是 Exception 的父类。
另一种常用的方法是循环 try 语句,适用于 API 接口的访问:
1 | |
函数
基础函数
1 | |
函数传参
Python 是不允许程序员选择采用传值还是传址的。Python 参数传递采用的肯定是「传对象引用」的方式【类似 C 中传值和传址的结合】。如果函数收到的是一个可变对象(比如 list、dict)的引用,就能修改对象的原始值;如果是一个不可变对象(比如 int、float、str)的引用,就不能直接修改原始对象。
传入参数的数量可以不固定,但是必须指定默认值;也可以调换顺序,但必须指明对象。
1 | |
当然,也可以传递列表等容器,但传递的也是列表的地址,在函数中修改同样会改变原列表,如果不想修改原列表可以用 [:] 传递切片。
传递任意数量的参数
- 在形参前加星号
*,Python3 会创建一个该形参名称的元组,本质上是一种元组解包 - 在形参前加双星号
**,Python3 会创建一个该形参名称的字典
返回值
作为动态类型语言,函数返回值可以不固定,可以多个 return 在不同情况下返回不同值,或者没有 return(等价于 return None)。
值得注意的是,Python 中虽然不需要指定参数和返回值的类型,但可以使用类型提示(Type Hints)来在函数定义时体现参数和返回值的类型。这是一种对函数参数和返回值进行注释的方法,它可以增加代码的可读性和可维护性,并提供了一种方便的方式来指定函数的输入和输出类型。
1 | |
函数模块调用
函数可以被存储在模块中被调用,模块是扩展名为 .py 的文件,包含函数的代码【类似于 C 的头文件】
- 导入整个模块:使用
import pizza导入,调用时使用.句点,如:pizza.make(16, 'green peppers') - 导入模块中特定函数:使用
from pizza import make, eat,调用时无需句点,直接用函数名 - 导入特定函数别名:使用
from pizza import make as mk,调用时无需句点,直接用别名 - 导入模块中所有函数:使用
from pizza import *,调用时无需句点,但是会污染命名空间,不建议使用
所有 import 都放在程序开头【类似于 C++ 的 #include<>】。
第三方模块
作为一种工具语言,大部分情况我们都是通过「调包」完成任务,即导入第三方库。市面上的第三方库可以通过多种途径获取,包括 pip、conda 等。在命令行中使用 pip list 即可查看当前安装的所有库和版本信息,也可以结合管道 pip list | findstr numpy(Windows)。
模块封装
在 Python 的程序模板中经常看见 if __name__ = '__main__',这其实是 Python 中的一种 Magic Method。
Python 中所有 .py 文件都可被视为一个模块,当模块作为脚本被直接运行时,其 __name__ 的值变为 __main__。相反,当模块被其他导入其他脚本运行时,其 __name__ 存放的就是模块的名字(类似环境变量)。
而作为一种解释型语言,Python 会将所有导入的代码全部顺序执行一遍。如果我们在一个脚本中实现了某个算法,想将其作为模块在其他脚本中调用,我们并不希望它在 import 的那一刻就执行,因此,正确的做法是将其封装成函数。
但如果要单独运行该模块,譬如调试功能时,使用 __name__ 就可以完美解决:
1 | |
Lambda 匿名函数
和很多语言⼀样,Python 中可以使用 Lambda 匿名函数完成一些简单的逻辑,但比较特殊的地方在于,Python 中匿名函数必须只由单个表达式构成,这个表达式的值也就是匿名函数的返回值。
lambda 关键字可以用来创建一个匿名函数,紧跟其后的是参数列表和用冒号 : 分割开的单个表达式。如,lambda x: 2 * x 是将任何输入的数乘 2,而 lambda x, y: x+y 是计算两个数字的和。
使用匿名函数的经验准则是保持简单以及只在本地使用一次。一种常见却不推崇的做法是将其作为简单函数的另一种声明方式,赋值给变量,如:
1 | |
对 lambda 函数命名的唯一作用可能是出于教学目的,其问题在于这使得调试不那么直观——错误信息只会提示某个
lambda函数存在问题,但不会提示哪个函数。
正确的做法应该是将其作为参数传递,如 .sort 函数、sorted 函数等,此时单个表达式会被计算为一个值并且参与后续的计算。
1 | |
文件操作
open() 函数的第二个实参 w 表示写入(自动创建或覆盖原内容),r 表示只读,a 表示附加(自动创建或添加到文件末尾),r+ 表示读写。如果不加第二个实参,则默认为 r 只读。
读取文件
一种【与 C 相似】的操作是:
1 | |
此外,还有一种更推崇的操作方式,使用 with 上下文管理器,将打开的文件的对象存储在 as 后的变量,这样可以避免由于忘记关闭文件导致丢失数据:
1 | |
回到读取本身,方法 read() 读取整个文件的内容并返回单个字符串,并包含每个 \n 换行符。
但实际操作中读入一个长字符串是丑陋而且难以处理的,我们更倾向于逐行读入,用 line.rstrip() 消除行末的 \n:
1 | |
另一种逐行读入是创建一个包含文件各行内容的列表,每个元素是一行内容的字符串,包含行尾的 \n:
1 | |
写入文件
1 | |
方法 write() 表示将字符串写入文件。如果要写入数值,应先用 str() 将其转化为字符串。
同样,按行写入也有对应的函数 writelines(),但要求传入的参数为字符串列表,同时每个字符串后面都要自带换行符,因此比较鸡肋。
如果要追加写入文件,则需要将 w 替换为 a 模式,需要注意的是新文本可能会在旧文本之后立即添加,因此可能需要添加一个换行符 file.write("\n")。
类
当涉及到面向对象编程(Object-Oriented Programming, OOP),Python 提供了类(class)的概念,允许你创建对象、定义属性和方法。
类与对象
在 Python 中,类是创建对象的蓝图。类定义了对象的属性和方法,可以通过实例化(即创建对象)来使用类。下面介绍类与对象的基础用法。
定义类
可以使用 class 关键字来定义一个类。以下是一个简单的类的示例:
1 | |
类包含了一个特殊的方法 __init__,它被称为构造函数(Constructor)。构造函数在创建对象时被调用,并用于初始化对象的属性。在构造函数中,self 是一个特殊的参数,它代表实例化后的对象本身。我们可以使用 self 来引用对象的属性和方法。
创建对象
要创建类的实例(即对象),可以像调用函数一样使用类的名称并传递所需的参数。以下是一个创建 Person 类的对象的示例:
1 | |
访问属性和调用方法
要访问对象的属性和调用对象的方法,可以使用点号 . 运算符。示例:
1 | |
继承与多态
在面向对象编程中,继承和多态是重要的概念。继承允许创建一个新的类,它从现有的类派生,并继承其属性和方法。多态允许子类重写父类的方法,以实现不同的行为。
继承
要创建一个继承自另一个类的子类,可以将父类作为子类定义中的参数。示例:
1 | |
这里定义了一个名为 Student 的子类,它继承自 Person 父类。子类重写了父类的构造函数,并添加了一个名为 student_id 的新属性和一个名为 study 的新方法。使用 super() 函数可以在子类中调用父类的方法。
多态
多态允许不同的对象对相同的方法进行不同的实现。示例:
1 | |
在上面的示例中,我们定义了一个名为 introduce 的函数,它接受一个 Person 类的对象作为参数,并调用对象的 greet 方法。通过向 introduce 函数传递不同的对象,可以实现多态行为。