参考资料
切片
对于有序对象,列表和元组,我们如果要获取其中的元素,可以通过下标索引访问
对于字典,我们可以通过 .get 获取对应键的值
对于集合,无法通过下标索引访问,一般我们是把 需要去重 的内容放进集合中
1. 什么时候用切片?
- 对于有序对象(列表、元组、字符串),要获取多个元素时,用切片比循环更简洁。
- 字典通过
.keys() / .values() / .items() 遍历,集合主要用于去重,都不支持索引/切片。
2. 切片基本语法
start:起始索引(包含),默认 0
stop:结束索引(不包含),默认 len(序列)
step:步长,默认 1;不能为 0
核心规则:左闭右开 [start, stop),索引越界会自动截断,不会报 IndexError
3. 常见用法示例
1
2
3
4
5
6
7
8
9
10
11
|
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 取前 3 个
L[:3] # [0, 1, 2]
# 取索引 2~5
L[2:6] # [2, 3, 4, 5]
# 每隔一个取一个
L[::2] # [0, 2, 4, 6, 8]
# 倒数 3 个
L[-3:] # [7, 8, 9]
# 反转序列
L[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
4. 切片赋值与删除(列表专用)
列表支持切片赋值,可以批量修改/删除/插入元素:
1
2
3
4
5
6
7
|
L = [1, 2, 3, 4, 5]
# 替换索引 1~3
L[1:4] = [20, 30, 40] # L → [1, 20, 30, 40, 5]
# 删除前 2 个
L[:2] = [] # L → [30, 40, 5]
# 在索引 2 处插入
L[2:2] = [100] # L → [30, 40, 100, 5]
|
注意:字符串、元组不可变,不支持切片赋值,只能取子串/子元组。
函数
函数的目的
- 将复杂、繁琐、重复性高的代码封装成一个方法。
- 调用时只关心:返回类型、参数个数与顺序,不用关心内部实现细节。
类型判断
1
2
3
4
5
6
7
8
9
10
|
def my_abs(x):
"""
将输入参数转换成非负整数
"""
if not isinstance(x, (int, float)):
raise TypeError('输入的参数类型有误')
if x > 0:
return x
else:
return -x
|
- 使用
isinstance(x, (int, float)) 做类型判断,比 type(x) == int 更适应继承关系。
参数的默认值
不要用可变对象作为参数默认值:
1
2
3
4
5
|
def test(L=[]):
L.append('hello')
return L
print(test()) # ['hello']
print(test()) # ['hello', 'hello']
|
原因:默认值在函数定义时只创建一次,多次调用会共享同一个列表对象,导致结果不符合预期。
推荐写法:
1
2
3
4
5
|
def test(L=None):
if L is None:
L = []
L.append('hello')
return L
|
迭代器
迭代 iteration
- 用
for ... in ... 遍历对象的过程就叫迭代。
- Python 的 for 循环抽象层次比 C/Java 的下标循环更高,可以作用于任何可迭代对象,包括没有下标的 dict。
1
2
3
4
5
6
7
8
9
10
|
d = {'name': 'appleyk', 'age': 26}
# 迭代 key
for k in d:
print(k)
# 迭代 value
for v in d.values():
print(v)
# 迭代 key-value
for k, v in d.items():
print(k, v)
|
可迭代的 Iterable
- 可以被
for 遍历的对象就是可迭代对象,如 list、tuple、str、dict 等。
- 判断方式:
1
2
3
|
from collections.abc import Iterable
isinstance('ABC', Iterable) # True
isinstance(123, Iterable) # False
|
迭代器 Iterator
- 迭代器是实现了迭代器协议的对象:
__iter__() + __next__()。
- 通过
iter() 获取可迭代对象对应的迭代器,用 next() 逐个访问元素,直到抛出 StopIteration。
1
2
3
4
5
6
7
8
|
L = [1, 2.0, '3', 'hello']
L_iter = iter(L)
while True:
try:
x = next(L_iter)
print(x)
except StopIteration:
break
|
- 也可以直接用
for 循环,自动处理 StopIteration:
区别:
- 可迭代对象(如 list)可以多次
for 循环。
- 迭代器(包括生成器)是消耗型,遍历完一次就空了。
列表生成器 / 生成器
列表推导式
1
2
|
tmp1 = [x * x for x in range(1, 11)]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|
生成器表达式
把 [] 换成 (),就得到一个生成器:
1
2
|
tmp2 = (x * x for x in range(1, 11))
# <generator object <genexpr> at ...>
|
- 生成器是惰性求值,只在需要时才生成下一个值,节省内存。
- 生成器也是迭代器的一种,只能遍历一次。
1
2
3
|
for i in tmp2:
print(i) # 正常输出 1, 4, 9, ...
# 第二次遍历不会有输出,因为生成器已被消耗
|
生成器函数(yield)
使用 yield 的函数叫生成器函数,调用时返回生成器对象:
1
2
3
4
5
6
7
8
9
|
def fib(max_n):
n, a, b = 0, 0, 1
while n < max_n:
yield b
a, b = b, a + b
n += 1
f = fib(5)
for x in f:
print(x) # 1, 1, 2, 3, 5
|
高阶函数
- 高阶函数:至少满足下列一个条件的函数:
- 接受一个或多个函数作为参数;
- 返回一个函数作为结果。
- 函数式编程就是利用高阶函数进行高度抽象的编程范式。
示例:
add(x, y, f),其中 f 是函数参数:
1
2
3
4
5
6
7
8
9
10
|
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x > 0:
return x
else:
return -x
def add(x, y, f):
return f(x) + f(y)
print(add(-10, 10, my_abs)) # 20
|
高阶函数(二):map / filter / reduce
map
map(func, Iterable):对可迭代对象每个元素执行 func,返回迭代器。
1
2
3
4
|
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5])
list(r) # [1, 4, 9, 16, 25]
|
filter
filter(func, Iterable):根据 func 返回的布尔值过滤元素,返回迭代器。
1
2
3
|
def is_odd(x):
return x % 2 == 1
list(filter(is_odd, [1, 2, 3, 4, 5])) # [1, 3, 5]
|
reduce
reduce(func, Iterable):对元素累计执行 func,Python3 中在 functools 里。
1
2
3
4
|
from functools import reduce
def add(x, y):
return x + y
reduce(add, [1, 2, 3, 4, 5]) # 15
|
排序函数
list.sort() vs sorted()
list.sort():列表方法,原地排序,返回 None,只适用于列表。
sorted(iterable, key=None, reverse=False):内置函数,返回新列表,适用于任何可迭代对象。
1
2
3
|
a = [3, 1, 4, 2]
a.sort() # a → [1, 2, 3, 4]
b = sorted(a) # b = [1, 2, 3, 4],a 不变
|
key 参数与 lambda
1
2
3
4
5
|
L = [('Kobe', 95), ('James', 90), ('Durant', 98), ('Bob', 85)]
# 按姓名升序
L.sort(key=lambda x: x[0])
# 按成绩降序
L.sort(key=lambda x: x[1], reverse=True)
|
惰性函数(惰性求值)
- 生成器、
map/filter 等返回迭代器的函数,都是惰性求值:只在真正需要时才计算下一个值。
- 优点:节省内存,适合处理大数据流或无限序列。
匿名函数(lambda)
基本语法
- 只能包含一个表达式,自动返回该表达式的值,不能写
return。
示例:
1
2
|
f = lambda x, y: x + y
print(f(1, 2)) # 3
|
常见用法
1
2
3
|
list(map(lambda x: x * x, [1, 2, 3, 4])) # [1, 4, 9, 16]
list(filter(lambda x: x % 2 == 1, range(10))) # [1, 3, 5, 7, 9]
sorted([('Kobe', 95), ('James', 90)], key=lambda x: x[1])
|
- 限制:逻辑复杂时应改用普通
def 函数,不要强行用 lambda。
装饰器
本质
- 装饰器本质是一个接收函数 / 类作为参数并返回新函数 / 类的高阶函数,用于在不修改原对象代码的前提下扩展功能。
最简装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def decorator(func):
def wrapper(*args, **kwargs):
# 前置增强
print('before call')
result = func(*args, **kwargs)
# 后置增强
print('after call')
return result
return wrapper
@decorator
def hello():
print('hello')
hello()
# 输出:
# before call
# hello
# after call
|
@decorator 等价于 hello = decorator(hello)。
带参数的装饰器
需要再包一层函数,用来接收装饰器本身的参数:
1
2
3
4
5
6
7
8
9
10
11
12
|
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hi():
print('hi')
say_hi() # 打印 3 次 hi
|
- 使用
@functools.wraps(func) 防止原函数名、文档等被包装函数覆盖。
1
2
3
4
5
6
7
|
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'call {func.__name__}')
return func(*args, **kwargs)
return wrapper
|
偏函数
概念
functools.partial 的作用:固定原函数的部分参数,返回一个新函数,调用更简单。
示例:
1
2
3
4
5
|
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2) # 固定 exp=2
print(square(5)) # 25
|
常见用法
- 固定
int 的 base,快速转换二进制字符串:
1
2
3
|
from functools import partial
int2 = partial(int, base=2)
print(int2('10010')) # 18
|
模块
- 模块是一个包含 Python 定义和声明的文件(
.py),通过 import 导入。
- 导入方式:
1
2
3
4
|
import module_name
from module_name import func_name
from module_name import * # 不推荐
import module_name as alias
|
__name__ 属性:每个模块都有一个 __name__,直接运行模块时为 '__main__',被导入时为模块名,常用于写测试代码。
pymysql 的安装和使用(简要)
- 基本流程:连接 → 游标 → 执行 SQL → 提交/回滚 → 关闭连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
user='root',
password='123456',
database='test',
charset='utf8mb4'
)
cursor = conn.cursor()
# 查询
cursor.execute('SELECT * FROM users')
rows = cursor.fetchall()
for row in rows:
print(row)
# 插入
cursor.execute('INSERT INTO users(name, age) VALUES(%s, %s)', ('Tom', 20))
conn.commit()
cursor.close()
conn.close()
|
面向对象
类与实例
1
2
3
4
5
6
7
8
|
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi(self):
print(f'Hi, I am {self.name}, {self.age} years old.')
p = Person('appleyk', 26)
p.say_hi()
|
类属性与实例属性
- 类属性:属于类本身,所有实例共享。
- 实例属性:属于每个实例,互不影响。
1
2
3
4
5
6
7
8
|
class Dog:
species = 'Canis familiaris' # 类属性
def __init__(self, name):
self.name = name # 实例属性
d1 = Dog('Buddy')
d2 = Dog('Milo')
print(d1.species) # Canis familiaris
print(d2.species) # Canis familiaris
|
__slots__ 限制实例属性的绑定
- 默认情况下,Python 实例可以动态绑定任意属性。
- 使用
__slots__ 可以限制允许绑定的属性名,节省内存并防止拼写错误。
1
2
3
4
5
6
|
class Student:
__slots__ = ('name', 'age') # 只允许 name 和 age
s = Student()
s.name = 'Tom'
s.age = 20
# s.score = 100 # AttributeError: 'Student' object has no attribute 'score'
|
内置装饰器 @property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('Expected str')
self._name = value
p = Person('appleyk')
print(p.name) # 调用 getter
p.name = 'Tom' # 调用 setter
|
多继承的 Mix-in 混入技术
- Mixin 类:专门用来被多继承的“功能模块”,通常只包含方法,不包含数据属性。
- 作用:组合多个功能,而不是使用深层继承树。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class LogMixin:
def log(self, msg):
print(f'[LOG] {msg}')
class SerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class User(LogMixin, SerializableMixin):
def __init__(self, name):
self.name = name
u = User('appleyk')
u.log('login') # 来自 LogMixin
print(u.to_json()) # 来自 SerializableMixin
|
常规类和定制类
- 定制类常见魔法方法:
__str__ / __repr__:打印/调试时的字符串表示。
__iter__ / __next__:让对象可迭代。
__getitem__:支持索引 / 切片访问。
__len__:支持 len()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class MyList:
def __init__(self, *args):
self.items = list(args)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __iter__(self):
return iter(self.items)
ml = MyList(1, 2, 3)
print(len(ml)) # 3
print(ml[0]) # 1
for x in ml:
print(x)
|
枚举类型
- 使用
enum 模块定义枚举类,更安全地表示有限状态集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from enum import Enum, unique
@unique
class Weekday(Enum):
Mon = 0
Tue = 1
Wed = 2
Thu = 3
Fri = 4
Sat = 5
Sun = 6
day = Weekday.Mon
print(day) # Weekday.Mon
print(day.value) # 0
|
利用二维数组解决平面图形 M 的打印
- 思路:把图形看作二维网格,每个位置用字符或数字表示,用嵌套列表存储,再用循环打印。
示例:打印一个简单的“M”图案:
1
2
3
4
5
6
7
8
9
|
M = [
' ** ** ',
' * * * ',
' * * ',
' * * ',
' * * ',
]
for row in M:
print(row)
|
- 更一般的做法:根据行数、列数和规则生成二维数组,再输出。
异常处理
1
2
3
4
5
6
7
8
9
10
11
|
try:
# 可能出错的代码
r = 10 / 0
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
except TypeError as e:
print('TypeError:', e)
else:
print('No error')
finally:
print('Finally always executed')
|
1
2
3
4
5
6
|
class MyError(Exception):
pass
try:
raise MyError('custom error')
except MyError as e:
print('MyError:', e)
|
读写文件
文本文件
1
2
3
4
5
6
7
8
9
|
# 读
with open('test.txt', 'r', encoding='utf-8') as f:
text = f.read()
# 或逐行
for line in f:
print(line, end='')
# 写
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('hello\n')
|
二进制文件
1
2
3
4
|
with open('test.bin', 'rb') as f:
data = f.read()
with open('test.bin', 'wb') as f:
f.write(b'\x00\x01\x02')
|
文件和目录操作
os / os.path / shutil 常用接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import os
import shutil
# 路径操作
os.path.join('dir', 'file.txt')
os.path.abspath('.')
os.path.isdir('/path/to/dir')
os.path.isfile('/path/to/file')
# 目录操作
os.mkdir('new_dir')
os.makedirs('a/b/c') # 递归创建
os.rmdir('empty_dir')
# 文件操作
shutil.copy('src.txt', 'dst.txt')
shutil.move('old.txt', 'new.txt')
os.remove('file.txt')
|
序列化和反序列化
我们简单地执行两个数字相加的运算,然后输出结果
程序结束之后,假设我们还想知道这个结果,尝试打印出来
但是发现已经访问不到了
我们以为运行结果还在内存中,但是内存毕竟有限
程序一旦结束,内存就会把有关的一切,全部清掉
所以只能在内存中重新分配地址,重新计算,重新赋值
有没有其他的办法,可以让运行结果,一次运行,处处调用且不再考虑内存地址的重新分配呢?
借助序列化将变量或者对象转换成字节流存储在文件中
这样我们需要它的时候就能从磁盘中读取文件内容
序列化概念
- 序列化:把变量或对象转成可存储/传输的字节流;
- 反序列化:从字节流恢复成对象。
pickle
1
2
3
4
5
6
7
8
9
|
import pickle
d = {'name': 'Tom', 'age': 20}
# 序列化到文件
with open('test.txt', 'wb') as f:
pickle.dump(d, f)
# 从文件反序列化
with open('test.txt', 'rb') as f:
d2 = pickle.load(f)
print(d2)
|
json
1
2
3
4
5
6
7
8
9
|
import json
d = {'name': 'Tom', 'age': 20}
# 序列化到文件
with open('test.json', 'w', encoding='utf-8') as f:
json.dump(d, f)
# 从文件反序列化
with open('test.json', 'r', encoding='utf-8') as f:
d2 = json.load(f)
print(d2)
|
注意写入和读取方式不需要是 b 二进制的
多线程
threading 和 Lock
- Python3 推荐使用
threading 模块,而不是底层的 _thread。
- GIL(全局解释器锁):同一时刻只有一个线程执行 Python 字节码,多线程适合 I/O 密集型任务。
示例:两个窗口卖纪念品,使用锁保证库存安全:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import threading
import random
import time
souvenirs = 10
lock = threading.Lock()
def win(name):
global souvenirs
while souvenirs > 0:
lock.acquire()
try:
if souvenirs > 0:
time.sleep(random.randint(1, 2))
souvenirs -= 1
print(f'窗口{name}售出一份纪念品,剩余{souvenirs}份')
else:
print('纪念品已售罄')
break
finally:
lock.release()
if __name__ == '__main__':
print(f'纪念品共有{souvenirs}份')
t1 = threading.Thread(target=win, args=('1',))
t2 = threading.Thread(target=win, args=('2',))
t1.start()
t2.start()
t1.join()
t2.join()
|
- 推荐写法:用
with lock: 代替 acquire()/release(),更安全简洁。