Contents

Python学习

参考资料

切片

对于有序对象,列表和元组,我们如果要获取其中的元素,可以通过下标索引访问
对于字典,我们可以通过 .get 获取对应键的值
对于集合,无法通过下标索引访问,一般我们是把 需要去重 的内容放进集合中

1. 什么时候用切片?

  • 对于有序对象(列表、元组、字符串),要获取多个元素时,用切片比循环更简洁。
  • 字典通过 .keys() / .values() / .items() 遍历,集合主要用于去重,都不支持索引/切片。

2. 切片基本语法

1
序列[start:stop:step]
  • 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
1
2
for x in L:
    print(x)

区别:

  • 可迭代对象(如 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

高阶函数

  • 高阶函数:至少满足下列一个条件的函数:
    1. 接受一个或多个函数作为参数;
    2. 返回一个函数作为结果。
  • 函数式编程就是利用高阶函数进行高度抽象的编程范式。 示例: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

  • key 指定一个函数,作用于每个元素后再比较。
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)

基本语法

1
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

  • 使用 @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

常见用法

  • 固定 intbase,快速转换二进制字符串:
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 的安装和使用(简要)

  • 安装:
1
pip install 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')
  • 自定义异常:继承 Exception 或其子类。
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

  • Python 独有,不能和其它语言交互。
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(),更安全简洁。