学习日志40 python
1 numpy模块
NumPy(Numerical Python)是 Python 中用于科学计算的基础库,提供了高性能的多维数组对象,以及大量用于数组操作的函数。以下是对它的详细介绍:
1. 核心数据结构 - ndarray
- 定义:ndarray(N-dimensional array object,多维数组对象)是 NumPy 的核心数据结构,它表示一个由相同类型元素组成的多维数组。
- 特点:
- 高效存储:ndarray 在内存中以连续的方式存储数据,这使得对数据的访问和操作非常高效。与 Python 内置的列表相比,ndarray 占用的内存更少,并且在执行数值计算时速度更快。
- 固定数据类型:数组中的所有元素必须具有相同的数据类型,这在创建数组时就需要指定。常见的数据类型包括
int32
、float64
、bool
等。例如,np.array([1, 2, 3], dtype=np.int32)
。 - 多维索引:可以通过整数元组进行多维索引和切片操作,方便地访问和修改数组中的元素。如
arr[0, 1]
表示访问二维数组arr
中第 0 行第 1 列的元素。
2. 创建 ndarray 数组
- 使用 np.array () 函数:可以将 Python 列表或元组转换为 ndarray 数组。例如:
python
运行
import numpy as np
list_data = [1, 2, 3, 4]
arr = np.array(list_data)
- 特殊数组创建函数:
np.zeros(shape)
:创建指定形状的全 0 数组,如np.zeros((3, 4))
创建一个 3 行 4 列的全 0 二维数组。np.ones(shape)
:创建指定形状的全 1 数组,如np.ones((2, 2))
创建一个 2 行 2 列的全 1 二维数组。np.arange(start, stop, step)
:类似于 Python 的range()
函数,创建一个等差数组,如np.arange(0, 10, 2)
生成[0, 2, 4, 6, 8]
。
3. 数组操作
- 算术运算:可以对 ndarray 数组进行各种算术运算,如加、减、乘、除等。这些运算都是按元素进行的。例如:
python
运行
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = arr1 + arr2 # 结果为 [5, 7, 9]
- 广播机制:当对形状不同的数组进行运算时,NumPy 会自动尝试使用广播机制来使运算可行。例如,一个一维数组和一个二维数组可以在满足一定条件下进行运算。
- 索引和切片:支持灵活的索引和切片操作,与 Python 列表类似,但更强大。例如:
python
运行
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr[0, 1]) # 输出 2
print(arr[:, 1]) # 输出 [2, 5],获取第二列
4. 数学函数
NumPy 提供了大量的数学函数,可以直接应用于数组,如三角函数(np.sin()
、np.cos()
等)、指数和对数函数(np.exp()
、np.log()
等)、统计函数(np.mean()
、np.std()
等)。例如:
python
运行
arr = np.array([1, 2, 3])
print(np.mean(arr)) # 输出 2.0,计算数组元素的平均值
5. 线性代数运算
在科学计算和机器学习中,线性代数运算非常常见。NumPy 提供了丰富的线性代数函数,如矩阵乘法(np.dot()
)、求逆矩阵(np.linalg.inv()
)、求行列式(np.linalg.det()
)等。例如:
python
运行
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = np.dot(a, b) # 计算矩阵 a 和 b 的乘积
6. 应用场景
- 科学计算:在物理模拟、数据分析、数值计算等领域,用于处理大规模的数值数据。
- 机器学习和深度学习:是 Scikit-learn、TensorFlow、PyTorch 等机器学习和深度学习库的基础,用于数据预处理、模型训练中的张量运算等。
- 图像处理:可以用来表示和处理图像数据,进行图像滤波、变换等操作。
NumPy 凭借其高效的数组操作和丰富的函数库,极大地简化了 Python 中科学计算的过程,是 Python 科学计算生态系统中不可或缺的一部分。
2 Python中的链式比较3>2>2等价于(3>2) and (2>2)
虽然3>2为True,但2>2为False,因此整体结果为False。
Python 的链式比较是一种简洁的语法,允许在一个表达式中连续使用多个比较运算符(如>
、<
、>=
、<=
、==
、!=
等),用来同时判断多个条件是否成立。其核心逻辑是将多个比较通过and
连接,只有所有条件都满足时,整体结果才为True
。
1. 基本语法与等价逻辑
链式比较的格式为:
a 比较符1 b 比较符2 c 比较符3 ...
它等价于:
(a 比较符1 b) and (b 比较符2 c) and (c 比较符3 ...)
例如:
3 > 2 > 1
等价于(3>2) and (2>1)
,结果为True
(两个条件都满足)。3 > 2 > 2
等价于(3>2) and (2>2)
,结果为False
(第二个条件不满足)。5 == 5 < 6
等价于(5==5) and (5<6)
,结果为True
。
2. 为什么要使用链式比较?
相比拆分成多个and
连接的表达式,链式比较的优势是:
- 更简洁:减少重复变量的书写。例如
x > 0 and x < 10
可以简写为0 < x < 10
。 - 可读性更高:更符合人类的自然语言逻辑(如 “x 大于 0 且小于 10”)。
3. 注意事项
变量只计算一次:在链式比较中,中间的变量(如
a < b < c
中的b
)只会被计算一次,而拆分后的(a<b) and (b<c)
也会计算一次b
,两者效果一致,不会有性能差异。
例:x = 5; 1 < x < 10
中,x
只被读取一次。支持多种比较运算符混合:只要逻辑合理,可混合使用不同运算符。
例:1 <= x < 10
(x 大于等于 1 且小于 10)、a != b == c
(a 不等于 b,且 b 等于 c)。短路逻辑:和
and
一样,链式比较也遵循 “短路原则”—— 如果前面的条件不满足,后面的条件会直接跳过,不再计算。
例:3 > 5 > 2
中,3>5
为False
,直接返回False
,不会再判断5>2
。
4. 常见错误
不要混淆数学中的 “连续区间” 表达:
例如 “x 在 1 到 10 之间” 在数学中写为1 < x < 10
,这在 Python 中是合法的,但其他语言(如 C、Java)中必须写成x>1 && x<10
,否则会报错。避免逻辑矛盾的表达式:
例如3 > 2 < 1
等价于(3>2) and (2<1)
,结果为False
,虽然语法合法,但逻辑上可能不符合预期。
通过链式比较,Python 让条件判断的代码更简洁、直观,尤其适合范围判断场景(如数值区间、年龄范围等)。理解其等价于and
连接的多个条件,就能轻松掌握其用法。
核心就是:代码的链式逻辑准确对应数学上的“同时成立”
只有当 Python 链式比较的逻辑,和数学中 “多个条件必须同时成立” 的逻辑完全对应时,代码输出的结果才会与数学判断一致(为 True)。
可以通过两个具体场景进一步明确这种 “对应关系”:
1. 对应数学 “区间范围”(最典型的 “同时成立”)
数学中描述 “一个数在某两个值之间”,本质就是 “大于左边界 且 小于右边界”(两个条件同时成立),这和链式比较的逻辑完全匹配。
- 数学:
5 < x < 10
(x 大于 5,且 x 小于 10) - 代码:
5 < x < 10
(等价于(5 < x) and (x < 10)
)
只要 x 的取值满足数学上的区间,代码结果就为 True(比如 x=7 时,5<7
和7<10
都成立,整体为 True)。
2. 对应数学 “多个独立条件”(非区间,但需同时满足)
除了区间,数学中也会有 “多个独立条件必须同时满足” 的场景,链式比较同样能对应。
- 数学:
a > b
且b > c
(a 大于 b,同时 b 大于 c,即 a > b > c) - 代码:
a > b > c
(等价于(a > b) and (b > c)
)
比如 a=4、b=3、c=2 时,数学上 4>3>2 成立,代码结果也为 True;若 b=5(a=4 < b=5),则数学上不成立,代码结果为 False。
关键反例:若不对应 “同时成立”,则结果不符
如果数学上需要的是 “任意一个条件成立”(而非 “同时成立”),此时用链式比较就会出错 —— 因为链式比较只认 “and”,不认 “or”。
- 数学:
x < 2
或x > 5
(x 不在 2~5 之间即可,无需两个条件同时满足) - 错误代码:
x < 2 > 5
(等价于(x<2) and (2>5)
)
数学上 x=1 时满足 “x<2”,但代码中2>5
永远是 False,整体结果为 False—— 这就是因为代码逻辑(and)和数学逻辑(or)不对应,导致结果不符。
综上:链式比较是 Python 对数学 “多条件同时成立” 逻辑的一种简洁语法实现。只有当你想表达的数学关系是 “所有条件必须一起满足” 时,用链式比较才能得到和数学判断一致的 True;反之则会出错。
3 capitalize()
方法
在 Python 3 中,capitalize()
方法用于将字符串的第一个字符转换为大写,而将其余字符转换为小写,并且不会修改原字符串,而是返回一个新的字符串。
例如:
python
运行
text = "hello world"
new_text = text.capitalize()print(text) # 输出: hello world (原字符串不变)
print(new_text) # 输出: Hello world (新字符串)
这个方法非常适合处理需要规范化句子首字母大写的场景,同时又能保持原始字符串的完整性。
联想到upper和lower是对全字符操作的,敲了以下代码附上输出。
a = "python"
b = a.capitalize()
print(a)
print(b)
b = a.upper()
print(b)
b = b.lower()
print(b)
输出
python
Python
PYTHON
python
除了capitalize是首字符,我发现这个print是按序施行的,类似于流式管理吗(?不清楚),这里的内存是怎么放的?
测试一下
a = "python"
b = a.capitalize()
print(a)
print(b)
print(id(b))
b = a.upper()
print(b)
print(id(b))
b = b.lower()
print(b)
print(id(b))
输出
python
Python
2202715617680
PYTHON
2202715617728
python
2202715617680
最后的内存地址不变?,值改变,应该是因为只对值操作没有创建新的对象(开辟新的内存空间),应该是这样,一知半解,问问ai我说的对不对。
====================================
你的观察很细致!不过关于内存地址的理解需要稍微修正一下:每次字符串方法调用(capitalize ()、upper ()、lower ())都会创建新的字符串对象,而不是在原有对象上修改。
从你的测试结果来看:
plaintext
python
Python
2202715617680 # b指向capitalize()创建的新对象
PYTHON
2202715617728 # b指向upper()创建的新对象
python
2202715617680 # b指向lower()创建的新对象(恰好与第一个对象地址相同)
这里最后一个id(b)
与第一个相同,并不是因为 "没有创建新对象",而是因为:
- Python 会对短字符串(尤其是纯小写 / 大写的简单字符串)进行缓存(称为 "字符串驻留" 机制)
- 当
b.lower()
生成的 "python" 与最初a
的值相同时,Python 直接复用了已存在的缓存对象 - 这是一种内存优化策略,并非修改原有对象
关键点:
- 字符串在 Python 中是不可变类型,任何修改操作(包括大小写转换)都会产生新对象(划重点)
- 变量
b
只是不断指向新创建的对象,原有对象如果没有被引用会被垃圾回收 - 内存地址相同只是巧合(缓存机制导致),不能认为是 "值改变而对象不变"
可以理解为:变量像一个标签,每次调用字符串方法后,这个标签就从旧对象贴到了新对象上,而不是在旧对象上修改内容。
------------------------------------------------------------------
你对代码执行过程和内存地址变化的观察很深入,其中一些细节可以进一步明确,帮你厘清不确定的地方:
1. 关于 "print 按序施行,类似于流式管理" 的理解
Python 代码的执行是自上而下、顺序执行的,这属于程序的基本执行流程,而非特殊的 "流式管理"。
- 每一行代码按照书写顺序依次执行
print()
函数会在执行到该行时立即输出结果- 变量
b
的值会被后续赋值语句覆盖(如b = a.upper()
会覆盖之前b = a.capitalize()
的结果)
这种顺序执行是绝大多数编程语言的基础特性,确保了代码逻辑的可预测性。
2. 关于内存地址变化的核心原理(修正你的理解)
你的测试中id(b)
出现重复(最后一个地址与第一个相同),并非因为 "没有创建新对象",而是由两个关键机制导致:
(1)字符串是不可变类型
- 任何对字符串的修改操作(包括
capitalize()
/upper()
/lower()
)一定会创建新对象,原字符串始终不变 - 例如
b = a.upper()
中,upper()
会生成全新的 "PYTHON" 字符串,再让b
指向它(原b
指向的 "Python" 对象若无人引用,会被垃圾回收)
(2)字符串驻留(String Interning)机制
- Python 会对符合特定条件的字符串(如纯字母、数字、下划线组成的短字符串)进行缓存,避免重复创建相同内容的对象
- 当
b.lower()
生成 "python" 时,由于这个字符串与最初的a = "python"
内容完全相同,且符合驻留条件,Python 会让新对象直接复用已缓存的 "python" 地址 - 这就是最后一个
id(b)
与第一个id(b)
相同的原因 —— 不是修改旧对象,而是新生成的对象恰好复用了缓存地址
验证:如果字符串不符合驻留条件
python
运行
a = "python!" # 包含特殊字符,不被驻留
b = a.capitalize() # "Python!"
print(id(b))
b = b.lower() # "python!"
print(id(b)) # 两次id会不同,因为不触发驻留
3. 总结关键结论
- 变量
b
的本质:只是一个 "标签",每次赋值都会让它指向新的对象(旧对象若无人引用则被回收) - 对象的不可变性:字符串一旦创建就无法修改,所有 "修改" 都是生成新对象
- 地址重复的原因:是 Python 的缓存优化(字符串驻留),而非 "值变而对象不变"
你的测试方法(用id()
追踪对象)非常好,这是理解 Python 内存管理的有效手段~
4 如何在Python中实现流式处理?
在 Python 中,"流式处理" 通常指对数据进行增量式处理(边产生边处理,而非一次性加载全部数据),适用于大型文件、实时数据流或内存受限的场景。实现方式主要有以下几种:
1. 处理大型文件时的流式读取
对于大文件(如 GB 级日志文件),直接用 read()
会一次性加载到内存,可能导致内存溢出。流式处理通过逐行读取实现:
python
运行
# 流式读取大文件
with open("large_file.txt", "r") as f:for line in f: # 逐行读取,每次只加载一行到内存process_line(line) # 处理单行数据(如解析、过滤等)
原理:文件对象是可迭代对象,迭代时会自动按需加载数据,而非一次性读取全部内容。
2. 使用生成器(Generator)实现自定义流
生成器通过 yield
关键字增量产生数据,每次迭代返回一个值,适合构建自定义数据流:
python
运行
def data_stream():"""生成器:模拟源源不断产生数据的流"""for i in range(1000000):yield i # 每次返回一个值,暂停并保存状态# 流式处理生成器的数据
for data in data_stream():result = process_data(data) # 逐个处理数据if result > 1000: # 可随时中断处理break
优势:生成器本身不存储完整数据,内存占用极低,支持 "产生一个、处理一个" 的模式。
3. 处理实时数据流(如网络数据)
对于网络请求、传感器数据等实时流,可结合异步 I/O 或线程实现持续处理:
python
运行
import socketdef network_stream(host, port):"""流式处理网络数据"""with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.bind((host, port))s.listen()conn, addr = s.accept()with conn:while True:data = conn.recv(1024) # 每次接收1KB数据if not data:breakprocess_network_data(data) # 实时处理
4. 使用专业库处理复杂流
对于需要窗口计算、状态管理的复杂流(如实时统计、ETL 等),可使用专用库:
itertools
:标准库,提供islice
(切片流)、chain
(拼接流)等工具python
运行
from itertools import islice# 从无限流中取前10个元素处理 for item in islice(data_stream(), 10):process(item)
pandas
:处理大型数据集时,可通过chunksize
分块读取 CSV/Excelpython
运行
import pandas as pd# 每次读取1000行数据处理 for chunk in pd.read_csv("large_data.csv", chunksize=1000):process_chunk(chunk)
Apache Kafka + confluent-kafka-python
:处理分布式实时数据流PySpark
:大数据场景下的分布式流式处理
核心思想
流式处理的关键是避免一次性加载全部数据,通过 "产生 - 处理 - 丢弃" 的模式,用有限内存处理无限或海量数据。选择哪种方式取决于数据来源(文件 / 网络 / 数据库)和处理复杂度。
5 sort()与sorted()方法
在 Python 中,sort()
和 sorted()
都用于对数据进行排序,但它们在使用方式、返回值和适用对象上有显著区别:
1. sort()
方法
适用对象:仅用于列表(
list
),是列表的内置方法排序方式:对列表原地排序(直接修改原列表,不会创建新列表)
返回值:返回
None
(因此不能直接赋值给变量)语法:
list.sort(key=None, reverse=False)
key
:用于指定排序依据的函数(如key=str.lower
忽略大小写排序)reverse
:是否反转排序结果(False
升序,True
降序,默认False
)
示例:
python
运行
nums = [3, 1, 4, 2]
result = nums.sort() # 原地排序原列表print(nums) # 输出: [1, 2, 3, 4](原列表已被修改)
print(result) # 输出: None(无返回值)
2. sorted()
函数
适用对象:可对所有可迭代对象排序(列表、元组、字符串、字典等)
排序方式:不修改原对象,而是创建并返回一个新的排序后的列表
返回值:返回排序后的新列表
语法:
sorted(iterable, key=None, reverse=False)
- 参数含义同
sort()
,但第一个参数是待排序的可迭代对象
- 参数含义同
示例:
python
运行
# 对列表排序
nums = [3, 1, 4, 2]
new_nums = sorted(nums) # 创建新列表print(nums) # 输出: [3, 1, 4, 2](原列表不变)
print(new_nums) # 输出: [1, 2, 3, 4](新列表)# 对元组排序(返回列表)
tup = (3, 1, 4, 2)
sorted_tup = sorted(tup)
print(sorted_tup) # 输出: [1, 2, 3, 4](元组被转为列表)# 对字符串排序(按字符ASCII值)
text = "bac"
sorted_text = sorted(text)
print(sorted_text) # 输出: ['a', 'b', 'c']
3. 核心区别总结
特性 | sort() | sorted() |
---|---|---|
适用对象 | 仅列表(list ) | 所有可迭代对象 |
是否修改原对象 | 是(原地排序) | 否(返回新列表) |
返回值 | None | 排序后的新列表 |
内存占用 | 较低(无需创建新对象) | 较高(需创建新对象) |
4. 选择建议
- 若要排序列表且允许修改原列表,用
sort()
(更节省内存) - 若要排序非列表对象(如元组、字典),或需要保留原对象,用
sorted()
- 两者都支持
key
和reverse
参数,可实现复杂排序(如按字典的某个键排序)
复杂排序示例:
python
运行
# 按字典的"age"键排序
people = [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 20}]
sorted_people = sorted(people, key=lambda x: x["age"])
print(sorted_people) # 输出: [{"name": "Alice", "age": 20}, {"name": "Bob", "age": 25}]
自己尝试、测试
list = [1,3,4,2]
list.sort()
print(list.sort())
sorted(list)
print(sorted(list))
输出
None
[1, 2, 3, 4]
你的代码执行结果可以拆解为两个部分来理解,核心原因在于 sort()
和 sorted()
的返回值特性不同:
1. 关于 print(list.sort())
输出 None
list.sort()
是列表的原地排序方法,它会直接修改原列表,但返回值为None
(这是 Python 中所有 "原地修改" 方法的设计规范)。当你执行
print(list.sort())
时,实际上是在打印sort()
方法的返回值,也就是None
。代码执行过程:
python
运行
list = [1,3,4,2] list.sort() # 原列表被修改为 [1,2,3,4],但无返回值 print(list.sort()) # 打印的是第二次调用 sort() 的返回值 None
2. 关于 print(sorted(list))
输出 [1,2,3,4]
sorted(list)
是内置函数,它不会修改原列表,而是返回一个新的排序后的列表。当你执行
print(sorted(list))
时,打印的是这个新生成的排序后列表。代码执行过程:
python
运行
sorted(list) # 对已排序的 list 再次排序,返回新列表 [1,2,3,4](但未赋值给变量) print(sorted(list)) # 打印这个新列表,因此输出 [1,2,3,4]
关键结论
sort()
无返回值(返回None
),仅用于原地修改列表。sorted()
有返回值,返回排序后的新列表,不影响原对象。
如果想打印排序后的列表,正确的写法是:
python
运行
list = [1,3,4,2]
list.sort() # 原地排序
print(list) # 直接打印修改后的原列表,输出 [1,2,3,4]
或者用 sorted()
:
python
运行
list = [1,3,4,2]
print(sorted(list)) # 打印排序后的新列表,输出 [1,2,3,4]