post / 2023.06.27
Python基础
本文合并了原先的四篇 Python 笔记,去掉了重复与无关内容,作为日常速查使用。示例基于 Python 3。
变量与对象
- 变量不需要声明类型,但使用前必须赋值,否则抛
NameError。 - Python 没有
const,也没有任何机制阻止变量被重新赋值,所谓"常量"只是约定用全大写命名。 =是赋值,==是比较。
>>> age
NameError: name 'age' is not defined
面向对象的理解方式:对象是数据实体,变量是贴在对象上的标签。
a = "Jack" # 创建字符串对象 "Jack",再把标签 a 贴上去
b = a # 把标签 b 贴到同一个对象上,而不是复制
id(a) == id(b) # True,两者指向同一个对象
a = "Ross" # 换一个对象,贴上 a 标签;b 仍指向 "Jack"
- 对象可以挂多个标签,一个变量同时只能指向一个对象。
- 变量本身没有类型,类型属于对象。所以
a = 1之后a = "1"完全合法。
赋值与切片
= 是右结合的,a = b = c = 1 表示三者都指向整数对象 1。
a, b, c = 1, 2, 3 # 分别赋值
age = 17 + 1 # 先算右边再赋值
切片适用于字符串、元组、列表,语法为 obj[a:b:c]:
| 写法 | 含义 |
|---|---|
obj[a:b] | 取下标 $[a, b)$,不含 b;省略 a 从 0 开始,省略 b 到末尾 |
obj[a:b:c] | 每 c 个取一个,c 省略时为 1 |
c 为负数 | 从前向后反向取 |
obj[::-1] | 反转(得到新对象,原对象不变) |
>>> name = "polaris"
>>> name[1:3]
'ol'
>>> name[::-3]
'sap'
>>> name[::-1]
'siralop'
切片还可用于复制:y = x[:] 得到一份浅拷贝,再对 y 排序不会影响 x。
字符串
- 单引号和双引号等价,不区分单字符与字符串(没有单独的 char 类型)。
- 三引号可以写多行字符串。
- 引号内使用同类引号需要转义
\"。
三种格式化方式
name, age = "布洛妮娅", 18
print('My wife is %s, she is %d.' % (name, age)) # % 方法,最古老
print('My wife is {}, she is {}.'.format(name, age)) # format 方法
print(f'My wife is {name}, she is {age}.') # f-string,推荐
%方法后面接 tuple 或 dict,按顺序或按名字取值。format的{}内可用下标{0}或关键字{name}。- f-string 需要 Python 3.6+,可读性最好;三者都支持宽度、对齐、精度等格式控制。
数字与布尔
| 类型 | 说明 |
|---|---|
int | 整数,支持 0x 十六进制、0o 八进制、0b 二进制 |
float | 浮点数,支持科学计数法 1.23e-6 |
complex | 复数,写作 a + bj 或 complex(a, b) |
bool | 布尔值,True 本质是 1,False 本质是 0 |
NoneType | 空值,None 的首字母必须大写 |
- 整数与浮点数混合运算时,整数会被提升为浮点数:
1 + 2.0得3.0。 - 浮点数存在精度误差:
0.1 + 0.2 == 0.3结果是False(实际为0.30000000000000004)。需要精确计算时用decimal模块。 round()采用银行家舍入(四舍六入五取偶):round(0.5)得0,round(1.5)得2,round(2.5)得2。
假值:0、0.0、None、空字符串、空列表、空元组、空字典、空集合、False。
真值:其他一切,包括非空字符串(哪怕内容是 "False")。
运算符
算术运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
+ - * | 加减乘 | |
/ | 除法,结果一定是浮点数 | 6 / 3 → 2.0 |
// | 整除,向下取整 | 7 // 2 → 3,-7 // 2 → -4 |
% | 取余(模) | 7 % 2 → 1 |
** | 幂,右结合 | 2 ** 2 ** 3 → 256 |
- | 一元负号,优先级低于 ** | -2 ** 2 → -4 |
其他运算符
| 类别 | 运算符 |
|---|---|
| 比较 | == != < <= > >=,可以连用:a > b == c 等价于 a > b and b == c |
| 逻辑 | and or not |
| 成员 | in not in |
| 身份 | is is not,比较是否引用同一对象(比较 id,不是比较值) |
| 位运算 | & | ^ ~ << >> |
| 复合赋值 | += -= *= //= **= &= |= ^= <<= >>= |
and / or 返回的是操作数本身,不是 True / False:
>>> True and 0 + 3
3
>>> False or 31
31
>>> 0 or 0
0
优先级(由高到低)
() → 索引 [] → 属性 . → ** → 一元 ~ + - → * / % // → + - → << >> → & → ^ → | → 比较(< == in is 等同一级)→ not → and → or → 赋值
记不住就加括号,代价最低。
输入输出与内置函数
print(self, *args, sep=' ', end='\n', file=None) # sep 分隔符,end 结尾字符
age = input("请输入年龄:") # 返回值永远是字符串
int(age) # 需要数字时显式转换
常用内置函数:
| 函数 | 作用 |
|---|---|
id(x) | 对象的内存地址(身份标识) |
type(x) | 对象的类型,如 <class 'int'> |
len(x) | 元素个数 |
abs() round() bool() | 绝对值、四舍五入、布尔转换 |
enumerate(seq, start=0) | 循环时同时取下标和值 |
range() zip() sum() max() min() sorted() | 常用序列工具 |
类型转换函数:int(x, base)、float()、complex()、str()、repr()、chr()、ord()、hex()、oct()、eval()、list()、tuple()、set()、frozenset()、dict()。
转换不是无条件的,取决于原数据是否含有足以表达目标类型的信息:int("123") 可行,int("Hello") 会报错;列表可以转集合,但转不成一个数字。
隐式转换遵循精度顺序:bool < int < float < complex,混合运算时向高精度靠拢。
galgames = ['咖啡馆', '千恋万花', '天使骚骚']
for index, galgame in enumerate(galgames, 1):
print(f'今天玩第{index}部作品{galgame}。')
判断与循环
if condition1:
...
elif condition2:
...
else:
...
for 用于遍历任何可迭代对象(列表、字典、字符串、元组、生成器……):
for x in 序列:
...
for i in range(5):
...
break:跳出当前这一层循环。continue:跳过本次剩余部分,进入下一次循环。for ... else/while ... else:循环正常结束时执行else;被break中断则不执行。
while 判断条件:
...
写 while 时务必确认循环体最终能改变判断条件或触发 break,否则会死循环。
四种容器
| 容器 | 字面量 | 有序 | 可变 | 元素可重复 | 元素要求 |
|---|---|---|---|---|---|
列表 list | [1, 2] | 是 | 是 | 是 | 任意 |
元组 tuple | (1, 2) | 是 | 否 | 是 | 任意 |
字典 dict | {k: v} | 插入序 | 是 | 键不可重复 | 键必须可哈希 |
集合 set | {1, 2} | 否 | 是 | 否(自动去重) | 必须可哈希 |
list
a = [] # 空列表
a.append(x) # 末尾追加
a.insert(i, x) # 在下标 i 处插入,原元素后移
a.extend(b) # 把 b 的元素逐个接到 a 后面
a.pop() # 弹出末尾元素(返回该元素)
a.pop(i) # 弹出下标 i 的元素
a.remove(x) # 删除第一个值为 x 的元素,不存在则报错
a.index(x) # 第一个值为 x 的下标
a.count(x) # 值为 x 的元素个数
a.reverse() # 原地反转
a.sort(key=..., reverse=False) # 原地排序,无返回值
del a[i] # 删除下标 i
del a[1:3] # 删除切片范围
排序示例:
random = [(2, 2), (3, 4), (4, 1), (1, 3)]
random.sort(key=lambda e: e[1]) # 按每个元组的第二个元素排序
# [(4, 1), (2, 2), (1, 3), (3, 4)]
注意:
sort()只有key和reverse两个参数,cmp参数在 Python 3 中已被移除。
tuple
- 元组创建后不可修改,没有增删改。
- 单元素元组必须带逗号:
(1,)是元组,(1)只是整数。 - 转换:
list(t)得到列表,tuple(l)得到元组,原对象不变,返回的是新对象。
dict
profile = {'name': '孤筝', 'age': 19}
profile2 = dict(name='孤筝', age=19) # 用等号时键不带引号
profile3 = dict([('name', '孤筝'), ('age', 19)]) # 由键值对序列构造
profile4 = {i: i ** 2 for i in range(2, 5)} # {2: 4, 3: 9, 4: 16}
取值与增改:
profile['name'] # 键不存在时抛 KeyError
profile.get('gender') # 不存在返回 None
profile.get('gender', 'male') # 不存在返回默认值
profile.setdefault('gender', 'male') # 不存在则写入该键值对并返回
profile['gender'] = 'male' # 新增或修改
del profile['age']
profile.pop('age')
关键区别:
get()只读,不会改动字典;只有setdefault()会在键缺失时写入。
判断键是否存在用 in / not in;has_key() 是 Python 2 的写法,Python 3 已删除。
set
s = {1, 2, 2, 3} # 自动去重为 {1, 2, 3}
s = set() # 空集合只能这样写,{} 是空字典
| 操作 | 方法 | 运算符 |
|---|---|---|
| 并集 | a.union(b) | a | b |
| 交集 | a.intersection(b) | a & b |
| 差集(在 a 不在 b) | a.difference(b) | a - b |
| 对称差(只属于其中一个) | a.symmetric_difference(b) | a ^ b |
- 增:
add(x)(只能加不可变类型)、update(seq)(可接字符串、列表、元组、集合;接字典只取其键)。 - 删:
remove(x)元素不存在会报错;discard(x)不会报错;pop()删除并返回任意一个元素,不能传参;clear()清空。 - 判断:
x in s、a.isdisjoint(b)(无交集返回 True)、b.issubset(a)。 - 集合无序无索引,既不能按下标取值,也不能修改元素,只能增删。
常见误解:
pop()删的是"任意"元素,不是"随机"元素。集合的存储顺序由哈希值决定,看起来随机,但并不可控。
迭代器与生成器
可迭代对象:能用 for 遍历的对象。满足下面任一条即可:
- 实现了
__iter__(),返回一个迭代器实例; - 没有
__iter__(),但实现了__getitem__()且按下标取元素可行。
迭代器对象:iter(可迭代对象) 的返回值,用 next() 逐个取值,取完后抛 StopIteration。
from collections.abc import Iterable, Iterator
isinstance([], Iterable) # True,列表是可迭代对象
isinstance([], Iterator) # False,但列表本身不是迭代器
isinstance(iter([]), Iterator) # True
也就是说:列表是可迭代对象,不是迭代器,一个列表可以反复遍历;而迭代器只能往前走一次。
生成器是"用函数写的迭代器":
# 列表推导式得到列表
alist = [i for i in range(5)] # [0, 1, 2, 3, 4]
# 换成圆括号得到生成器
agen = (i for i in range(5)) # <generator object ...>,惰性求值
注意:
(i for i in ...)是生成器表达式,不是元组推导式——Python 里没有元组推导式。要得到元组请写tuple(i for i in ...)。
yield 与 return 的区别:函数执行到 yield 会暂停并交出值,但函数没有结束;下次取值时从暂停处继续,直到函数结束。yield 不带值则返回 None。
def generator():
i = 0
while i < 5:
i += 1
yield i
gen = generator()
next(gen) # 1
for v in generator():
print(v) # 1 2 3 4 5
生成器的价值在于惰性:不会一次性把全部结果算出来放进内存,处理大数据流时比列表更省内存。
其他笔记
注释
# 单行注释
'''
三引号严格来说是"多行字符串"而不是注释:
它会被解释器当作一个字符串对象创建出来,只是一个未被使用的表达式。
'''
正因为它本质是字符串而非注释,所以在代码中间随意插入三引号会破坏语法,可能直接报错。规范做法是只用 # 写注释。
编码格式
- Python 3 源文件默认就是 UTF-8,通常不需要声明编码。
- 只有 Python 2 需要文件头声明,现在基本用不到:
# -*- coding: UTF-8 -*-
标识符
- 由字母、数字、下划线组成,不能用数字开头,区分大小写。
- Python 3 允许用中文命名,但不建议。
- 下划线的特殊约定:
_name:单下划线开头,表示"内部使用",from module import *不会导入。__age:双下划线开头,触发名称改写(name mangling),用于避免子类冲突。__init__:双下划线开头和结尾,是 Python 的内置特殊方法,不要自己造这种名字。
赞助 / 我一个人打车六七十回家!


何时一樽酒,重与细论文。