这份Python实验记录,其实不只是记录代码怎么跑通,更像是我从零开始折腾Python这段路上的完整复盘。从装解释器、配环境,到写爬虫、做数据分析,再到打包成exe、用Docker跑起来,中间踩过的坑比代码行数还多。像“python was not found”这种报错、pip安装库失败、matplotlib横坐标挤成一团,这些高频问题我都遇到过,也都找到了能直接照抄的解决办法。
如果你正准备学Python、或者已经写了一段时间但总被环境问题和各种报错卡住,这份记录应该很适合你。我不会只贴一句“安装成功”,而是把每一步背后的为什么也讲清楚,比如为什么要用虚拟环境、为什么切片容易搞混、为什么协程能提高效率。这样你遇到类似问题时,不是靠猜,而是能自己推导出来。
实验记录这种东西,最怕的就是写成流水账。我尽量把每次实验的目的、过程、结果和心理活动都留下来了,成功的、失败的都是,希望对你有用。
1. 实验环境准备:装对Python比学语法更先磨人
1.1 Python版本怎么选、PATH勾选为什么要慎重
很多新手拿到Python第一个坑,就是不知道下载哪个版本。我的建议是直接选官方的最新稳定版,比如3.11或3.12,不要碰2.x系列,也不要贪新用那种刚发布的alpha版本。原因很简单:第三方库的兼容性都优先保证主流稳定版,比如pandas、numpy、sklearn这些库,你在3.8上能装,在3.10上也能装,但如果选了太冷门的版本,说不定哪天安装某个库就报“找不到匹配版本”。
安装时Windows安装包界面上那个“Add Python to PATH”复选框,一定记得勾上。很多人就是因为没勾,后面在cmd里输入python,冒出那句经典提示“Python was not found”。这句提示不是说你电脑没装Python,而是系统在PATH环境变量里找不到python.exe。你要是没勾,后期手动加环境变量也可以,但为什么不一步到位呢。
安装路径也别图省事放在带空格或者中文的目录下,比如“C:\Program Files (x86)\Python”虽然也能用,但后续某些工具处理起来会麻烦。我习惯自定义一个“D:\Python312”这样简洁的路径,好记也不容易出问题。装完以后打开命令行,输入python --version和pip --version,都能正常输出版本号,说明解释器和包管理器都就位了。
1.2 虚拟环境加pip换源:避免重装Python的后悔药
我刚学Python时,所有第三方库一股脑装到全局环境里,结果就是不同项目依赖的库版本互相冲突,今天升级了这个库,明天那个项目的代码就报错。后来才养成了用虚拟环境的习惯。Python自带的venv模块就够用了,不需要额外安装其他工具。
# 创建虚拟环境(在项目目录下执行) python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate激活后,命令行前面会出现“(venv)”标识,这时候pip install的库就都装在这个隔离环境里,不会污染全局。每次新建项目,我第一件事就是创建并激活虚拟环境。
还有一个体验提升很大的操作,就是给pip换源。默认的官方源在境外,下载速度真的能急死人。我用的阿里云镜像源,配置一次永久生效。Windows下在%APPDATA%\pip目录建一个pip.ini文件(没有目录就新建),Linux/Mac下是~/.config/pip/pip.conf,写入:
[global] index-url = https://mirrors.aliyun.com/pypi/simple/ trusted-host = mirrors.aliyun.com换源之后pip install numpy这种安装基本几秒钟搞定。如果公司内网环境比较特殊、不能联网,那就还需要另一招:在一台联网机器上执行pip download numpy -d ./packages把安装包下载下来,再拷贝到内网机器上,pip install numpy时指定本地包目录--no-index --find-links=./packages就行。这个离线安装思路我后来在部署服务器时反复用到。
1.3 VSCode与PyCharm:编辑器配置Python环境的核心操作
编辑器这块我主力用VSCode,好在轻量,也好在跨平台。装好VSCode之后,安装Python扩展(微软官方那个),然后按Ctrl+Shift+P,输入“Python: Select Interpreter”,选择刚创建的虚拟环境。这一步如果漏了,VSCode里运行时可能一直找不到模块,或者直接提示Python没安装。
我习惯在项目根目录建.vscode/launch.json,用F5调试时直接跑当前文件,配置很简单:
{ "version": "0.2.0", "configurations": [ { "name": "Python: 当前文件", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "env": { "PYTHONPATH": "${workspaceFolder}" } } ] }PyCharm我也用过一段时间,它更适合大型工程,但启动速度和内存占用是硬伤。无论选哪个,核心逻辑是一样的:告诉IDE用哪个解释器、把项目根目录加入PYTHONPATH。配置好环境之后,我养成了一个习惯——每写几行代码就跑一下,看见输出就安心了,而不是等写几百行再一口气运行,那样报错定位会很痛苦。
2. Python语法实验:从变量、函数到切片和类型转换
2.1 动态类型与内存对象的id实验
Python是动态类型语言,变量本身没有类型,类型属于对象。这个概念我最初是模糊的,直到做了个简单的id实验才真正理解。id()函数返回对象在内存中的唯一标识,用它观察能发现很多有趣的现象。
a = 10 print(id(a)) # 比如输出 140721861621720 a = "hello" print(id(a)) # 输出完全不同的另一个值,说明a绑定了新的对象这看起来简单,但很有深意。我一开始以为“给a赋新值”是把旧变量内容改掉了,实际是让变量名a指向了另一个对象。还有个经典实验是Python的小整数驻留机制:[-5, 256]范围内的整数因为频繁使用,会被Python缓存,所以a = 256; b = 256; a is b是True,但a = 257; b = 257; a is b却是False。知道这个规律,你就不会在代码里依赖is来比较整数,因为is比较的是对象身份,数值相等该用==。
这个观念转变很重要,尤其是后面学函数传参时,可变对象和不可变对象的行为差异就来源于这里。列表、字典这些可变对象作为参数传入函数时,函数内部修改会影响外部原对象;而整数、字符串、元组这种不可变对象则不会。搞清楚这一个点,很多bug都能提前避免。
2.2 函数定义实验:默认参数、可变参数和abs函数
定义函数是语法学习的分水岭。我实验过的常规定义方式:
def greet(name, greeting="Hello"): return f"{greeting}, {name}!" def sum_all(*args): # 可变位置参数 return sum(args) def info(**kwargs): # 可变关键字参数 return kwargs print(greet("小明")) # Hello, 小明! print(sum_all(1, 2, 3, 4)) # 10 print(info(name="小红", age=18)) # {'name': '小红', 'age': 18}这里有个经典大坑,就是默认参数不要用可变对象。我踩过这个坑:
def add_item(item, lst=[]): lst.append(item) return lst print(add_item("a")) # ['a'] print(add_item("b")) # ['a', 'b'],问题来了!第二次调用时,lst还会保留上一次的“a”,因为默认列表在函数定义时只创建一次,后续调用共享同一个对象。正确写法是def add_item(item, lst=None):,函数内部再判断if lst is None: lst = []。
顺便说个容易被忽略的内置函数abs。很多基础教程都是一句话带过——“求绝对值”。但它的用途其实比想象中广:计算两个坐标点距离、判断误差是否在允许范围内、处理负值数据都离不开它。abs(-3.14)等于3.14,abs(3 + 4j)返回5.0,因为复数绝对值的含义是模长。这些细节我是在写数据清洗脚本时才发现,原来基础函数也有这么多门道。
lambda表达式我把它当作一次性小函数的语法糖。比如排序时指定key,直接data.sort(key=lambda x: x[1])就能按第二个元素排序,比单独定义一个具名函数更简洁。但如果逻辑比较复杂,还是def更清晰,可读性永远比“写得很炫”重要。
2.3 类型转换实验:显式、隐式与常见的ValueError
Python的类型转换,说白了就是四种场景:字符串转数字、数字转字符串、数值之间的转换、序列类型之间的转换。我做了个简单实验:
# 字符串转数字 print(int("123")) # 123 print(float("3.14")) # 3.14 # 数字转字符串 print(str(123) + "abc") # 123abc # 数值间转换需要注意截断 print(int(3.9)) # 3,不是四舍五入,是向零截断 # 进制转换 print(int("ff", 16)) # 255,按16进制解析最容易出错的,是int("abc")或者int("3.14")这类无法转换的字符串,会直接抛ValueError。我在实际写脚本时经常要处理用户输入,比如从Excel读出来一个长度数字,但它可能被存成了“10,000”这种带千分位逗号的样子,直接int肯定报错。我的处理是先清洗再转:
def safe_convert_to_int(text): cleaned = str(text).replace(",", "").strip() try: return int(cleaned) except ValueError: return 0字符串拼接也是新手的重灾区:price = 12.5,然后直接print("价格是" + price)会报TypeError。因为字符串和浮点数不能直接相加。正确方式是print("价格是" + str(price))或者用f-string:print(f"价格是{price}")。Python的f-string是我最喜欢的格式化方式,简洁直观,强烈建议早点用起来。
2.4 数组切片实验:一个口诀解决所有切片难题
切片是Python里非常优雅也容易混淆的特性。我做了大量实验,最终总结了一个口诀:切片三步走,起点、终点、步长;左闭右开,步长为负时方向反转。
data = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(data[1:5]) # [1, 2, 3, 4],不包含索引5 print(data[:3]) # [0, 1, 2],开头省略表示从0开始 print(data[7:]) # [7, 8, 9],结尾省略表示到末尾 print(data[::2]) # [0, 2, 4, 6, 8],每隔一个取一个 print(data[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],反转列表 print(data[-3:]) # [7, 8, 9],负索引从末尾数字符串也是同样的规则。"python"[1:4]得到“yth”,“python”[::-1]得到“nohtyp”,这个技巧我经常用来快速判断回文串。
还有一个很多人忽略的坑:切片返回的是新对象还是视图?对于列表来说,切片返回的是新列表,修改切片不会影响原列表。这个特性能用来复制列表——copy_list = data[:]。但注意,如果你直接写copy_list = data,那不是复制,只是给原列表起了一个新名字,两者指向同一个对象,改其中一个另一个也会变。这个区别我在项目里吃过亏,排查半天发现是引用共享导致数据被意外修改。
3. 并发与协程实验:线程不灵,协程来凑
3.1 从同步到协程:为什么IO密集型任务该用协程
我在写爬虫时遇到一个现实问题:要下载100个小文件,一个一个来太慢,用多线程又老是要控制共享变量。后来才系统学了协程,才知道在Python里处理IO密集型任务,协程是更轻量的方案。
先做个小实验对比一下。同步方式下载多个URL,总耗时是所有请求耗时的总和。协程方式,在等待网络响应的间隙,解释器会自动切到另一个任务去执行,总耗时约等于最慢那个请求的耗时。这个提升在网络请求这种IO场景非常明显。
这里科普一个背景知识:CPython有GIL(全局解释器锁),多线程在CPU密集型任务上基本发挥不了多核优势。但协程不一样,它是在单线程内实现的并发,靠的是“遇到IO就主动让出控制权”,没有线程切换的系统开销。所以结论是:CPU密集用多进程,IO密集用协程,这个判断标准后来一直指导着我的方案选型。
3.2 asyncio实战:async、await与create_task
Python协程的核心是事件循环。我用标准库asyncio写了一个模拟请求的实验:
import asyncio async def task(name, delay): print(f"任务{name}开始") await asyncio.sleep(delay) print(f"任务{name}结束,耗时{delay}秒") return f"{name}的结果" async def main(): # 方式一:顺序执行 # result = await task("A", 1) # result = await task("B", 2) # 方式二:并发执行 tasks = [ asyncio.create_task(task("A", 2)), asyncio.create_task(task("B", 1)), asyncio.create_task(task("C", 0.5)), ] results = await asyncio.gather(*tasks) print(results) asyncio.run(main())这个实验验证了一件事:在协程里,用asyncio.sleep()来模拟IO,是理解事件循环最好的方式。顺序执行总耗时约3秒,并发执行总耗时约2秒,因为最慢任务是2秒,其余任务在等待间隙被穿插执行了。gather能把多个协程的返回值一次性收集起来,比逐个await高效得多。
实际写爬虫时,我还用到了Semaphore限制并发数量,防止一下子发太多请求被封:
sem = asyncio.Semaphore(10) async def fetch_with_limit(url): async with sem: return await fetch_one(url)协程给我最大的启发,不是性能数字上的翻倍,而是“等待时不闲着”这种思维。同时也纠正了我一个错误习惯:协程里绝不能写time.sleep(),那会阻塞整个事件循环,所有任务都要排队等它睡完,相当于把并发打回了串行。必须用await asyncio.sleep()才能让出控制权。
4. 数据分析与可视化实验:从CSV到Excel的完整流程
4.1 numpy、pandas、matplotlib的安装与版本匹配问题
数据分析四件套,我装过无数次。直接一条命令搞定:
pip install numpy pandas matplotlib openpyxlnumpy是很多科学计算库的地基,sklearn、scipy都依赖它。安装sklearn库时如果报错,绝大多数情况是numpy版本不兼容,要么numpy太老、要么太新。我的处理方式是先看一眼pip list里numpy的版本,再装对应的scikit-learn版本。新版pandas有时候还会要求numpy最低版本,所以装库遇到看不懂的错误,先升级一下numpy和pip准没错。
还有一次在Linux服务器上离线装这些库,那才叫一个折腾。机器不联网,我本地用pip download把整个依赖链都拉下来,然后打包拷过去。这个过程中发现了一个规律:numpy和pandas的whl文件名里都带“cp37”或“cp311”这样的标记,它表示兼容的Python版本。cp311代表仅适用于Python 3.11,下载时一定要看准,否则本地装得上,服务器装不上。
4.2 数据读取与清洗实验:dropna、fillna的适用场景
我用一个真实场景来演示:一份CSV销售记录,里面有空值、有错误格式,还有重复行。实验目标是清洗成可用的DataFrame。
import pandas as pd df = pd.read_csv("sales.csv", encoding="utf-8") print(df.head()) # 快速预览前5行 print(df.info()) # 查看每列类型和缺失情况 print(df.isnull().sum()) # 统计缺失值数量清洗的核心决策是:空值到底删还是补?我的经验是看业务场景和缺失比例。如果某列缺失超过50%,基本可以考虑直接丢列;如果是个别零星缺失,且是数值列,用df["price"].fillna(df["price"].mean())填充平均值;如果是时间序列,用df.fillna(method="ffill")向前填充更合理。
dropna()也不是一味地删行,它的参数thresh很有用:df.dropna(thresh=5)表示某行至少要有5个非空值才保留,这样那些几乎全是缺失的垃圾行会被清掉,而“只是某个字段没填”的有效行会留下。这个细节我第一次用的时候完全没注意,后来处理带大量空列的表才发现它的价值。
还有个高频需求是把字符串数字转为数值类型:df["sales"] = pd.to_numeric(df["sales"], errors="coerce")。errors="coerce"的作用是无法转换的自动变成NaN,而不是抛异常中断脚本。这样洗数据的过程就能一路跑到底,最后再统一处理NaN。
4.3 matplotlib画图横坐标太密集:xticks调参心得
画图踩过最大的坑,就是横坐标标签太多挤在一起,黑乎乎一坨。一开始我以为数据有问题,后来才明白是刻度密度问题。解决方案很直接:
import matplotlib.pyplot as plt import numpy as np x = [f"样本{i}" for i in range(100)] y = np.random.randn(100) plt.figure(figsize=(12, 6)) plt.plot(x, y) # 核心:每隔10个显示一个标签,并旋转45度防止重叠 step = 10 plt.xticks(np.arange(0, len(x), step), [x[i] for i in range(0, len(x), step)], rotation=45, ha="right") plt.tight_layout() plt.savefig("chart.png", dpi=150) plt.show()figsize控制整体画布大小,np.arange(0, len(x), step)控制显示哪些位置的刻度,rotation=45旋转标签,ha="right"让标签右对齐,看起来更整齐。tight_layout()用来自动调整子图布局,避免标签被裁剪。这几个参数组合起来,基本解决了“横坐标太密”这个老大难。
我后来做周报的时候,数据点很多又需要完整保存,就用这个方式一步到位。还有个隐藏心得是dpi=150的导出质量,在PPT投屏上也完全够清晰。
4.4 写入Excel:pandas.to_excel的多sheet操作
数据分析完的结果要交付的话,Excel是绕不开的格式。pandas写Excel很方便,但要注意环境里确实装了openpyxl引擎,否则会报错。我的完整实验代码:
import pandas as pd result_df1 = pd.DataFrame({"名称": ["A", "B"], "值": [1, 2]}) result_df2 = pd.DataFrame({"名称": ["C", "D"], "值": [3, 4]}) with pd.ExcelWriter("output.xlsx", engine="openpyxl") as writer: result_df1.to_excel(writer, sheet_name="汇总表", index=False) result_df2.to_excel(writer, sheet_name="明细表", index=False)index=False很重要,不写的话会把默认的行号也写进Excel,看上去多了一列莫名其妙的数字。多sheet输出使用ExcelWriter,上下文管理器会自动保存和关闭文件,不用手动writer.save()。
如果要把某个单元格样式也做漂亮,直接在pandas里调比较费劲,我的习惯是先把数据写好,然后再用openpyxl打开文件调整格式,比如设置表头加粗、列宽自适应。数据分析和格式美化分开处理,代码逻辑反而更清晰。
5. 爬虫与自动化实验:从静态到动态页面
5.1 静态页面爬虫:requests加BeautifulSoup的经典组合
入门爬虫,requests加BeautifulSoup是最经典的组合。我实验时写了一个不太复杂的例子,目标是从公开的新闻列表页提取标题和链接。核心代码:
import requests from bs4 import BeautifulSoup import time headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } try: resp = requests.get("https://example.com/news", headers=headers, timeout=10) resp.raise_for_status() resp.encoding = resp.apparent_encoding # 处理编码问题 except requests.exceptions.RequestException as e: print(f"请求失败: {e}") exit() soup = BeautifulSoup(resp.text, "html.parser") for item in soup.select(".news-item a")[:10]: title = item.get_text(strip=True) link = item.get("href") print(title, link) time.sleep(1)几个细节说明一下。raise_for_status()会在返回状态码不是200时直接抛异常,省去手动判断。resp.encoding = resp.apparent_encoding处理乱码问题,尤其是一些老站用的gbk编码,requests默认猜测可能不准。time.sleep(1)是基本的礼貌延迟,不给目标服务器压力。爬虫的道德底线,我给自己立的规矩是:遵守robots.txt,只爬公开数据,控制请求频率,不碰任何登录后才能看的非授权内容。这不是口号,稍有不慎,人就容易滑向灰色地带。
5.2 动态页面实验:Playwright无头浏览器与等待策略
很多页面是JavaScript动态渲染的,requests拿到手的HTML里根本没有真实数据。这个时候需要无头浏览器。我选的是Playwright,它对现代浏览器的控制能力不错,而且API相对友好。安装一下就两行:
pip install playwright playwright install chromium实验场景是抓取一个表格数据。核心逻辑是:打开页面,等表格渲染出来,再取HTML解析:
from playwright.sync_api import sync_playwright url = "https://example.com/list" with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(url, wait_until="networkidle") page.wait_for_selector("table#data-table") rows = page.locator("table#data-table tbody tr").count() for i in range(rows): cells = page.locator(f"table#data-table tbody tr:nth-child({i+1}) td").all_text_contents() print(cells) browser.close()wait_until="networkidle"是等网络空闲,再配合wait_for_selector确保关键元素出现。这种两层等待策略,比刚加载就开始找元素稳得多。headless=True是无头模式,不弹浏览器窗口,适合服务器上跑。
写爬虫时还经常遇到动态翻页,我的处理是循环里改URL参数或者点击下一页按钮,但每步操作之间都要加等待。实验了几次,我发现“等”是动态爬虫的核心艺术——等对位置、等够时间,不要一上去就咔咔点。
5.3 爬虫常见异常:超时、SSL、编码的一次排查实录
爬虫最容易翻车的就是网络层异常。我自己的排查顺序是:先给所有requests加上timeout参数,避免无限等待挂死;遇到SSL错误时先用verify=False跳过证书验证赶紧跑通逻辑,但生产环境必须换用正确的证书处理;编码问题主要靠apparent_encoding兜底。
还有一个高频坑是请求头不完整被服务器识别成爬虫,返回403。最简单的应对是把User-Agent换成一个完整的浏览器UA。再进一步可以添加Referer、Accept-Language这些头,让请求看起来更像真人访问。但也要记住,反爬对抗是螺旋上升的,最稳的策略永远是:慢一点、少一点、温和一点。我爬取的一个实际项目中,把请求间隔从0.5秒调到2秒后,从频繁触发校验码到完全正常运行,损失一点速度换来稳定性,非常划算。
6. 打包、部署与问题排查:从本地到生产环境的最后一公里
6.1 PyInstaller打包exe:从脚本到可执行文件
Python脚本在别人电脑上运行,对方通常没有Python环境,或者装了但版本不对。所以我用PyInstaller把脚本打包成独立的exe文件。基本命令:
pip install pyinstaller pyinstaller -F -w my_script.py --add-data "assets;assets"-F表示打包成单文件,方便分发;-w表示运行时隐藏控制台窗口,适合带图形界面的程序。--add-data用来额外打包资源文件,Windows下用分号分隔源目录和目标目录,Linux下用冒号,我第一次打包带资源的脚本就栽在这了,拷贝到别的电脑上运行提示找不到数据文件,排查半天才发现是资源没打进去。
打包完成后的exe在dist目录里。有个经验是,exe首次启动可能会被Windows Defender提示,因为PyInstaller打包出来的程序没有数字签名,杀毒软件容易误报。个人使用时信任一下即可,正式对外分发就得考虑代码签名证书了。还有,如果脚本里用了某些动态库,打包后可能提示缺少DLL,最常见的是VC++运行库缺失,让用户装一下vcredist就行。这个实验让我意识到,Python开发是一回事,交付产品又是另一回事。
6.2 Ubuntu安装Docker并运行Python环境
部署到服务器时,Docker是我的首选方案。它能把Python环境和所有依赖打包成镜像,到任何装Docker的机器上直接运行,彻底告别“在我电脑上能跑啊”的尴尬。先简单说下Ubuntu上Docker的安装流程:
sudo apt update sudo apt install -y docker.io sudo systemctl enable docker sudo systemctl start docker docker --version然后准备一个Dockerfile,构建Python环境:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ COPY . . CMD ["python", "main.py"]构建和运行:
docker build -t my-python-app . docker run -d --name my-app -v $(pwd)/data:/app/data my-python-app docker exec -it my-app /bin/bash-v把宿主机的data目录挂载进容器,这样容器里生成的数据文件在宿主机上也能看到,不用进入容器拷贝。docker exec -it my-app /bin/bash可以进入容器排查问题。这套流程我在一台干净的Ubuntu服务器上裸跑过,从装Docker到容器内跑通Python数据分析脚本,半小时内完成。Docker的好处用一次就懂了:换机器重来一遍的成本几乎为零。
6.3 Python常见报错速查表:实验中的高频故障与解决
做实验这段时间,我把遇到过的报错整理成一张速查表,每次卡住就来看一眼,非常实用:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
| Python was not found | PATH环境变量未配置 | 安装时勾选Add to PATH,或手动添加python.exe所在目录 |
| pip不是内部或外部命令 | 未安装pip或PATH未配置 | 执行python -m ensurepip,或重新安装Python并勾选pip |
| ModuleNotFoundError: No module named 'xxx' | 模块未安装或解释器选错 | pip install xxx,并确认当前使用的解释器是虚拟环境 |
| ImportError: libGL.so.1 | 缺少系统依赖库 | Ubuntu执行apt install -y libgl1 libglib2.0-0 |
| UnicodeDecodeError | 文件编码与系统默认编码不一致 | read_csv时指定encoding="utf-8"或"gbk" |
| SyntaxError: invalid syntax | 常见于高版本语法在低版本运行 | 检查Python版本,f-string和类型注解要求3.6以上 |
| FileNotFoundError | 文件路径不存在 | 用os.path.exists提前判断,注意相对路径以当前工作目录为准 |
| PermissionError | 文件被占用或权限不足 | 关闭正在打开该文件的程序,或以管理员权限运行 |
表格里那个libGL报错,是我在Linux服务器上装OpenCV时碰到的经典问题。pip install opencv-python本身装好了,但import cv2时愣是报错,后来才明白OpenCV的whl包依赖一些系统级动态库。所以装cv2之前,先检查系统库齐不齐,能少走很多弯路。
6.4 高频操作的心得汇总:环境与依赖管理的小习惯
我在实验接近尾声时,整理了几条自己最受用的操作习惯。第一,创建新项目第一步永远是建虚拟环境,这已经是条件反射了。第二,pip安装库之前先确认解释器,目前使用哪个Python环境,直接用where python或者which python看一眼,避免装错环境。第三,项目依赖一定要写到requirements.txt里,随时pip freeze > requirements.txt导出。第四,代码里涉及到路径的地方,尽量用pathlib.Path而不是字符串拼接,跨平台兼容性会好很多。
此外,离线安装的场景我再补充个细节。如果你需要在一台完全不联网的机器上装Python环境,光拷贝whl是不够的,还要保证whl文件与目标机的pip版本、系统的glibc版本兼容。我的做法是先在目标机上执行python -m pip --version确认版本,本地下载依赖时也用相同版本的pip去download。这样搬过去之后,安装成功率高得多。这套流程我帮同事在几台物理隔离的机器上搭过环境,全程手把手,一次成功。