python进阶知识
python进阶知识
索引
Python 里的索引(indexing)其实就是告诉程序“我要取第几个元素”。拿 列表、字符串、张量 这些数据结构来说,规则都差不多:
1. 基本索引(正数)
- 从 0 开始计数(不是 1!)
Python基础知识
nums = [10, 20, 30, 40] print(nums[0]) # 第1个元素 → 10 print(nums[2]) # 第3个元素 → 302. 反向索引(负数)
-1代表最后一个,-2倒数第二个
print(nums[-1]) # 倒数第1个 → 40
print(nums[-2]) # 倒数第2个 → 303. 切片(slice)
- 用
start:end取一段,注意:左闭右开(包括 start,不包括 end)
print(nums[1:3]) # 从第2个到第3个 → [20, 30]
print(nums[:2]) # 从开头到第2个(不含索引2)→ [10, 20]
print(nums[2:]) # 从第3个到结尾 → [30, 40]
print(nums[:]) # 全部 → [10, 20, 30, 40]4. 步长
start:end:step,可以隔几个取一个
print(nums[::2]) # 每隔一个取 → [10, 30]
print(nums[1::2]) # 从第2个开始,每隔一个取 → [20, 40]5. 多维索引(比如 PyTorch 张量 / NumPy 数组)
import torch
X = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(X[0]) # 第1行 → [1, 2, 3]
print(X[1, 2]) # 第2行第3列 → 6
print(X[:2, 1:]) # 前两行,取第2列到最后 → [[2,3],[5,6]]一句话总结:
[i]→ 取单个元素[i:j]→ 取一段[i:j:k]→ 取一段并设置步长- 多维数组/张量 → 可以用逗号分隔索引
类Class
直言不讳地说:类是把“数据 + 操作数据的方法”捆在一起的工具。你用得好,它会让程序结构清晰;用得不好,它会制造难以维护的复杂性。下面从最基础讲到高级用法、常见陷阱与实战建议 —— 以务实、可运行的例子为主。
1 基础:如何定义类与实例
class Person:
species = "Homo sapiens" # 类属性(共享)
def __init__(self, name, age):
self.name = name # 实例属性(每个实例自己的)
self.age = age
def greet(self):
return f"Hi, I'm {self.name}, {self.age} years old."
# 使用
p = Person("小明", 20)
print(p.greet()) # Hi, I'm 小明, 20 years old.
print(Person.species) # Homo sapiens要点:
class定义类;__init__是初始化(构造器),第一个参数通常叫self,代表实例。self.xxx是实例属性;直接在类体中定义的是类属性(所有实例共享)。
2 实例属性 vs 类属性、可变默认的陷阱
类属性是所有实例共享的,尤其要注意可变对象(列表、字典):
class C:
shared = [] # 所有实例共享同一个列表
a = C(); b = C()
a.shared.append(1)
print(b.shared) # [1] —— 很容易被误用
# 正确写法(每实例独立)
class D:
def __init__(self):
self.data = [] # 每个实例自己的列表常见 bug 源于把可变对象放在类作用域。
3 方法绑定、函数变成方法的机制(descriptor)
类中的普通函数在被访问时,会变成 bound method(绑定方法),self 由 Python 自动传入:
class A:
def f(self): print("running f", self)
a = A()
print(A.f) # <function A.f at ...>
print(a.f) # <bound method A.f of <__main__.A object at ...>>
a.f() # running f <__main__.A object ...>原因:函数对象实现了描述符协议 (__get__)。从类名访问得到函数,从实例访问得到绑定方法。
4 属性访问的细节:__getattr__ 与 __getattribute__
__getattribute__:任何属性访问都会先调用(慎用,容易导致无限递归)。__getattr__:仅在属性未找到时调用(更安全)。
class X:
def __getattr__(self, name):
# 只在普通查找失败后被调用
return f"没找到 {name}"
x = X()
print(x.some_attr) # 没找到 some_attr5 属性封装:property 与 描述器(Descriptors)
property 是常用的“把方法伪装成属性”的工具:
class Celsius:
def __init__(self, c=0):
self._c = c
@property
def c(self):
return self._c
@c.setter
def c(self, value):
if value < -273.15:
raise ValueError("低于绝对零度")
self._c = value描述器(实现 __get__/__set__/__delete__)更强大,可以复用逻辑:
class Typed:
def __init__(self, name, typ):
self.name = name
self.typ = typ
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, self.typ):
raise TypeError(f"{self.name} 必须是 {self.typ}")
instance.__dict__[self.name] = value
class Person:
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name, age):
self.name = name
self.age = age6 构造与创建:__new__ vs __init__
__new__(cls, ...):负责创建实例并返回实例(用于不可变类型或控制实例创建)。__init__(self, ...):对已有实例做初始化。
单例示例(用__new__控制):
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton(); b = Singleton()
print(a is b) # True注意:__init__ 每次创建实例对象时都会被调用(上例如果想避免重复初始化,需要额外控制)。
7 魔法方法 / 运算符重载(常用)
实现特殊行为或与内建语法配合:
__repr__/__str__:打印/调试显示;__len__、__iter__、__next__:容器/迭代;__getitem__/__setitem__:索引访问;__add__/__radd__/__iadd__:算术;__call__:让实例可调用。
举例:向量类
class Vec:
def __init__(self, x, y):
self.x = x; self.y = y
def __repr__(self):
return f"Vec({self.x},{self.y})"
def __add__(self, other):
return Vec(self.x+other.x, self.y+other.y)
def __len__(self):
return int((self.x**2 + self.y**2)**0.5)
a = Vec(1,2); b = Vec(3,5)
print(a + b) # Vec(4,7)8 继承、覆盖、super() 与 MRO(方法解析顺序)
单继承很直观;多继承要注意 MRO(C3 线性化)和协作式 super()。
class A:
def do(self): print("A")
class B(A):
def do(self):
print("B"); super().do()
class C(A):
def do(self):
print("C"); super().do()
class D(B, C):
def do(self):
print("D"); super().do()
d = D(); d.do()
# 输出:
# D
# B
# C
# A
print(D.mro()) # [D, B, C, A, object]要点:
- 多继承时,所有相关类的实现若使用
super(),会形成一条调用链(协作式继承)。 - 不要在多继承体系中用硬编码父类名去调用父方法(例如直接
A.do(self)),会破坏super()的协作。
9 抽象基类(ABC)与接口样式
使用 abc 模块定义接口(强制子类实现某些方法):
import abc
class Shape(abc.ABC):
@abc.abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, r): self.r=r
def area(self): return 3.14159 * self.r * self.r抽象基类也可以用于类型检查和注册虚拟子类。
10 dataclass:简化数据类(常用于“仅数据”对象)
from dataclasses import dataclass, field, asdict
@dataclass
class Point:
x: int
y: int
tags: list = field(default_factory=list) # 避免可变默认陷阱
p = Point(1, 2)
print(asdict(p)) # {'x': 1, 'y': 2, 'tags': []}优点:自动生成 __init__, __repr__, __eq__(可配置 order=True 等)。常用于数据携带对象。
11 __slots__:节省内存 / 禁止动态属性
class P:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x; self.y = y
p = P(1,2)
# p.z = 3 # 抛出 AttributeError,因为没有 __dict__优点:每个实例不再有 __dict__,内存更小,属性访问略快;缺点:不够灵活(不能动态添加属性),对多重继承需谨慎。
12 元类(metaclass):创建类的“工厂”
元类在类创建时被触发,用于在类定义阶段修改类行为。很多高级框架会用到(ORM、序列化器等)。
简单示例:给每个类自动增加一个属性
class MyMeta(type):
def __new__(mcls, name, bases, namespace):
namespace.setdefault('auto_id', 0)
return super().__new__(mcls, name, bases, namespace)
class A(metaclass=MyMeta):
pass
print(A.auto_id) # 0元类强大但也容易让代码难懂,谨慎使用。
13 设计模式与实用建议(实战角度)
- 优先组合(composition)而非继承(inheritance)。
- 小类、单一职责:每个类只负责一件事。
- 使用
property隐藏实现细节;用 dataclass 简化“仅数据”类型。 - 当需要“接口”时,优先用 duck typing(结构化),必要时用
abc或typing.Protocol。 classmethod适合做备用构造器(alternate constructors);staticmethod是放在类名空间但不需要self/cls的函数。
例:备用构造器
class User:
def __init__(self, name, age): self.name=name; self.age=age
@classmethod
def from_str(cls, s):
name, age = s.split(',')
return cls(name, int(age))
u = User.from_str("alice,30")14 迭代、上下文管理与可调用对象
- 迭代协议:实现
__iter__返回迭代器(实现__next__)。 - 上下文管理器:实现
__enter__/__exit__或使用contextlib.contextmanager。 - 可调用对象:实现
__call__。
例:简单可调用对象
class Adder:
def __init__(self, n): self.n = n
def __call__(self, x): return x + self.n
f = Adder(10)
print(f(5)) # 1515 序列化(Pickle / JSON)与 __getstate__ / __setstate__
- 默认 pickling 会尝试保存对象的
__dict__,若对象有不能序列化的属性(文件句柄、线程等),实现__getstate__/__setstate__自定义序列化行为。 - 对于 JSON,通常把
dataclass或对象转成 dict(手写to_dict()或asdict())再json.dumps。
16 调试与反射工具
内置函数和模块有助调试:
dir(obj)、vars(obj)、getattr(obj, 'name', default)、setattr(obj, 'name', value)、hasattr.inspect模块能看源码、签名、类 MRO 等。- 写好
__repr__(开发时友好)和__str__(用户友好)可以大幅简化调试。
17 常见陷阱汇总(务实清单)
- 可变类属性导致共享状态(最常见)。
- 误用
super()(尤其在多继承中未采用协作式设计)。 - 滥用元类与描述器,让代码难以理解。
- 在
__getattribute__中不小心递归:一定用super().__getattribute__. - 把复杂逻辑塞进
__init__(影响测试、单元化),应把逻辑拆到专门方法或工厂方法中。
18 进阶题材(你可以慢慢学)
- 自定义描述器实现类型检查、延迟加载、缓存(类似
functools.cached_property)。 - 实现一个线程安全的单例(用元类或锁)。
- 写一个支持 MRO 的 mixin 体系(学习
super()的协作)。 - 用元类实现自动注册类(插件系统常用)。
- 深入
importlib、inspect与运行时修改类(动态创建类、热重载)。
19 代码风格与最佳实践(简短清单)
- 尽量保持类小而明确,方法短小。
- 用
dataclass简化数据对象,用常规类实现需要逻辑的方法。 - 文档字符串(
"""...""")写清楚每个类的职责。 - 使用类型注解(
mypy/pyright)提高可维护性。 - 写单元测试覆盖公共 API(尤其是继承时)。
20 示例集合(汇总实战片段)
1)带类型检查的描述器
class Typed:
def __init__(self, name, typ): self.name=name; self.typ=typ
def __get__(self, instance, owner):
if instance is None: return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, self.typ): raise TypeError
instance.__dict__[self.name] = value
class Person:
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name, age):
self.name = name
self.age = age2)协作式多继承示例(正确使用 super())
class Base:
def go(self): print("Base")
class MixinA(Base):
def go(self):
print("MixinA start")
super().go()
print("MixinA end")
class MixinB(Base):
def go(self):
print("MixinB start")
super().go()
print("MixinB end")
class Concrete(MixinA, MixinB):
def go(self):
print("Concrete start")
super().go()
print("Concrete end")
Concrete().go()
# 输出顺序反映 MRO,super() 建立链式调用21 小结(务实一句话)
类是把数据和行为组织起来的强大抽象工具。学会用 property、dataclass、super() 与描述器,避免把可变对象放在类作用域,尽量 favor 组合而非滥用继承。高阶功能(元类、描述器)强而危险:只在确实需要时使用。
22 接下来可以做的练习(实战)
- 用
dataclass写一个Point3D,实现向量加法、点乘、长度计算。 - 写一个
LRUCache类(实现__getitem__、__setitem__),练习魔法方法。 - 用描述器实现一个
Validated字段(类型+范围检查)。 - 实现支持上下文管理的数据库连接包装类(
__enter__/__exit__)。 - 写个示例展示多继承 MRO 与
super()的调用顺序。
如果你希望,我可以把上面的某一节(例如:继承与 MRO / 描述器 / 元类 / dataclass 教程)展开成一个带练习与答案的完整学习单元,或者把示例代码打包成一个可直接运行的笔记。
字符串str
Python 字符串详解(尽可能全面、干净利落、直说无糖衣)
字符串(str)在 Python 里几乎无处不在:用户输入、文件内容、网络数据的文本层面、日志、模板、以及很多数据处理。下面把你需要知道的从基础到常用技巧、性能坑、Unicode 陷阱、常见操作速查,都讲清楚并给出可复制的例子。
1 概念 & 基础语法
- 在 Python 3 中,
str是 Unicode 文本序列,每个元素是一个 Unicode 代码点(code point)。与之相对的是bytes(字节序列)。 - 字符串是 不可变(immutable) 的。修改字符串会产生新对象。
创建字符串:
s1 = '单引号'
s2 = "双引号"
s3 = '''三引号,也可以换行'''
s4 = """三引号也可,常用于 docstring"""
raw = r"原始字符串,反斜杠不转义: \n 没换行" # 注意:原始字符串不能以单个反斜杠结尾转义要点:
\n换行、\t制表、\\反斜杠、\'、\"等。- 原始字符串
r"..."很方便,但不能以单反斜杠结束(语法限制)。
打印:
print("a\nb") # 会换行
print(r"a\nb") # 输出 a\nb2 索引、切片与迭代
字符串像序列,支持索引、切片、迭代。
s = "hello"
s[0] # 'h'
s[-1] # 'o'
s[1:4] # 'ell'
s[:3] # 'hel'
s[::2] # 'hlo' 步长可为负,s[::-1] 用于反转字符串
for ch in s:
print(ch)注意:索引/切片按代码点(code points),不是“人类可见字符(grapheme cluster)”。例如带组合字符或 emoji 时,单个可视字符可能由多个 code points 组成。
3 不可变性:影响与应对
- 不可变意味着每次“修改”都会创建新字符串(
replace、upper、切片等都会返回新对象)。 - 在大量拼接的场景下不要在循环中用
+=(可能导致 O(n²) 的开销)。应使用str.join()、list.append()+join、或io.StringIO。
示例(推荐写法):
parts = []
for item in iterable:
parts.append(process(item))
result = ''.join(parts)4 常用方法速查(精选,实用优先)
下面只列出最常用并配示例。完整 API 请看 help(str)。
- 大小写与测试:
s.lower(), s.upper(), s.title(), s.capitalize(), s.swapcase()
s.casefold() # 更强的大小写折叠,适合做不区分大小写的比较
s.isalpha(), s.isdigit(), s.isnumeric(), s.isdecimal(), s.isalnum(), s.isspace()- 查找/计数/替换:
s.find("sub") # 找到返回 index,找不到返回 -1
s.rfind("x")
s.index("sub") # 找不到抛 ValueError
s.count("a")
s.replace("old", "new", count=-1) # count 控制替换次数- 分割/拼接:
"sep".join(iterable)
s.split(sep=None, maxsplit=-1) # sep=None 时按任意空白分割
s.rsplit(...)
s.splitlines(keepends=False) # 按行分割
s.partition(sep) # 返回 (before, sep, after),找不到时 sep 为空字符串
s.rpartition(sep)- 去空白与填充:
s.strip(), s.lstrip(), s.rstrip()
s.zfill(width) # 用 0 填充到指定宽度
f"{num:0>5}" # 也可用 format 填充(见下)
s.center(width), s.ljust(width), s.rjust(width)- 格式化相关:
"{} {}".format(a, b)
f"{var:.2f}" # f-string(表达式高效、易用)
"%s %d" % ("a", 1) # 旧式,偶尔见到- 编码:
b = s.encode("utf-8") # str -> bytes
s2 = b.decode("utf-8") # bytes -> str- 翻译与映射:
trans = str.maketrans("abc", "123")
s.translate(trans) # 将 'a'->'1' 等
# 也可用于删除: str.maketrans('', '', 'chars_to_delete')- 其他有用的小工具:
s.startswith("pre"), s.endswith("suf")
s.format_map({'a':1}) # 用 dict 填充 format 模板5 字符串格式化(重点:f-strings、format、%)
现代 Python 推荐使用 f-strings(Python 3.6+):
name = "Alice"; age = 30
f"{name} is {age} years"
f"{3.14159:.2f}" # 保留 2 位小数 -> '3.14'
f"{12345:,}" # 千位分隔 -> '12,345'
f"{value:0>8x}" # 用 0 填充到宽度 8,并以 hex 表示(示例)str.format() 更灵活但写法更冗长:
"{name} is {age}".format(name="Bob", age=25)
"{:>10}".format("right") # 右对齐宽度 10
"{:.3f}".format(3.14159) # 浮点格式旧式 %:
"%s %d" % ("a", 3)不要无脑混用,f-strings 可读性最好,且对表达式支持直接写入(例如 f"{(a+b)/2:.1f}")。
f-string 调试语法(Python 3.8+):
x = 5
print(f"{x=}") # 打印 "x=5"要在 f-string 中输出 { 或 },写成 {{ 或 }}。
6 Unicode、编码与对比(常踩雷的部分)
核心点:str 是文本(Unicode);bytes 是原始字节。文本 <-> 字节 需要 显式编码/解码。
常见问题场景:
- 从文件或网络读取文本时没设置或设置错误编码,可能抛
UnicodeDecodeError或出现乱码。 - 在做不区分大小写比较时,应使用
casefold()(比lower()更强)。 - 视觉上相同的字符可能有不同的 Unicode 表示(合成字符 vs 组合字符),比较前可能需要
unicodedata.normalize():
示例(NFC vs NFD):
import unicodedata
s1 = "é" # 可能为单一 code point
s2 = "e\u0301" # e + 组合重音符
s1 == s2 # 可能 False
unicodedata.normalize('NFC', s1) == unicodedata.normalize('NFC', s2) # True大小写折叠示例(德语 ß):
"ß".lower() # 'ß'
"ß".casefold() # 'ss' —— 更适合做无视大小写的比较长度注意:
len(s)返回 code point 数目,不等于“可见字符数”当字符串包含组合字符或 emoji 家族时。
7 bytes 与 str 的关系(什么时候用哪个)
- 存储/网络/二进制协议 → 用
bytes或bytearray。 - 文本处理/显示 → 用
str。 socket.send()、文件以二进制模式写入都需要bytes。
text = "你好"
b = text.encode("utf-8") # bytes
text2 = b.decode("utf-8")bytearray 是可变的,适合需要原地修改字节的场景。
8 性能与最佳实践(务实)
- 多次拼接:用
''.join(list)比在循环里+=更快和更节省内存(虽然 CPython 对少量拼接有优化,但习惯上仍用 join)。 - 大文本缓冲写入:用
io.StringIO或直接写文件逐块写入。 - 避免频繁创建临时字符串(例如在 tight loop 里做大量
s.replace())。 - 如果要频繁格式化很多次,考虑把格式模板预先准备好并复用。
示例:join 比 += 好
# 不推荐:
s = ''
for piece in pieces:
s += piece
# 推荐:
s = ''.join(pieces)9 正则(re)与复杂匹配
常见复杂字符串操作用正则更灵活:
import re
re.findall(r'\d+', text) # 找所有数字段
re.sub(r'\s+', ' ', text).strip()# 把多重空白压缩并去首尾空白
m = re.match(r'(\w+)@(\w+)\.(\w+)', email)re 支持分组、命名分组、回溯引用、非贪婪匹配等。正则是强大工具,但也容易让代码难读:复杂逻辑尽量加注释或拆成小函数。
10 实用“食谱”(常见任务的示例代码)
- 去除开头结尾和多余空白:
s = " hello world \n"
s = ' '.join(s.split()) # 'hello world' —— 压缩中间的多空白- 检查是否回文(忽略大小写和非字母数字):
import re
def is_palindrome(s):
t = re.sub(r'[^0-9A-Za-z]+', '', s).casefold()
return t == t[::-1]- 批量替换多个不同子串(效率高于多次 replace):
# 用正则(如果子串数量大)
import re
patterns = {"cat":"狗", "hello":"hi"}
regex = re.compile("|".join(map(re.escape, patterns.keys())))
result = regex.sub(lambda m: patterns[m.group(0)], text)- 安全地格式化用户输入(避免 format 注入):
from string import Template
t = Template("Hello, $name")
t.safe_substitute({"name": user_input})- 将字符串转换为“数值显示友好”:
val = 1234567.8912
f"{val:,.2f}" # '1,234,567.89'11 常见坑 & 注意事项(务实点)
- 可变类属性:不要把列表/字典作为类属性(共享状态问题)。
- 编码错误:读文件时不指定正确
encoding会出现 UnicodeDecodeError 或乱码。写文件时也明确encoding='utf-8'。 - 比较时的大小写:用
casefold()做不区分大小写的比较,而不是lower()(更安全)。 - 视觉相同但非相同:组合字符(NFD)与合成字符(NFC)比较前要
normalize。 - 原始字符串不能以单反斜杠结尾。
- f-string 表达式会立刻求值(注意不要在其中做昂贵计算或副作用)。
- 正则贪婪/非贪婪:使用
?控制贪婪性,避免匹配过多文本。 - len 与“可见字符数”不同:emoji、旗帜、肤色修饰等可能占多个 code points。
12 小总结速查表(最常用)
- 拼接:
''.join(list) - 切片反转:
s[::-1] - 查找:
s.find(sub)/s.index(sub) - 替换:
s.replace(old, new, count) - 分割:
s.split(sep)/s.splitlines() - 去空白:
s.strip()/' '.join(s.split())(压缩空白) - 格式化:
f"{var:.2f}"/"{:.2f}".format(x) - 编码/解码:
s.encode('utf-8')/b.decode('utf-8') - 大小写比较:
s.casefold()
13 推荐进阶练习(由浅入深)
- 写一个函数,用
translate把字符串里的所有元音替换成*,并保留原始大小写。 - 实现一个
truncate(s, max_len),在不拆分单词的前提下截断并在末尾加…(注意 Unicode 宽度)。 - 比较
lower()与casefold()在多语言文本上的结果,理解差异。 - 写一个小工具,读取一个 UTF-8 文件并统计出现频率最高的 10 个“可见字符”(注意剔除空白和控制字符)。
- 用正则写一个 email 验证器(建议使用简单规则,不必追求 RFC 的完整性)。
这已经很长了,但如果你想把这些内容做成练习题集、备忘速查表(PDF/MD)、或演示 notebook(含可运行例子),我可以把你选的部分打包成一个可运行的脚本或笔记本,带注释和测试用例,直接拿来练手。接下来我会把最常用的 20 个 str 方法做成一张“速查表 + 示例”,如果你同意我就直接生成。
