Contents

Python

Contents

numpy

pandas

matplotlib

scikit-learn

scipy

教程

Python3 教程 | 菜鸟教程

Python教程

深浅复制

  • **直接赋值:**其实就是对象的引用(别名)。
  • **浅拷贝(copy):**拷贝父对象,不会拷贝对象的内部的子对象。
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

形象理解:

大箱子(容器)里面嵌套了许多小箱子,小箱子里面又有小箱子,最内部箱子里面有值

  • 直接赋值:给最外部大箱子重新贴了一个tag
  • 浅拷贝(copy):重新换了一个外部大箱子
  • 深拷贝(deepcopy):全部箱子对应新换,值不变

一句话核心区别:

引用就是用别名,浅拷贝是复制一层壳,深拷贝是复制全套内外。

Python 字典(Dictionary) copy()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
dict1 =  {'user':'runoob','num':[1,2,3]}
 
dict2 = dict1          # 浅拷贝: 引用对象
dict3 = dict1.copy()   # 浅拷贝:深拷贝父对象(一级目录),子对象(二级目录)不拷贝,还是引用
 
# 修改 data 数据
dict1['user']='root'
dict1['num'].remove(1)
 
# 输出结果
print(dict1)
print(dict2)
print(dict3)

Python 直接赋值、浅拷贝和深度拷贝解析 | 菜鸟教程

Python-copy()与deepcopy()区别

f-string 格式化

Python格式化字符串f-string概览_sunxb10的博客-CSDN博客_f string

语法:

{content:format}

进制转换

Python 二进制,十进制,十六进制转换_kunpengku的博客-CSDN博客_python 二进制转十进制

转十进制:int(x,[base])

转二进制:bin()、

转8进制:oct()

转16进制:hex()

Python 十进制转二进制、八进制、十六进制

内置函数:

https://www.runoob.com/python3/python3-built-in-functions.html

https://www.runoob.com/python3/python3-built-in-functions.html

任意键继续/暂停

python程序里,按任意键继续

1
2
3
4
5
6
# windows
import os
os.system('pause')

# linux
input('Press any key to continue...')

None值

python 中None,is和==的深入探讨

is表示的是对象标识符,用来检查对象的标识符是否一致,即两个对象在内存中的地址是否一致。在使用 a is b 的时候,相当于id(a)==id(b)

因为None在Python里是个单例对象,一个变量如果是None,它一定和None指向同一个内存地址。

逻辑判断

[Python]万物皆可Bool——Python的隐式布尔值和真值测试

Python提供了一个简单有效的真值测试机制,来得到一切对象的隐式布尔值。该机制借助两个魔术方法__bool____len__,如果我们希望给自定义的类赋予符合一定逻辑的布尔值,可以通过定义这两个魔术方法来完成。Python也提供了一个内置函数bool,使得对象的隐式布尔值可以通过该函数显式计算得到,而不一定只能用于ifwhile等过程。

任何对象都可以在ifwhile语句或andor等布尔操作符中进行真值测试(Truth Value Testing),测试的结果默认是True,除非一下两种情况的任何一种发生:

  • 该对象的类定义了__bool__函数,且该函数返回False
  • 该对象的类定义了__len__函数,且该函数返回0

另外,以下内置对象被认为是False:

  • 常量NoneFalse
  • 值为0的数值类型:00.00jDecimal(0)Feaction(0, 1)
  • 空的序列和容器类型:''()[]{}set()range(0)

Python true(真) 和 false(假)判断

类型FalseTrue
布尔False(与0等价)True(与1等价)
数值0,   0.0非零的数值
字符串‘’,  “"(空字符串)非空字符串
容器[],  (),  {},  set()至少有一个元素的容器对象
NoneNone非None对象

时间处理

Python时间日期格式化之time与datetime模块总结 - 奥辰 - 博客园

python记录程序运行时间的三种方法-CSDN博客

1
2
1time.time()
2datetime.now()

计时器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import functools
from datetime import datetime

def timer(func):
    # print(f'{timer.__name__} is a func decorator.')

    @functools.wraps(func)  # 保留func函数元信息
    def warpper(*args, **kwargs):
        start = datetime.now()
        print(f'start {start}')
        # print(func.__doc__) # 接口文档信息
        ret = func(*args, **kwargs)
        end = datetime.now()
        print(f'end {end}')
        print(f'run {func.__name__} cost {end - start}')
        return ret

    return warpper

glob

https://rgb-24bit.github.io/blog/2018/glob.html

1
2
3
4
import glob

pathname = os.path.join(src, '*.txt')
files = glob.glob(pathname)

排序

1
2
3
# 相同前缀,只按数字部分进行排序
import re
sorted(dirs, key=lambda x: int(re.search(r'\d+$', x).group()))

序列类型

python中不是所有的容器都叫序列

range to list

1
2
3
4
a = range(10)
b = list(a)
c = [*a]
d = [*a,x]

列表list

转字符串

在 Python 中将 列表转换为字符串 是常见操作,下面是各种方法的总结(适用于不同场景)👇


✅ 方法 1:join() — 最常用方式(适用于字符串列表)

1
2
3
lst = ['apple', 'banana', 'cherry']
result = ', '.join(lst)
print(result)  # 输出: apple, banana, cherry

🔹说明:

  • join() 只能用于字符串组成的列表
  • 可自定义分隔符(如 ', ', '\n', '' 等)。

✅ 方法 2:map() + join() — 用于非字符串列表

1
2
3
lst = [1, 2, 3]
result = ','.join(map(str, lst))
print(result)  # 输出: 1,2,3

🔹说明:

  • map(str, lst) 把列表中所有元素转成字符串。
  • 比直接遍历更优雅、性能更好。

✅ 方法 3:str() 直接转(带括号和逗号)

1
2
3
lst = ['a', 'b', 'c']
result = str(lst)
print(result)  # 输出: ['a', 'b', 'c']

🔹说明:

  • 不推荐用于拼接字符串,仅用于查看或调试。
  • 输出带有中括号 [] 和引号 'a'

✅ 方法 4:list comprehension 自定义格式化

1
2
3
lst = [1, 2, 3]
result = ' | '.join([f'item:{x}' for x in lst])
print(result)  # 输出: item:1 | item:2 | item:3

🎯 总结对比:

方法适用类型输出格式推荐程度
', '.join(lst)字符串列表无括号✅✅✅
', '.join(map(str, lst))任意列表无括号✅✅✅
str(lst)任意列表有括号⚠️ 一般
列表推导式 + join任意列表自定义格式✅✅

列表生成式

1
2
3
4
5
print([i for i in range(10) if i%2 == 0])
print([i if i == 0 else 100 for i in range(10)])
 
[0, 2, 4, 6, 8]
[0, 100, 100, 100, 100, 100, 100, 100, 100, 100]

Python列表解析配合if else_python 列表生成式 if else-CSDN博客

字典

创建字典

python创建字典(dict)的几种方法(详细版)_python如何构建字典-CSDN博客

python字典的键值对互换 - Little_Raccoon - 博客园

1
2
d1 = {'a':1,'b':2,'c':3}
d2 = {key:value for v,k in d1.items()}

星号*用法

https://www.jianshu.com/p/a159ea1dbccc

*位置参数

**关键字参数

调用函数时使用* **

test(args) 的作用其实就是把序列 args 中的每个元素,当作位置参数传进去。比如上面这个代码,如果 args 等于 (1,2,3) ,那么这个代码就等价于 test(1, 2, 3) 。

test(kwargs) 的作用则是把字典 kwargs 变成关键字参数传递。比如上面这个代码,如果 kwargs 等于 {‘a’:1,‘b’:2,‘c’:3} ,那这个代码就等价于 test(a=1,b=2,c=3) 。

print打印

换行打印

print(*sys.path, sep='\n')

print('\n'.join(files))

实时缓冲问题

这种现象通常是由于 Python 解释器的缓冲机制所导致的。在 PyCharm 里,它或许会对输出进行特殊处理,从而让日志能够实时显示;而在终端中,Python 解释器默认采用行缓冲或者块缓冲,这就使得日志要等到缓冲区满或者程序结束时才会被打印出来。下面为你介绍几种解决办法。

方法一:在脚本中设置 stdout 为无缓冲模式

