post / 2023.06.27

Python基础

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。

字符串

  1. 单引号和双引号等价,不区分单字符与字符串(没有单独的 char 类型)。
  2. 三引号可以写多行字符串。
  3. 引号内使用同类引号需要转义 \"。

三种格式化方式

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 遍历的对象。满足下面任一条即可:

  1. 实现了 __iter__(),返回一个迭代器实例;
  2. 没有 __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 的内置特殊方法,不要自己造这种名字。

本文作者: 孤筝

本文标题: Python基础

本文发布时间: 2023-06-27

本文更新时间: 2026-10-05

本文版权: CC BY-NC-SA 4.0

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

搜索