你可以在 Python 脚本里把 sys.stdout 设置为无缓冲模式,这样就能让日志实时输出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import sys
import time

# 设置 stdout 为无缓冲模式
sys.stdout.reconfigure(buffer=0)
sys.stdout.flush() # or this
sys.stdout.reconfigure(line_buffering=True)  # Python 3.7+

for i in range(5):
    print(f"这是第 {i + 1} 条日志")
    time.sleep(1)
    
# or this
print("Logging info...", flush=True) # 每句都要设置,不方便

方法二:使用 u 选项运行 Python 脚本

在调用 subprocess.Popen() 时,使用 -u 选项来运行 Python 脚本,这个选项的作用是强制使用无缓冲的标准输出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import subprocess

# 使用 -u 选项运行 Python 脚本
process = subprocess.Popen(['python', '-u', 'your_script.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

# 实时读取并打印日志
for line in process.stdout:
    print(line, end='')

# 等待进程结束
process.wait()

# or this
process = subprocess.Popen(['python', 'my_script.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
process = subprocess.Popen(['unbuffer', 'python', 'my_script.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process = subprocess.Popen(['stdbuf', '-oL', 'python', 'my_script.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

方法三:在终端中设置环境变量

在终端里设置 PYTHONUNBUFFERED 环境变量为 1,这样可以让 Python 解释器使用无缓冲的标准输出。

1
2
export PYTHONUNBUFFERED=1
python your_script.py

代码解释

  • 方法一:通过 sys.stdout.reconfigure(buffer=0)stdout 设置为无缓冲模式,保证每次调用 print() 函数时日志都能立即输出。
  • 方法二:在 subprocess.Popen() 中使用 u 选项,让 Python 解释器以无缓冲模式运行脚本,然后实时读取并打印脚本的输出。
  • 方法三:设置 PYTHONUNBUFFERED 环境变量为 1,全局生效,使得所有 Python 脚本都以无缓冲模式运行。

你可以根据具体情况选择合适的方法来解决日志不能实时输出的问题。

argparse

常用参数选项

  • type:指定参数类型(如 int, float, str 等)。
  • default:指定默认值。
  • help:参数的帮助信息。
  • required:是否必须提供(针对可选参数)。required=True
  • choices:限制参数的取值范围。choices=['train', 'test']
  • nargs:指定参数的数量(如 ?最多一个, *不限, +最少一个 等)。
  • action:指定参数的行为(如 store_true, store_false,出现就选取默认值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import argparse

# 1. 创建 ArgumentParser 对象
parser = argparse.ArgumentParser(description="这是一个示例程序")

# 2. 添加参数
parser.add_argument('name', type=str, help="你的名字") # 位置参数
parser.add_argument('-a', '--age', type=int, help="你的年龄", default=18) # 可选参数
parser.add_argument('-v', '--verbose', action='store_true', help="是否显示详细信息")

# 3. 解析命令行参数
args = parser.parse_args()

# 4. 使用解析后的参数
print(f"你好, {args.name}!")
if args.verbose:
    print(f"年龄: {args.age}")

RawTextHelpFormatter

1. 为什么使用 RawTextHelpFormatter

  • 保留格式:默认的 HelpFormatter 会自动换行并调整文本格式,而 RawTextHelpFormatter 会保留帮助文本的原始格式。
  • 复杂帮助信息:当帮助信息包含多行文本、表格、代码块等复杂内容时,使用 RawTextHelpFormatter 可以更好地控制显示效果。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import argparse

# 使用 RawTextHelpFormatter
parser = argparse.ArgumentParser(
    description='这是一个示例程序\n\n'
                '详细信息:\n'
                '  - 第一行\n'
                '  - 第二行\n'
                '  - 第三行',
    formatter_class=argparse.RawTextHelpFormatter
)

# 添加参数
# 以为一个选项同时指定长选项和短选项
parser.add_argument('name', help="你的名字")
parser.add_argument('-a', '--age', type=int, help="你的年龄\n(默认值: 18)", default=18)

# 解析参数
args = parser.parse_args()

# 使用参数
print(f"你好, {args.name}!")
print(f"年龄: {args.age}")

迭代器、生成器

迭代器是一种按照从前向后的顺序依次访问元素的对象,访问时不能回退;
生成器是一个返回迭代器的函数,包含yield关键字,可以在迭代过程中逐步产生值,其遇到yield语句会暂停并return当前结果,函数会从上次暂停的地方继续执行。

迭代器

迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()

生成器

生成器是 Python 中的一种特殊类型的迭代器,允许你使用 yield 关键字来逐步生成值,而不是一次性返回所有值。这使得生成器在处理大数据集时非常高效,因为它们可以按需生成数据,而不是在内存中存储所有数据。

生成器的基本特性

  1. 延迟计算:生成器的值在需要时才计算,避免了一次性占用大量内存。
  2. 可迭代性:生成器对象可以用在 for 循环中,也可以转换为列表、元组等可迭代对象。
  3. 状态保持:每次调用生成器的 __next__() 方法时,生成器会记住上一次的状态(包括局部变量等)。

创建生成器

生成器可以通过以下两种方式创建:

1. 使用生成器函数

生成器函数是一个普通函数,但使用 yield 语句返回值。例如:

python
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

# 使用生成器
for number in count_up_to(5):
    print(number)

# print    
1
2
3
4
5

闭包

Python 中,闭包(Closure)是一个函数对象,能够“记住”其定义时的作用域,即使在函数的外部被调用时,仍然可以访问那些局部变量。闭包通常用于在函数内部定义另一个函数,并返回该内部函数。

闭包的特点

1. 嵌套作用域

闭包的内部函数能够访问外部函数的变量。即使外部函数已经执行完毕,内部函数依然可以“记住”这些变量的状态。

2. 函数对象

闭包是一个函数对象,它可以被调用并返回其值。这使得闭包可以被赋值给变量、作为参数传递或作为返回值返回。

3. 保持状态

闭包可以用来维护状态。通过外部函数的参数,闭包能够记住外部函数的局部变量的值,适合于实现一些需要状态保持的功能。

4. 避免全局变量

闭包可以在不使用全局变量的情况下,封装一些数据和状态。这增强了代码的封装性和可维护性。

5. 延迟计算

闭包可以用于延迟计算,因为它们可以在需要时才被调用。你可以创建一个闭包,在之后的时间点执行,而不是立即执行。

创建闭包的步骤

  1. 定义一个外部函数。
  2. 在外部函数中定义一个内部函数。
  3. 在外部函数中返回内部函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def outer_function(x):
    # 定义外部函数,接收一个参数 x
    def inner_function(y):
        # 定义内部函数,接收一个参数 y
        return x + y  # 使用外部函数的参数 x
    return inner_function  # 返回内部函数

# 创建闭包
closure = outer_function(10)

# 调用闭包
result = closure(5)  # 这里 y = 5
print(result)  # 输出 15,因为 10 + 5 = 15

装饰器

https://www.runoob.com/w3cnote/python-func-decorators.html

https://foofish.net/python-decorator.html

https://blog.csdn.net/jpch89/article/details/84026130

随机数生成

在python中,有两个模块可以产生随机数:

  1. python自带random包: 提供一些基本的随机数产生函数,可满足基本需要

2. numpy.random:提供一些产生随机数的高级函数,满足高级需求

区别总结

  1. 功能范围: random 模块提供的功能较为基础,而 np.random 提供的功能更强大,特别是在生成多维随机数方面更为高效和灵活。
  2. 速度和效率: np.random 通常比 random 快,尤其是在生成大规模随机数时,因为 NumPy 是用 C 语言实现的,性能更高。
  3. 多维数组: np.random 更适合科学计算,可直接生成多维数组的随机数,而 random 主要用于一维随机数生成。

本文先分别介绍这两个包的常用函数,文末将总结一下两个包的区别。

Python随机数小结——random和np.random的区别与联系_Zhang_Raymond的博客-CSDN博客

【python】random与numpy.random

Python——随机数生成模块numpy.random - 怪猫佐良 - 博客园

np.random.seed(1234)

np.random简单随机数

函数名称函数功能参数说明
rand(d0, d1, …, dn)产生均匀分布的随机数dn为第n维数据的维度
randn(d0, d1, …, dn)产生标准正态分布随机数dn为第n维数据的维度
randint(low[, high, size, dtype])产生随机整数low:最小值;high:最大值;size:数据个数
random_sample([size])在[0,1)内产生随机数size:随机数的shape,可以为元祖或者列表,[2,3]表示2维随机数,维度为(2,3)
random([size])同random_sample([size])同random_sample([size])
ranf([size])同random_sample([size])同random_sample([size])
sample([size]))同random_sample([size])同random_sample([size])
choice(a[, size, replace, p])从a中随机选择指定数据a:1维数组 size:返回数据形状
bytes(length)返回随机位length:位的长度

数据统计

1
2
3
4
5
6
# 平均值
sum(numbers) / len(numbers)
math.mean(numbers) # Priority
np.mean(numbers)
df.mean() # pandas
statistics.mean(numeric_numbers)

图像显示

解决plt.imshow显示cv2.imread读取的图像有色差发蓝的四种方法问题_python_脚本之家

python bgr转rgb,Python OpenCV-imshow不需要从BGR转换为RGB

下划线

Python 中下划线的 5 种含义 | 菜鸟教程

python中的下划线”_“讲解_dfd中的下划线_随心1993的博客-CSDN博客

自定义的最好不要用双下划线

环境管理

1
2
3
# 获取当前python环境
print(sys.path)
print(sys.executable)

Python获取当前运行函数的名称、类方法名称

在PyCharm中,你可以使用一些方法来判断当前是否处于Debug模式还是Run模式。以下是一些可能的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 使用 sys 模块:
import sys

if hasattr(sys, 'gettrace'):
    if sys.gettrace() is not None:
        print("Debug 模式")
    else:
        print("Run 模式")
else:
    print("无法确定模式")

此方法通过检查 sys.gettrace() 是否为 None 来判断是否处于调试模式

使用 PyCharm 特定的环境变量:
PyCharm 在运行和调试时会设置一些环境变量,你可以利用这些环境变量来判断当前的模式。
例如,可以检查 PYCHARM_HOSTED 是否存在:

1
2
3
4
5
6
7
8
import os

if os.environ.get('PYCHARM_HOSTED') == '1':
    print("Debug 模式")
else:
    print("Run 模式")`**

这种方法利用了PyCharm在Debug模式下设置的 PYCHARM_HOSTED 环境变量

使用 debug 变量:
在 Python 中,存在一个特殊的 debug 变量,其在调试模式下为 True,在正常运行模式下为 False。在PyCharm的Debug模式下,debug 通常为 True。

1
2
3
4
if __debug__:
    print("Debug 模式")
else:
    print("Run 模式")

这些方法中,第一和第二种方法是比较通用的,可以在其他IDE或环境中使用。第三种方法则是一种 Python 内建的方式,但需要注意的是,在某些特殊情况下可能会有异常。选择适合你项目和偏好的方法。

切换工作目录

在 Python 脚本中,若要将脚本的当前工作目录(Current Working Directory,CWD)切换为脚本所在的目录,可借助 ospathlib 模块来实现。下面为你分别介绍这两种模块的实现方式。

使用 os 模块

os 模块提供了与操作系统进行交互的功能,可用于获取脚本的路径并切换工作目录。示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import os

# 获取脚本所在的目录
script_dir = os.path.dirname(os.path.abspath(__file__))

# 切换当前工作目录为脚本所在的目录
os.chdir(script_dir)

# 打印当前工作目录进行验证
print(f"当前工作目录: {os.getcwd()}")

代码解释

  1. 获取脚本所在的目录
    • os.path.abspath(__file__)__file__ 是 Python 内置变量,代表当前脚本的文件名。os.path.abspath() 函数将其转换为绝对路径。
    • os.path.dirname():该函数用于获取文件所在的目录路径。
  2. 切换工作目录os.chdir() 函数用于将当前工作目录切换到指定的目录。
  3. 验证切换结果os.getcwd() 函数用于获取当前工作目录,并将其打印输出。

使用 pathlib 模块

pathlib 模块是 Python 3.4 及以上版本引入的面向对象的文件路径操作模块,使用起来更加简洁和直观。示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pathlib import Path

# 获取脚本所在的目录
script_dir = Path(__file__).resolve().parent

# 切换当前工作目录为脚本所在的目录
import os
os.chdir(script_dir)

# 打印当前工作目录进行验证
print(f"当前工作目录: {os.getcwd()}")

代码解释

  1. 获取脚本所在的目录
    • Path(__file__):创建一个 Path 对象,表示当前脚本的路径。
    • resolve():将相对路径转换为绝对路径。
    • parent:获取该路径的父目录。
  2. 切换工作目录:同样使用 os.chdir() 函数将当前工作目录切换到指定的目录。
  3. 验证切换结果:使用 os.getcwd() 函数获取并打印当前工作目录。

通过以上两种方法,你可以将 Python 脚本的当前工作目录切换为脚本所在的目录。

文件处理

Python字典保存(方便读取)_python 保存dict_风雨潇潇一书生的博客-CSDN博客

在 Python 中保存字典到文件

Object of type ’ndarray’ is not JSON serializable_AI视觉网奇的博客-CSDN博客

shutil.copy2(src, dst) 复制文件,保留元数据

路径处理

Python绝对路径和相对路径详解

【python3基础】相对路径,‘/’,‘./’,‘../’ - wuliytTaotao - 博客园

Python - 获取当前目录/上级目录/上上级目录_python 如何访问上级目录_wise哲的博客-CSDN博客

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding: UTF-8 -*-
import os

# 获取当前目录
print(os.getcwd())
print(os.path.dirname(__file__))
print(os.path.abspath(os.path.dirname(__file__)))

# 获取上级目录
print(os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
print(os.path.abspath(os.path.dirname(os.getcwd())))
print(os.path.abspath(os.path.join(os.getcwd(), "..")))

# 获取上上级目录
print(os.path.abspath(os.path.join(os.getcwd(), "../..")))

# 获取上上上级目录
print(os.path.abspath(os.path.join(os.getcwd(), "../../..")))

# ...以此类推

面向对象

Python 中 property() 函数及 @property 装饰器的使用-CSDN博客

获取函数名

Python获取当前运行函数的名称、类方法名称

获取函数的名称

1
2
3
4
5
6
# 在函数外部获取函数的名称,可以使用.__name__来获取。
def test_func_name1():
	print('test')

func_name1 = test_func_name1.__name__
print(func_name1)  # test_func_name1
1
2
3
4
5
6
7
# 在函数内部获取当前函数的名称,可以使用sys._getframe().f_code.co_name来获取
import sys

def test_func_name2():
	print(sys._getframe().f_code.co_name)

test_func_name2()  # test_func_name2

多进程

使用 multiprocessing 模块的 Pool 类的 apply 方法时,可以通过 get 方法获取子进程的返回值。以下是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

import multiprocessing

def square(x):
return x * x

if **name** == "**main**":
# 创建一个进程池
with multiprocessing.Pool() as pool:
# 调用 square 函数,并传递参数
result = pool.apply(square, args=(5,))

# 输出结果
print("返回值:", result)

在这个例子中,apply 方法会将 square 函数应用于参数 5,并返回结果。使用 get 方法获取返回值。

需要注意的是,apply 方法是阻塞的,即程序会等待子进程执行完毕并返回结果后才会继续执行。如果要并行执行多个任务并获取返回值,建议使用 map 方法。如果需要使用 apply,可以考虑使用 AsyncResult 对象来异步获取返回值。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

import multiprocessing

def square(x):
return x * x

if **name** == "**main**":
# 创建一个进程池
with multiprocessing.Pool() as pool:
# 调用 square 函数,并传递参数
result_async = pool.apply_async(square, args=(5,))

# 使用 get 方法获取返回值
result = result_async.get()

# 输出结果
print("返回值:", result)

这样可以实现异步获取返回值,而不会阻塞主进程。

apply_async 方法的执行顺序是不确定的,因为它取决于子进程的启动和执行速度。不同的子进程可能以不同的顺序完成执行。
因此,不能假设子进程的返回值的顺序与调用 apply_async 的顺序相同。

子进程

推荐

subprocess.run() # 阻塞处理

subprocess.Popen() # 非阻塞处理

方法对比

gpt

在 Python 中,subprocess 模块是执行 shell 命令的主要方式。常用方法包括:

方法适用场景是否支持管道是否获取返回值是否阻塞
subprocess.run()简单命令,需等待执行完成
subprocess.call()简单命令,仅返回状态码
subprocess.Popen()需要实时交互或流式处理✅(communicate() 获取)
os.system()执行简单 shell 命令❌(仅返回状态码)
commands.getoutput()(Python 2)仅 Python 2,可获取输出

1. subprocess.run()(推荐)

Python 3.5+ 推荐使用 subprocess.run(),它可执行 shell 命令并返回 CompletedProcess 对象。

示例

1
2
3
4
import subprocess

result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(result.stdout)  # 获取标准输出

优点

  • 支持获取返回值 (stdout, stderr)
  • 自动等待进程完成
  • 支持 capture_output=True 直接获取输出

2. subprocess.call()

类似 run(),但只返回状态码,不返回输出。

示例

1
2
3
4
import subprocess

return_code = subprocess.call(["ls", "-l"])
print("Exit Code:", return_code)

适用场景

  • 仅需要知道命令是否成功,而不关心输出。

3. subprocess.Popen()(适用于交互或流式处理)

适用于长时间运行的命令、实时读取输出需要交互的情况。

示例

1
2
3
4
5
6
7
import subprocess

process = subprocess.Popen(["ping", "-c", "4", "google.com"], stdout=subprocess.PIPE, text=True)
for line in process.stdout:  # 实时读取输出
    print(line.strip())

process.wait()  # 等待进程结束

优点

  • 支持实时读取输出
  • 不阻塞主线程
  • 支持 communicate() 进行交互

适用场景

  • 长时间运行的进程
  • 实时获取输出
  • 需要进程间通信

4. os.system()(不推荐)

执行 shell 命令,但无法获取输出,只返回退出状态码。

示例

1
2
3
4
import os

exit_code = os.system("ls -l")
print("Exit Code:", exit_code)

缺点

  • 无法捕获输出
  • 不支持管道
  • 安全性较差(可能导致 Shell 注入)

5. commands.getoutput()(仅 Python 2)

Python 3 已移除,等效于 subprocess.check_output()

示例(Python 2)

1
2
3
import commands
output = commands.getoutput("ls -l")
print(output)

对比总结

方法获取输出获取状态码是否推荐
subprocess.run()✅(可选 capture_output=True✅ 推荐
subprocess.call()❌ 仅适用于简单情况
subprocess.Popen()✅(stdout=PIPE✅ 适用于流式处理
os.system()❌ 不推荐
commands.getoutput()(Python 2)❌ 仅适用于 Python 2

🔹 推荐使用 subprocess.run(),如果需要流式处理则使用 subprocess.Popen()

qwen

以下是 Python 中执行 Shell 命令的子进程方法对比,包含使用场景、优缺点及代码示例:


1. os.system()

特点

  • 简单直接:适合执行单条命令
  • 输出到终端:结果直接打印到控制台,无法捕获
  • 返回状态码:返回命令的退出状态码
  • 安全隐患:直接拼接字符串易受 Shell 注入攻击

示例

1
2
3
4
import os

exit_code = os.system("ls -l")
print(f"命令执行状态码: {exit_code}")

适用场景

  • 快速执行简单命令(如脚本启动)
  • 无需捕获输出的场景

2. subprocess.run()(Python 3.5+ 推荐)

特点

  • 功能全面:支持超时、输入/输出捕获、错误检查
  • 返回 CompletedProcess 对象:包含返回码、输出等信息
  • 默认阻塞:等待命令执行完成
  • 安全参数传递:通过列表形式避免 Shell 注入

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import subprocess

result = subprocess.run(
    ["ls", "-l"],           # 命令参数列表
    capture_output=True,    # 捕获 stdout/stderr
    text=True,              # 输出解码为字符串
    timeout=5               # 超时时间(秒)
)

print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
print("返回码:", result.returncode)

适用场景

  • 需要捕获输出或检查错误的场景
  • 需要超时控制的长时间任务

3. subprocess.Popen(高级控制)

特点

  • 非阻塞执行:可通过 communicate() 控制阻塞行为
  • 灵活的 I/O 管理:支持管道、重定向、实时读取输出
  • 复杂流程:适合需要多进程交互的场景(如管道)

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import subprocess

process = subprocess.Popen(
    ["ping", "google.com"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

# 实时读取输出
while True:
    output = process.stdout.readline()
    if output == '' and process.poll() is not None:
        break
    if output:
        print(output.strip())

# 获取最终结果
stdout, stderr = process.communicate()
print("最终输出:", stdout)

适用场景

  • 实时处理命令输出(如日志监控)
  • 复杂进程交互(如管道、多命令组合)

4. subprocess.call() / check_call()

特点

  • 简单封装
    • call():返回状态码
    • check_call():状态码非零时抛出异常
  • 阻塞执行:等待命令完成

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import subprocess

# call() 示例
exit_code = subprocess.call(["ls", "-l"])
print(f"命令返回码: {exit_code}")

# check_call() 示例
try:
    subprocess.check_call(["false"])  # 故意触发错误
except subprocess.CalledProcessError as e:
    print(f"命令失败,返回码: {e.returncode}")

适用场景

  • 需要简单判断命令是否成功的场景

5. sh 模块(第三方库)

特点

  • 语法简洁:提供类似 Shell 的调用方式
  • 自动解码:输出直接返回字符串
  • 链式调用:支持管道操作

示例

1
2
3
4
5
6
7
8
9
import sh

# 执行命令
output = sh.ls("-l", "/tmp")
print(output)

# 管道操作
result = sh.grep(sh.cat("/etc/passwd"), "root")
print(result)

适用场景

  • 快速开发脚本(需安装 sh 库:pip install sh

对比总结表

方法输出捕获实时处理超时控制安全性复杂度推荐场景
os.system()低(易注入)简单快速执行简单命令
subprocess.run()中等需要捕获输出的标准任务
subprocess.Popen复杂实时交互或复杂进程管理
subprocess.call()简单仅需状态码的简单命令
sh 模块简单快速脚本开发(第三方依赖)

安全最佳实践

  1. 避免 shell=True:防止命令注入攻击

    1
    2
    3
    4
    5
    
    # 不安全写法
    subprocess.run("ls " + user_input, shell=True)
    
    # 安全写法
    subprocess.run(["ls", user_input])
    
  2. 参数列表化:始终将命令拆分为参数列表

    1
    2
    
    from shlex import split
    subprocess.run(split("ping -c 4 google.com"))
    
  3. 限制权限:通过 preexec_fn 或用户组限制子进程权限(Unix)

    1
    2
    3
    4
    
    subprocess.run(
        ["dangerous_cmd"],
        preexec_fn=lambda: os.setuid(1000)  # 降权运行
    )
    

根据需求选择合适的方法,推荐优先使用 subprocess.run()subprocess.Popen 以获得更好的控制和安全性。

run详细用法

subprocess.run() 是 Python subprocess 模块中的一个常用方法,用于执行 shell 命令,并等待命令完成后返回结果。


1. subprocess.run() 语法

1
2
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False,
               shell=False, cwd=None, timeout=None, check=False, encoding=None, text=None)

2. 详细参数说明

参数说明默认值
args需要执行的命令(字符串或列表)必须提供
stdin指定子进程的标准输入(如 subprocess.PIPENone
input传递给子进程的输入数据(如字符串或字节流)None
stdout指定子进程的标准输出(如 subprocess.PIPE 捕获输出)None
stderr指定子进程的标准错误输出None
capture_output如果为 True,等价于 stdout=subprocess.PIPE, stderr=subprocess.PIPEFalse
shell是否通过 shell 运行命令(如 bashFalse
cwd指定运行命令的工作目录None
timeout超时时间(秒),超过时间会抛出 subprocess.TimeoutExpired 异常None
check是否检查返回码,如果非 0 则抛出 subprocess.CalledProcessErrorFalse
encoding指定输出的编码(如 encoding="utf-8"None
textTrue 时等价于 encoding="utf-8",返回字符串而非字节流None

3. subprocess.run() 示例

(1)最基本的使用

1
2
3
import subprocess

subprocess.run(["ls", "-l"])  # 运行 ls -l

(2)捕获标准输出

1
2
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(result.stdout)  # 获取命令输出

等价于:

1
2
result = subprocess.run(["ls", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.stdout)

(3)使用 shell=True

1
subprocess.run("echo Hello World", shell=True)  # 通过 shell 运行

⚠️ 注意:

  • shell=True 适用于运行复杂的 shell 命令,如 ls *.txt | grep "data"
  • 不推荐对不受信任的输入使用 shell=True,因为可能导致 命令注入(安全风险)。

(4)改变工作目录

1
subprocess.run(["ls", "-l"], cwd="/home/user")  # 在 /home/user 目录下执行 ls -l

(5)传递输入

1
2
result = subprocess.run(["cat"], input="Hello, World!", text=True, capture_output=True)
print(result.stdout)  # 输出:Hello, World!

(6)超时控制

1
2
3
4
try:
    subprocess.run(["sleep", "10"], timeout=5)
except subprocess.TimeoutExpired:
    print("进程超时!")

(7)错误处理

1
2
3
4
try:
    subprocess.run(["ls", "/nonexistent_path"], check=True)  # 目录不存在会报错
except subprocess.CalledProcessError as e:
    print(f"命令失败,返回码: {e.returncode}")

4. subprocess.run() 返回对象

subprocess.run() 返回 CompletedProcess 对象,该对象包含以下信息:

1
2
3
4
5
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(result.args)     # ['ls', '-l']
print(result.returncode)  # 0 (成功) 或非 0(失败)
print(result.stdout)   # 标准输出
print(result.stderr)   # 标准错误

5. subprocess.run() vs subprocess.Popen()

方法是否阻塞是否可交互适用场景
subprocess.run()✅ 阻塞❌ 不可交互运行简单命令,获取输出
subprocess.Popen()❌ 非阻塞✅ 可交互需要实时获取进程状态、交互式命令

总结

  • subprocess.run() 适用于运行命令并等待结果
  • 可以捕获输出、设置超时、改变工作目录
  • 对于交互式进程或长期运行的进程,使用 subprocess.Popen()

🚀 推荐使用 capture_output=True, text=True 获取标准输出

1
2
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print(result.stdout)

Popen详细用法

subprocess.Popen() 是 Python 用于创建子进程的强大工具,常用于执行外部命令或程序。以下是 Popen 的常用参数及其作用:


1. subprocess.Popen() 基本格式

1
2
3
4
5
6
7
import subprocess

process = subprocess.Popen(args,
                           shell=False,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           stdin=subprocess.PIPE)

2. 主要参数解析

参数作用
args要执行的命令,可以是字符串(shell=True 时)或列表(shell=False 时)
shell是否使用 shell 解析命令(默认为 False)。True 时支持 &&、`
stdout标准输出,可设为 subprocess.PIPE(捕获输出)、None(直接显示)、文件对象
stderr标准错误输出,同 stdout
stdin标准输入,用于传递输入数据,如 subprocess.PIPE
cwd指定子进程的 工作目录
env环境变量,可传入 dict 来修改默认环境
text / universal_newlinesTrue 时,stdoutstderr 返回 字符串(默认是字节流)
encoding指定编码,如 utf-8
bufsize缓冲区大小,默认为 -1(系统默认),0(无缓冲),1(行缓冲)
executable指定要执行的二进制文件路径
close_fdsTrue 时关闭子进程的文件描述符(Windows 不支持)

3. args 参数示例

✅ 推荐(shell=False,列表方式)

1
subprocess.Popen(["ls", "-l"])

优点

  • 更安全(避免 shell 命令注入)
  • 自动处理参数(如空格、特殊字符)

⚠️ shell=True(字符串方式)

1
subprocess.Popen("ls -l", shell=True)

适用于

  • 需要执行 多个命令(如 ls -l | grep py
  • 需要使用 shell 变量(如 $HOME

注意:⚠ shell=True 可能导致 命令注入风险,特别是如果 args 是用户输入的内容。


4. stdoutstderr 捕获

1
2
3
4
5
6
7
8
process = subprocess.Popen(["ls", "-l"],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           text=True)

out, err = process.communicate()
print("标准输出:", out)
print("标准错误:", err)
  • stdout=subprocess.PIPE:捕获子进程的 标准输出
  • stderr=subprocess.PIPE:捕获子进程的 错误输出
  • text=True:自动解码为字符串(默认是字节流)

5. 读取子进程的输出

(1) process.communicate() 方式

1
2
3
4
5
process = subprocess.Popen(["echo", "Hello"],
                           stdout=subprocess.PIPE,
                           text=True)
out, err = process.communicate()
print(out)  # 输出: Hello

适用于

  • 一次性读取完整输出
  • 阻塞 等待子进程完成

(2) process.poll() 轮询检查状态

1
2
3
4
process = subprocess.Popen(["sleep", "3"])
while process.poll() is None:
    print("子进程仍在运行...")
print("子进程已结束")

适用于

  • 需要在子进程运行时执行其他任务
  • 非阻塞方式

(3) process.wait() 等待子进程结束

1
2
3
process = subprocess.Popen(["sleep", "3"])
process.wait()
print("子进程已结束")

适用于

  • 阻塞 直到子进程完成
  • 返回 子进程的退出码

(4) 逐行读取子进程输出

1
2
3
4
5
process = subprocess.Popen(["ping", "-c", "4", "google.com"],
                           stdout=subprocess.PIPE,
                           text=True)
for line in process.stdout:
    print(line.strip())  # 实时打印子进程输出

适用于

  • 实时获取子进程输出
  • 适合 长期运行的进程

6. stdin 传入数据

1
2
3
4
5
6
process = subprocess.Popen(["python", "-c", "print(input())"],
                           stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           text=True)
out, _ = process.communicate("Hello World")
print(out)  # 输出: Hello World

适用于

  • 需要给子进程提供输入数据

7. 终止子进程

1
2
3
process = subprocess.Popen(["sleep", "10"])
process.terminate()  # 终止进程
process.wait()  # 确保进程结束
  • terminate()发送 SIGTERM 终止
  • kill()发送 SIGKILL 强制终止
  • wait()确保进程完全退出

8. 运行带环境变量的命令

1
2
3
4
env = {"MY_VAR": "123"}
process = subprocess.Popen(["printenv", "MY_VAR"], env=env, stdout=subprocess.PIPE, text=True)
out, _ = process.communicate()
print(out)  # 输出: 123

适用于

  • 需要给子进程提供特定的环境变量

9. cwd 指定工作目录

1
process = subprocess.Popen(["ls"], cwd="/tmp")

适用于

  • 需要在特定目录下执行命令

10. 等待多个子进程

1
2
3
4
procs = [subprocess.Popen(["sleep", str(i)]) for i in range(1, 4)]
for p in procs:
    p.wait()
print("所有子进程结束")

适用于

  • 启动多个子进程,并等待它们全部结束

总结

功能关键参数代码示例
执行命令argssubprocess.Popen(["ls", "-l"])
使用 shellshell=Truesubprocess.Popen("ls -l", shell=True)
获取输出stdout=subprocess.PIPEout, _ = process.communicate()
实时读取输出for line in process.stdout逐行读取 示例
输入数据stdin=subprocess.PIPEprocess.communicate("data")
检查进程状态process.poll()轮询检查 示例
终止进程process.terminate()process.kill()
工作目录cwd="/tmp"subprocess.Popen(["ls"], cwd="/tmp")
环境变量env={"MY_VAR": "123"}subprocess.Popen(["printenv", "MY_VAR"], env=env)

📌 推荐

  • 执行外部命令时尽量使用 shell=False(避免安全风险)
  • 需要返回数据时,stdout=subprocess.PIPE, text=True
  • process.communicate() 适用于一次性读取,for line in process.stdout 适用于实时读取

这样,你就可以灵活使用 subprocess.Popen() 处理子进程了!🚀

p.communicate() v.s. p.poll()

p.communicate()p.poll() 都用于处理 Python subprocess.Popen() 进程,但它们的作用和适用场景不同:

方法作用是否阻塞返回值适用场景
p.communicate()读取标准输出/标准错误并等待进程结束✅ 阻塞(stdout, stderr)需要获取完整输出,并等待进程结束
p.poll()检查进程是否结束,不阻塞❌ 非阻塞None(进行中)或退出码需要非阻塞地检查进程状态

1. p.communicate()

p.communicate() 适用于一次性获取子进程的完整输出,同时等待进程结束

示例

1
2
3
4
5
6
7
import subprocess

p = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = p.communicate()  # 读取输出并等待进程结束

print("STDOUT:", stdout)
print("STDERR:", stderr)

特点

  • 会阻塞(等待进程结束)
  • 一次性读取 stdoutstderr
  • 防止死锁(避免 stdoutstderr 缓冲区溢出)

2. p.poll()

p.poll() 适用于非阻塞地检查进程是否结束,它不会等待进程结束,而是立即返回进程状态。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import subprocess
import time

p = subprocess.Popen(["sleep", "5"])  # 执行一个 5 秒的命令

while True:
    retcode = p.poll()  # 检查进程状态
    if retcode is not None:
        print("进程结束,退出码:", retcode)
        break
    print("进程仍在运行...")
    time.sleep(1)  # 每秒检查一次

特点

  • 不会阻塞
  • 仅返回进程状态
  • 如果进程结束,返回退出码;否则返回 None

3. 何时用 p.communicate(),何时用 p.poll()

  • 如果你需要获取进程输出并等待进程结束,用 p.communicate()
  • 如果你只想检查进程是否结束(但不阻塞),用 p.poll()

4. p.communicate() vs p.poll() 综合示例

如果需要非阻塞地检查进程状态,并在进程结束后获取输出,可以组合使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import subprocess
import time

p = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

while p.poll() is None:  # 进程还在运行
    print("进程运行中...")
    time.sleep(1)

stdout, stderr = p.communicate()  # 进程结束后获取输出
print("STDOUT:", stdout)
print("STDERR:", stderr)

总结

方法作用是否阻塞返回值适用场景
p.communicate()读取标准输出/错误并等待进程结束✅ 阻塞(stdout, stderr)需要获取完整输出,并等待进程结束
p.poll()检查进程是否结束❌ 非阻塞None(运行中)或退出码需要非阻塞检查进程状态

🚀 最佳实践

  • p.poll() 适用于需要非阻塞检查进程是否结束
  • p.communicate() 适用于获取子进程的输出并等待完成
  • 如果进程可能输出大量数据,建议使用 p.communicate() 避免缓冲区溢出

command lines

在 Python 中,判断脚本是否在终端中执行的方式可以是通过检查标准输入、输出是否连接到终端。
可以使用 sys.stdin.isatty() 来检查标准输入是否连接到终端,以及 sys.stdout.isatty() 来检查标准输出是否连接到终端。

以下是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

import sys

def is_running_in_terminal():
return sys.stdin.isatty() and sys.stdout.isatty()

if is_running_in_terminal():
print("Running in a terminal.")
else:
print("Not running in a terminal.")

在终端中执行时,sys.stdin.isatty() 和 sys.stdout.isatty() 通常都返回 True,而在非终端环境中返回 False。

请注意,这种方法可能不是绝对可靠的,因为在一些特殊情况下,即使在终端中运行,也可能返回 False。这主要取决于操作系统和环境。

open source

GitHub Top 10 + Python 开源项目(2021版

强烈推荐:GitHub 上开源的 13 个 Python 资源 - 掘金

推荐11个值得研究学习的Python开源项目(从入门到python高级开发) - GlaryJoker - 博客园

GitHub 上适合新手的开源项目(Python 篇)

noqa

在 Python 深度学习代码中,# noqaFlake8 代码检查工具的一种指令,用于忽略特定代码行的警告或错误

📌 # noqa 作用

# noqa 全称是 “No Quality Assurance”,主要用于**抑制静态代码分析工具(如 Flake8、pylint)**检测到的错误。例如,在深度学习代码中,可能遇到以下情况:

1️⃣ 忽略未使用的导入

在 PyTorch 或 TensorFlow 代码中,某些导入可能仅用于装饰器或动态加载,但 Flake8 可能会报 F401 'XXX' imported but unused

1
2
import torch  # noqa
import torch.nn as nn  # noqa: F401

✅ 这样可以防止 Flake8 报告未使用的导入。


2️⃣ 忽略长行超出 79/88 字符限制

如果代码行超过 PEP8 规定的 79 或 88 个字符(比如复杂的神经网络定义),可以使用 # noqa

1
model = nn.Sequential(nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 128))  # noqa

3️⃣ 忽略 E402(导入不在文件顶部)

有时为了兼容 TensorFlow 或 PyTorch 设备初始化,可能会在导入之前进行某些设置:

1
2
3
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import torch  # noqa: E402

# noqa: E402 让 Flake8 忽略导入不在顶部的错误。


💡 # noqa 在深度学习中的常见场景

场景noqa 代码示例
忽略未使用的导入import torch # noqa: F401
忽略长行警告model = nn.Sequential(...) # noqa
忽略导入顺序警告import torch # noqa: E402
忽略变量未使用_ = torch.randn(1) # noqa: F841

🚀 总结

  1. # noqa 用于忽略 Flake8/Pylint 代码检查
  2. 常见于深度学习代码,用于跳过未使用的导入(如 torch, tensorflow)、长行警告等。
  3. 可以指定错误代码,如 # noqa: F401 仅忽略 未使用导入,避免忽略其他潜在错误。

如果你在深度学习项目中看到 # noqa,通常是为了让代码格式检查工具不报错,而不会影响代码的运行。 🚀

PYTHONPATH

Shell 中执行 Python 脚本时,如果遇到 ModuleNotFoundError,很可能是 PYTHONPATH 没有正确设置,导致 Python 无法找到模块或包。下面是一些常见的解决方法:


1. 直接在 Shell 中临时设置 PYTHONPATH

如果你的 Python 模块位于 /home/user/my_project/,那么可以在运行 Python 脚本时手动指定 PYTHONPATH

1
2
export PYTHONPATH=/home/user/my_project:$PYTHONPATH
python your_script.py

或者直接一行执行:

1
PYTHONPATH=/home/user/my_project python your_script.py

适用于: 临时解决路径问题,每次新开终端都需要重新设置。


2. 在 Python 代码中手动添加 sys.path

如果你不想手动设置环境变量,可以在 your_script.py 代码顶部添加:

1
2
import sys
sys.path.append('/home/user/my_project')

适用于: 代码可控的情况,适用于多个环境,但需要手动修改代码。


3. 直接使用当前工作目录

如果你希望 Python 搜索当前目录,可以使用:

1
2
export PYTHONPATH=$(pwd):$PYTHONPATH
python your_script.py

或者:

1
PYTHONPATH=$(pwd) python your_script.py

适用于: 代码和模块都在当前目录及其子目录下。


4. 在 .bashrc / .bash_profile / .zshrc 中永久设置 PYTHONPATH

如果 ModuleNotFoundError 频繁发生,可以把 PYTHONPATH 添加到 ~/.bashrc~/.zshrc

1
2
echo 'export PYTHONPATH=/home/user/my_project:$PYTHONPATH' >> ~/.bashrc
source ~/.bashrc  # 使其生效

对于 zsh(Mac 或部分 Linux 发行版):

1
2
echo 'export PYTHONPATH=/home/user/my_project:$PYTHONPATH' >> ~/.zshrc
source ~/.zshrc

适用于: 需要长期使用某个 Python 项目,不想每次手动设置 PYTHONPATH


5. 确保 python 解释器正确

有时 ModuleNotFoundError 可能是因为使用了错误的 Python 解释器。在 Shell 中运行:

1
2
3
4
which python
which python3
python --version
python3 --version

如果你的项目使用虚拟环境(venv / conda),确保你激活了它:

1
2
source venv/bin/activate  # venv/virtualenv
conda activate my_env     # conda

适用于: 确保使用正确的 Python 解释器。


6. 通过 sys.path 检查 PYTHONPATH

你可以在 Python 交互模式或脚本中执行:

1
2
import sys
print(sys.path)

检查 PYTHONPATH 是否包含你的模块路径。如果缺失,就需要手动添加或设置。


总结

方案适用场景方式
1. export PYTHONPATH=路径临时解决,手动执行exportPYTHONPATH=xxx python script.py
2. sys.path.append()代码可控,可跨环境在 Python 脚本中添加
3. PYTHONPATH=$(pwd)当前目录搜索模块适用于单次运行
4. ~/.bashrc / ~/.zshrc长期使用某路径添加 export PYTHONPATH=xxx
5. 确保 python 解释器正确可能是错误的 Python 版本which python 检查
6. 检查 sys.path确认 PYTHONPATH 生效print(sys.path)

试试看这些方法,应该可以解决你的 ModuleNotFoundError 问题!🚀

PYTHONPATH vs. sys.path

sys.pathPYTHONPATH 都用于 影响 Python 解释器查找模块的路径,但它们的作用方式有所不同。


1. sys.path 是 Python 解释器的模块搜索路径

  • sys.path 是一个 列表,包含 Python 解释器搜索模块时会参考的所有目录路径。

  • 可以在 Python 代码中直接访问和修改:

    1
    2
    
    import sys
    print(sys.path)  # 输出当前的模块搜索路径
    
  • 可以手动添加新的搜索路径:

    1
    
    sys.path.append('/home/user/my_project')  # 追加新的搜索路径
    

2. PYTHONPATH 是环境变量,影响 sys.path

  • PYTHONPATHShell 环境变量,当 Python 启动时,它的值会被自动添加到 sys.path

  • 可以在 Shell 中设置:

    1
    2
    
    export PYTHONPATH=/home/user/my_project:$PYTHONPATH
    python script.py
    

    这样,Python 启动时 /home/user/my_project 目录就会被加入 sys.path


3. sys.pathPYTHONPATH 的关系

  • PYTHONPATH 的值会被 Python 启动时添加到 sys.path
  • sys.path 包含了默认的 Python 解释器搜索路径(如 site-packages)+ PYTHONPATH 变量中的路径 + 运行脚本所在目录。
  • 修改 sys.path 只影响当前 Python 进程,而 PYTHONPATH 影响整个 Shell 会话或系统环境。

4. sys.path 组成

通常,sys.path 包括以下几部分:

  1. 运行脚本的目录(即 ''.)。
  2. 标准库路径(例如 /usr/lib/python3.x)。
  3. site-packages(第三方库的安装目录)。
  4. PYTHONPATH 变量指定的路径(如果有)。
  5. 其他特殊路径(某些 Python 发行版可能会额外添加)。

示例:

1
2
import sys
print("\n".join(sys.path))

可能输出:

/home/user/my_project
/usr/lib/python3.8
/usr/lib/python3.8/lib-dynload
/usr/local/lib/python3.8/dist-packages

5. 何时使用 PYTHONPATH vs sys.path

方式适用场景作用范围影响时间
PYTHONPATH设置 Python 解释器的全局搜索路径影响所有 Python 进程Python 启动时生效
sys.path.append()仅影响当前 Python 进程仅当前 Python 运行时运行时动态生效

6. 选择使用 PYTHONPATH 还是 sys.path

适合用 PYTHONPATH

  • 你需要全局(长期)设置 Python 搜索路径(如添加某个项目路径)。
  • 你希望在 Shell 中运行 Python 时自动生效(适用于 Docker、远程服务器等)。
  • 你在 .bashrc.zshrc 中配置了 PYTHONPATH 以持久化路径。

适合用 sys.path

  • 你只想在 当前 Python 进程 中临时添加路径,而不影响其他 Python 进程。
  • 你的脚本会被不同环境运行,手动修改 sys.path.append() 更灵活。
  • 你在 Jupyter Notebook 或动态环境中,临时需要额外路径。

7. 示例:两种方式的使用

方式 1:使用 PYTHONPATH(推荐长期设置)

1
2
export PYTHONPATH=/home/user/my_project:$PYTHONPATH
python my_script.py

这会在 Python 启动时/home/user/my_project 加入 sys.path

方式 2:使用 sys.path.append()(推荐短期/动态修改)

1
2
3
import sys
sys.path.append('/home/user/my_project')  # 运行时生效
import my_module  # 现在可以导入这个目录下的模块

适用于脚本内部动态修改,而不影响全局 Python 解释器。


总结

  • PYTHONPATH 影响 sys.path,但 sys.path 也可以在运行时动态修改。
  • 如果要全局长期修改 Python 搜索路径,用 PYTHONPATH(适用于环境配置)。
  • 如果只是临时修改某个 Python 进程的路径,用 sys.path.append()(适用于代码内部)。

🚀 最佳实践

  1. 开发环境:在 Shell 设置 PYTHONPATH,减少 sys.path.append() 的使用。
  2. 脚本运行:如果需要动态修改路径,使用 sys.path.append()
  3. 生产环境:确保 PYTHONPATH.bashrc / .zshrc 中配置好,避免硬编码 sys.path.append()

这样可以确保 Python 代码在不同环境下都能正确导入模块! ✅

append v.s. insert(0)

在 Python 中,sys.path.append(path)sys.path.insert(0, path) 都用于动态添加模块搜索路径,但它们的优先级行为有显著差异。以下是详细对比:


1. 核心区别

操作行为
sys.path.append(path)将路径 追加到 sys.path 列表的末尾,优先级最低。
sys.path.insert(0, path)将路径 插入到 sys.path 列表的最前面,优先级最高。

2. 优先级对模块导入的影响

Python 解释器按 sys.path 的顺序搜索模块。假设存在以下路径:

1
2
3
4
5
sys.path = [
    "/usr/lib/python3.8",
    "/usr/local/lib/python3.8",
    ...,
]

场景 1:使用 append

1
sys.path.append("/custom/path")
  • 新路径 /custom/path 会被添加到列表末尾。
  • 优先级:系统路径 > 用户路径。
  • 适用场景:希望优先使用系统默认模块,仅在必要时使用自定义路径。

场景 2:使用 insert(0, ...)

1
sys.path.insert(0, "/custom/path")
  • 新路径 /custom/path 会被插入到列表最前面。
  • 优先级:用户路径 > 系统路径。
  • 适用场景:希望覆盖系统模块(例如自定义同名模块)或强制优先加载本地代码。

3. 实际示例

假设目录结构如下:

project/
├── main.py
└── my_package/
    └── module.py

3.1 使用 append

1
2
3
import sys
sys.path.append("/path/to/project/my_package")
import module  # 会搜索到 my_package/module.py
  • 风险:如果系统中存在同名模块(如 module),且路径在 /path/to/project/my_package 之前,会优先导入系统模块。

3.2 使用 insert

1
2
3
import sys
sys.path.insert(0, "/path/to/project/my_package")
import module  # 确保导入的是 my_package/module.py
  • 优势:强制优先使用自定义路径中的模块,避免命名冲突。

4. 选择标准

需求推荐方法
需要覆盖系统或第三方模块sys.path.insert(0, path)
仅补充额外路径(不影响现有逻辑)sys.path.append(path)

5. 注意事项

  1. 临时性:两种方法仅对当前 Python 进程有效,脚本结束后失效。
  2. 路径冲突:插入高优先级路径可能导致意外覆盖系统模块(例如自定义 os.py 会覆盖标准库 os 模块)。
  3. 可维护性:频繁修改 sys.path 可能导致代码难以维护,建议优先使用虚拟环境(venv)或安装模块到全局路径。

6. 替代方案

  • 设置 PYTHONPATH 环境变量(全局生效):

    1
    
    export PYTHONPATH=/custom/path:$PYTHONPATH
    
  • 使用相对导入:在包内使用 from . import module

  • 安装模块到全局环境

    1
    
    pip install -e /path/to/package  # 开发模式安装
    

总结

  • append:保守添加路径,避免干扰现有模块。
  • insert(0, ...):强制优先加载指定路径,适用于调试或覆盖默认行为。

shebang语法

在 Python 中使用子进程时,是否推荐使用 shebang 语法取决于你具体的场景和需求。下面我将详细解释 shebang 语法及其在子进程调用中的适用性。


🔍 什么是 shebang?

shebang(#!) 是一种用于指明脚本文件解释器的语法。例如,#!/usr/bin/env python3 指定该脚本应使用 python3 来执行。它通常放在文件的第一行,作用是告诉操作系统用哪个解释器来执行脚本。

1
2
#!/usr/bin/env python3
print("Hello, World!")

在 Linux 和 macOS 上,这行会告诉操作系统用 python3 解释器来执行该文件。


🧑‍💻 shebang 在子进程中的使用场景

1. 子进程执行 Python 脚本

如果你从 Python 中调用另一个 Python 脚本(例如,使用 subprocess 模块),并且你希望该脚本能够正确使用指定的解释器执行,使用 shebang 是非常有意义的。

假设你有一个名为 my_script.py 的 Python 脚本,它的第一行是:

1
#!/usr/bin/env python3

然后,你在父进程中使用 subprocess.Popen() 调用它:

1
2
3
import subprocess

subprocess.Popen(["./my_script.py"])

为什么需要 shebang?

  • 如果你在脚本文件中包含了 shebang,操作系统会根据该行的解释器路径来执行脚本。因此,即使你没有显式指定 Python 解释器,操作系统会自动选择正确的解释器。
  • 如果你使用相对路径执行该脚本(例如,./my_script.py),而没有提供完整的解释器路径,shebang 会确保脚本被正确执行。

2. Python 子进程调用外部程序

在某些情况下,你可能希望通过 subprocess 调用其他 Python 程序(或者外部程序),而不是执行当前 Python 文件。此时,你可能需要手动指定解释器或使用环境变量。

例如:

1
2
3
4
import subprocess

# 使用 shebang 自动选择 Python 解释器
subprocess.Popen(["/usr/bin/env", "python3", "other_script.py"])

如果你已经在目标脚本中指定了 shebang,你甚至可以跳过显式指定 Python 解释器,如下所示:

1
subprocess.Popen(["./other_script.py"])

(前提是目标脚本的权限已设置为可执行,并且你在其中包含了正确的 shebang。)


⚠️ 注意事项

  1. 确保脚本可执行

    • 使用 shebang 时,目标 Python 脚本需要具有执行权限。你可以使用 chmod 命令设置执行权限:

      1
      
      chmod +x my_script.py
      
  2. 平台差异

    • #!/usr/bin/env python3 是跨平台的,但它依赖于操作系统环境,因此在不同平台上可能有不同的行为。确保你的操作系统中正确安装了 Python 解释器。
  3. 不使用 shebang 的情况

    • 如果你不打算将脚本作为独立程序运行,而只是作为子进程中的一部分运行,那么可以在 subprocess.Popen() 中明确指定解释器路径,如:

      1
      
      subprocess.Popen(["python3", "my_script.py"])
      

      这时,shebang 就不再必要了。


是否推荐使用 shebang?

  • 推荐:如果你希望在不同环境下执行 Python 脚本,并且该脚本可能独立运行,使用 shebang 会使得脚本更具可移植性,尤其是在 Linux/macOS 上。
  • 不推荐:如果你在 Python 内部通过 subprocess 调用其他 Python 脚本,并且明确知道解释器路径或使用的是虚拟环境,那么不一定需要使用 shebang,可以直接指定解释器路径。

👌 总结

  • 如果你的 Python 脚本需要作为独立脚本运行或者有子进程调用,可以使用 shebang 语法。
  • 使用 subprocess.Popen() 时,shebang 可以简化命令,自动选择解释器,但也可以直接在父进程中指定 Python 解释器。

包引用

deepseek

在Python项目中,若要在多个子目录及次级子目录中正确导入根目录下的模块,可采用以下步骤:

方法一:动态添加根目录到sys.path

  1. 在项目根目录创建标记文件(如.project_root),以便识别根目录。
  2. 在各子模块中动态计算根目录路径,并将其添加到sys.path中。

示例代码(子模块中):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import sys
from pathlib import Path

def find_root_dir(start_path):
    current = Path(start_path).resolve()
    # 寻找包含.project_root的目录
    while not (current / '.project_root').exists():
        if current.parent == current:
            raise RuntimeError("Project root not found.")
        current = current.parent
    return current

# 获取根目录并添加到sys.path
root_dir = find_root_dir(__file__)
if str(root_dir) not in sys.path:
    sys.path.append(str(root_dir))

# 导入根目录下的模块
from core_utils import some_function

方法二:设置PYTHONPATH环境变量

在运行脚本前,将项目根目录添加到PYTHONPATH中:

1
2
3
4
5
# Linux/macOS
export PYTHONPATH="${PYTHONPATH}:/path/to/project_root"

# Windows
set PYTHONPATH=%PYTHONPATH%;C:\\path\\to\\project_root

或在脚本中动态设置:

1
2
3
import sys
sys.path.append('/path/to/project_root')
from core_utils import some_function

方法三:使用可编辑安装(推荐用于复杂项目)

  1. 创建setup.py或使用pyproject.toml配置项目
  2. 执行可编辑安装
1
pip install -e .

安装后,所有模块可通过包名导入:

1
from project_name.core_utils import some_function

目录结构示例

project_root/
├── .project_root    # 标记文件
├── core_utils.py
├── subdir1/
│   ├── __init__.py
│   └── module1.py
└── subdir2/
    ├── __init__.py
    └── nested_subdir/
        ├── __init__.py
        └── module2.py

总结

  • 简单项目:使用方法一,动态添加路径,灵活且无需额外配置。
  • 团队协作/复杂项目:使用方法三,通过可编辑安装实现标准化导入。
  • 快速测试/临时使用:使用方法二,临时设置环境变量。

确保所有子模块在导入前正确设置根目录路径,即可跨目录层级正确导入核心模块。

qwen

要在多个子目录及次级子目录中正确导入根目录下的 core_utils.py,可以采用以下方法:

方法一:动态添加根目录到 sys.path

在每个需要导入的子模块中,动态计算根目录路径并添加到 sys.path。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import sys
from pathlib import Path

# 获取当前文件的绝对路径
current_file = Path(__file__).resolve()

# 计算根目录路径(根据实际层级调整 parent 的次数)
root_dir = current_file.parent.parent  # 假设子目录是两级深(如 app/module1/)

# 将根目录添加到系统路径
sys.path.insert(0, str(root_dir))

# 导入 core_utils
from core_utils import some_function

注意:根据子目录的层级调整 .parent 的次数。例如,若脚本在 app/module2/submodule/,则可能需要 parent.parent.parent.parent


方法二:自动查找根目录

编写函数自动向上查找包含 core_utils.py 的根目录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import sys
from pathlib import Path

def find_root():
    current = Path(__file__).resolve().parent
    while current != current.parent:
        if (current / 'core_utils.py').exists():
            return current
        current = current.parent
    raise FileNotFoundError("Root directory with core_utils.py not found.")

sys.path.insert(0, str(find_root()))
from core_utils import some_function

此方法自动定位根目录,无需手动调整路径层级。


方法三:使用相对导入(需包结构)

将项目重构为包结构,确保所有目录包含 __init__.py 文件:

project_root/
├── __init__.py
├── core_utils.py
└── app/
    ├── __init__.py
    ├── module1/
    │   ├── __init__.py
    │   └── script1.py
    └── module2/
        └── submodule/
            └── script2.py

script1.py 中使用相对导入:

1
from ..core_utils import some_function  # 需作为模块运行(如 python -m app.module1.script1)

方法四:设置环境变量

在运行脚本前设置 PYTHONPATH

1
PYTHONPATH=/path/to/project_root python app/module1/script1.py

或在代码中临时设置:

1
2
import os
os.environ['PYTHONPATH'] = '/path/to/project_root'

方法五:通过包安装(推荐大型项目)

使用 setup.pypyproject.toml 将项目安装为可编辑模式:

1
2
3
4
5
6
7
8
# setup.py
from setuptools import setup, find_packages

setup(
    name='my_project',
    version='0.1',
    packages=find_packages(),
)

安装命令:

1
pip install -e .

安装后,可在任意位置导入:

1
from core_utils import some_function

总结

  • 小型项目:使用方法一或二动态添加路径。
  • 中大型项目:推荐方法三或五,通过包结构管理导入。
  • 灵活运行:方法四设置环境变量。

根据项目需求选择最适合的方案。