Python¶
个人实用脚本与踩坑:邮件发送、词云、Jira 查询、AI 翻译 PDF、流式响应、paramiko 远程操作、流量监控、动态密码,另附常用方法与概念速查。通用语法、打包等可直接问 AI 的内容不再收录。
-
实用脚本
邮件、词云、Jira、PDF 翻译、流式响应
-
远程与系统
paramiko、subprocess、流量监控
-
语言技巧
装饰器、生成器、线程、文件读写
-
速查
排序 / filter / map、魔术方法、日志
📧 发送邮件¶
smtplib · MIMEMultipart · MIMEImage · formataddr。发送带附件与内嵌图片的 HTML 邮件。
使用 smtplib 与 email 库发送带附件和图片的 HTML 邮件。
import datetime
import locale
import smtplib
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.utils import formataddr
locale.setlocale(locale.LC_ALL, 'zh_CN.UTF-8')
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S %A")
sender_email = "uyme@qq.com"
password = "1111111111"
receiver_email = "aooboo@88.com"
message = MIMEMultipart()
message["Subject"] = f"测试邮件{now}"
message['From'] = formataddr(('机器人-96', sender_email))
message['To'] = formataddr(('', receiver_email))
def add_attachment(message, file_name):
with open(file_name, "rb") as attachment:
att = MIMEApplication(attachment.read(), _subtype=file_name.split(".")[-1])
att.add_header("Content-Disposition", "attachment", filename=file_name)
message.attach(att)
def add_img(message, img_name, imageid):
"""添加图片;imageid 用于在正文中引用"""
with open(img_name, "rb") as f:
img = MIMEImage(f.read())
message.attach(img)
img.add_header('Content-ID', f'<{imageid}>')
return f"<img src='cid:{imageid}'>"
add_attachment(message, "ccc.py")
image1 = add_img(message, "20230410113916.png", 'aadd')
body = f'<h1>这是一张图片</h1>:<br><br><br>{image1}'
message.attach(MIMEText(body, 'html'))
with smtplib.SMTP("smtp.qq.com", 587) as smtp:
smtp.starttls()
smtp.login(sender_email, password)
smtp.sendmail(sender_email, receiver_email, message.as_string())
☁️ 词云生成¶
使用 wordcloud 库,以图片作为形状蒙版生成词云。
from wordcloud import WordCloud
from PIL import Image
import numpy as np
# 蒙版框架
mask = np.array(Image.open("123.png"))
wordcloud = WordCloud(background_color="white",
width=1800,
height=1600,
max_words=200,
max_font_size=100,
mask=mask,
contour_width=3,
contour_color='steelblue',
font_path="仓耳舒圆体W05.ttf")
# 文字内容
wordcloud.generate('''建立声望
社交与人脉
让自己的技能跟得上时代
''')
wordcloud.to_file('lz9.png')
🔍 Jira 查询并导出 CSV¶
JIRA · jql_str · search_issues · csv.writer。按 JQL 查询并导出 CSV。
使用 jira 库按 JQL 查询,将结果保存为 CSV 表格。
import csv
import requests
from jira import JIRA
JIRA_SERVER = 'https://jira.mibo.com'
Personal_Access_Tokens = "填写令牌"
jira = JIRA(JIRA_SERVER, token_auth=Personal_Access_Tokens)
issues = jira.search_issues(
jql_str="assignee was in (mibo) AND issuetype in (客户反馈, 缺陷)",
json_result=True,
fields='summary,assignee,status,created,resolutiondate,assignee,customfield_10313,reporter,issuetype',
maxResults=500000)
def use_api():
"""直接使用 REST API 查询"""
headers = {
"Content-Type": "application/json",
"authorization": f"Bearer {Personal_Access_Tokens}"
}
jira_url = ("https://jira.mibo.com/rest/shdsd-Timesheet/latest/workReport/extension2"
"?queryType=convention&start=2022-01-01&end=2022-02-01&groupBy=U,P,I"
"&userGroup=产品研发线/XXX中心/XXX部门")
response = requests.get(jira_url, headers=headers).json()
def result_to_csv(issues, csv_file_path='issues.csv'):
with open(csv_file_path, 'w', newline='', encoding="utf8") as file:
writer = csv.writer(file)
writer.writerow(['Key', 'Summary', 'Assignee', 'Status', "created", "fixtime",
"assignee", "fixer", "reporter", "type"])
for issue in issues['issues']:
try:
fixer = issue['fields']['customfield_10313']['displayName']
except Exception:
fixer = "NONE"
try:
fixtime = issue['fields']['resolutiondate'][0:19]
except Exception:
fixtime = "NONE"
created = issue['fields']['created'][0:19]
assignee = issue['fields']['assignee']['displayName']
reporter = issue['fields']['reporter']['displayName']
type = issue['fields']['issuetype']['name']
writer.writerow([issue['key'], issue['fields']['summary'],
issue['fields']['assignee']['displayName'],
issue['fields']['status']['name'], created, fixtime,
assignee, fixer, reporter, type])
🌐 AI 翻译 PDF¶
使用 PyMuPDF (fitz) 解析 PDF 的文本块,调用 AI 接口逐段翻译后回填到原位置;使用 sqlite3 做翻译缓存,pywebio 提供上传与下载页面。
import fitz
import sqlite3
import pywebio
from pywebio import config
from pywebio.input import file_upload, input_group
from pywebio.output import put_file
import json
import requests
class maxai:
def __init__(self, auth="application-ad80edba84c30eb1161ae04e71ee7f78"):
self.base_url = 'http://ai.tech.intra.com'
self.auth = auth
self.headers = {'accept': 'application/json', 'AUTHORIZATION': self.auth}
self.new_chat = False
self.chatid = self.create_chat()
print(f"\r登录成功-{self.chatid}")
def chat(self, query):
chat_id = self.create_chat() if self.new_chat else self.chatid
url = self.base_url + "/api/application/chat_message/" + chat_id
data = {"message": query, "re_chat": False, "stream": True}
response = requests.post(url, headers=self.headers, json=data, stream=True)
translated_text = ''
if response.status_code == 200:
response.encoding = 'utf-8'
for line in response.iter_lines(decode_unicode=True):
if line:
event_data = json.loads(line[5:])
if event_data['is_end'] is False:
text = event_data['content']
if text:
translated_text += text
return translated_text
def create_chat(self):
id = self.login_and_get_appinfo()
url = self.base_url + "/api/application/" + id + "/chat/open"
return requests.get(url, headers=self.headers).json()['data']
def login_and_get_appinfo(self):
url = self.base_url + '/api/application/profile'
return requests.get(url, headers=self.headers).json()['data']['id']
def convert_color(color_int):
"""将颜色整数转换为 RGB 格式"""
r = (color_int >> 16) & 0xFF
g = (color_int >> 8) & 0xFF
b = color_int & 0xFF
return (r / 255, g / 255, b / 255)
def init_db():
conn = sqlite3.connect('translations_cache.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS translations (
original_text TEXT PRIMARY KEY,
translated_text TEXT
)
''')
conn.commit()
conn.close()
def get_cached_translation(text):
conn = sqlite3.connect('translations_cache.db')
cursor = conn.cursor()
cursor.execute('SELECT translated_text FROM translations WHERE original_text = ?', (text,))
result = cursor.fetchone()
conn.close()
return result[0] if result else None
def cache_translation(original_text, translated_text):
conn = sqlite3.connect('translations_cache.db')
cursor = conn.cursor()
cursor.execute('INSERT INTO translations (original_text, translated_text) VALUES (?, ?)',
(original_text, translated_text))
conn.commit()
conn.close()
def translate_text(text):
cached_translation = get_cached_translation(text)
if cached_translation:
return cached_translation
return ccaam.chat(text)
init_db()
ccaam = maxai()
def trancpdf(pdf_document, filename):
for page_num in range(pdf_document.page_count):
page = pdf_document.load_page(page_num)
blocks2 = page.get_text("dict", flags=11)["blocks"]
for block in blocks2:
for line in block["lines"]:
for span in line["spans"]:
text = span["text"]
if text.strip() and len(text) > 10:
translated_text = translate_text(text)
if "抱歉" in translated_text or "翻译" in translated_text:
continue
font_size = span["size"]
color = span["color"]
bbox = span["bbox"]
rect = fitz.Rect(bbox)
page.insert_text((rect.x0, rect.y0), translated_text,
fontsize=font_size / 3,
color=convert_color(color),
fontname="SimSun", fontfile="SimSun.ttf")
pdf_document.save("zh_CN/" + filename)
content = open("zh_CN/" + filename, 'rb').read()
put_file(filename, content, 'download me')
def sendmd():
data = input_group("翻译pdf", [
file_upload(accept=".pdf", placeholder="翻译pdf", name="file"),
])
f = data['file']
filename = f['filename']
open(filename, 'wb').write(f['content'])
if "file" in data:
pdf_document = fitz.open(filename)
trancpdf(pdf_document, filename)
pdf_document.close()
if __name__ == '__main__':
config(title="工具", theme="yeti", description="yes")
pywebio.start_server(sendmd, port=1008, cdn=False)
⚡ 处理流式响应¶
BaseHTTPRequestHandler · iter_lines · getReader() · TextDecoder。SSE 流式返回的前后端配合。
流式响应的两个坑:一是 header 设置,二是前端 JS 处理流式返回的方式(若不正确,会导致响应结束才有反应)。
后端¶
使用 http.server 提供接口:POST 接收消息写入 sqlite 队列,GET 取出并流式调用 AI 接口,逐块写回响应。
import json
import urllib.parse
import sqlite3
from ai.deep_speek import maxai
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
apkey = 'application-24f27892e5f9da86d669d842c1c68254'
c1 = maxai(new_chat=True, auth=apkey)
def create_table(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS my_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value TEXT NOT NULL
)
''')
conn.commit()
cursor.close()
conn.close()
def add_record(db_name, value):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
try:
cursor.execute("INSERT INTO my_table (value) VALUES (?)", (value,))
conn.commit()
except sqlite3.Error as e:
print(f"An error occurred while adding record: {e}")
finally:
cursor.close()
conn.close()
def get_and_delete_min_id_value(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
try:
cursor.execute("SELECT id, value FROM my_table ORDER BY id ASC LIMIT 1")
row = cursor.fetchone()
if row:
min_id, value = row
cursor.execute("DELETE FROM my_table WHERE id = ?", (min_id,))
conn.commit()
return value
return None
except sqlite3.Error as e:
print(f"An error occurred: {e}")
return None
finally:
cursor.close()
conn.close()
class SSEHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
pass
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
decoded_str = urllib.parse.unquote(post_data.decode('utf-8'))
add_record(db_name, decoded_str)
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Cache-Control', 'no-cache')
self.end_headers()
self.wfile.write(b'')
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html; charset=utf-8")
self.send_header('Cache-Control', 'no-cache')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
value = get_and_delete_min_id_value(db_name)
if value:
response = c1.webchat(value)
if response.status_code == 200:
response.encoding = 'utf-8'
for line in response.iter_lines(decode_unicode=True):
if line:
event_data = json.loads(line[5:])
if event_data['is_end'] is False:
text = event_data['content']
if text:
self.wfile.write(text.encode('utf-8'))
def run(server_class=ThreadingHTTPServer, handler_class=SSEHandler, port=8000):
httpd = server_class(('', port), handler_class)
print(f'Starting httpd server on port {port}...')
httpd.serve_forever()
if __name__ == '__main__':
db_name = 'ai'
create_table(db_name)
run()
前端¶
关键点:response.body.getReader() 配合 TextDecoder 逐块读取,递归 read() 持续消费流。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8"/>
<title>Simple example - Editor.md examples</title>
<link rel="stylesheet" href="css/style.css"/>
<link rel="stylesheet" href="../css/editormd.css"/>
</head>
<body>
<div id="layout">
<header><h1>Simple example</h1></header>
<form action="http://127.0.0.1:8000" onsubmit="handleFormSubmit(event)">
<input type="text" id="text" name="text">
<button type="submit">Send</button>
</form>
<div id="test-editormd">
<textarea style="display:none;" id="textareaabc"></textarea>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="../editormd.min.js"></script>
<script type="text/javascript">
var testEditor;
$(function () {
testEditor = editormd("test-editormd", {
width: "95%", height: 800, syncScrolling: "both",
path: "../lib/", preview: true,
});
});
function fetchData() {
fetch('http://127.0.0.1:8000/data')
.then(response => {
if (!response.body) {
throw new Error("ReadableStream not yet supported in this browser.");
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
function read() {
reader.read().then(({done, value}) => {
if (done) {
console.log("Stream completed");
return;
}
const chunk = decoder.decode(value, {stream: true});
var currentMarkdown = testEditor.getMarkdown();
testEditor.setMarkdown(currentMarkdown + chunk);
read(); // 递归继续读取
});
}
read();
})
.catch(error => console.error("Error fetching stream:", error));
}
function handleFormSubmit(event) {
event.preventDefault();
const inputValue = document.getElementById('text').value;
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://127.0.0.1:8000/events123', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(encodeURIComponent(inputValue));
xhr.onload = function () {
if (xhr.status === 200) {
console.log('请求成功:', xhr.responseText);
fetchData();
} else {
console.error('请求失败:', xhr.statusText);
}
};
}
</script>
</body>
</html>
📈 流量监控与告警¶
读取网卡 tx_bytes 统计,按小时监控上行流量,超过阈值调用 msg.py 告警,超过总上限则关机。
#!/bin/bash -e
# 阈值:每 5G 告警一次,总上限 300G
gosgos=$((5000 * 1024 * 1024)) # 5G
max=$((300 * 1024 * 1024 * 1024)) # 300G
nettag=$(cat /sys/class/net/eth0/statistics/tx_bytes)
end=$((nettag + gosgos))
echo "speedlog" > /var/speed.log
while true; do
current_traffic=$nettag
sleep 1m
new_traffic=$(cat /sys/class/net/eth0/statistics/tx_bytes)
traffic_increase=$((new_traffic - current_traffic))
echo "本小时上行流量: $((traffic_increase / 1048576)) MB; 起始量:$((current_traffic / 1048576)) MB 阈值:$((end / 1048576)) MB" >> /var/speed.log
# 超过单次阈值则告警
if [ $new_traffic -ge $end ]; then
/usr/bin/python3 /root/auto/msg.py $((traffic_increase / 1048576)) $((new_traffic / 1048576))
echo "超过 $((gosgos / 1048576)) MB,告警告警。" >> /var/speed.log
nettag=$(cat /sys/class/net/eth0/statistics/tx_bytes)
end=$((nettag + gosgos))
else
echo "未超过 $((gosgos / 1048576)) MB,继续监控。"
fi
# 超过总上限则关机
if [ $new_traffic -ge $max ]; then
/usr/bin/python3 /root/auto/msg.py 即将关机 poweroff
shutdown -h +5
echo "上行流量超过 $((max / 1073741824)) GB,将执行关机操作。" >> /var/speed.log.bk
else
echo "上行流量未超过 $((max / 1073741824)) GB,继续监控。"
fi
# 每小时重置起始值
if [ $(date +%M) == "00" ]; then
nettag=$(cat /sys/class/net/eth0/statistics/tx_bytes)
end=$((nettag + gosgos))
echo "新阈值为 $((end / 1048576)) MB。" >> /var/speed.log
fi
# 每天定时汇报流量
if [ $(date +%H:%M) == "18:02" ]; then
/usr/bin/python3 /root/auto/msg.py 每日流量情况 $((new_traffic / 1048576)) MB
fi
done
🔐 2FA 动态密码¶
pyotp · TOTP · interval · verify。基于时间的一次性密码。
使用 pyotp 生成 TOTP 动态密码。TOTP 基于共享密钥与时间戳,通过哈希函数生成随时间变化的验证码。
基本概念:
| 概念 | 说明 |
|---|---|
| 共享密钥(Secret Key) | 服务提供商和用户设备之间共享,用于生成和验证验证码 |
| 时间戳(Timestamp) | 从某个特定时间点(如 UNIX 纪元)开始经过的秒数 |
| 哈希函数(Hash Function) | 对共享密钥和时间戳的组合进行哈希运算,生成验证码 |
| 时间步长(Time Step) | 验证码的变化频率,常用 30 秒或 60 秒 |
工作流程:服务商生成随机密钥并与用户设备共享;双方用相同算法(默认 SHA1,也可用 SHA256/SHA512 等)计算当前时间窗口的哈希值并提取数字作为验证码;服务器比对用户提交的验证码是否匹配。
import pyotp
def verify_2af():
# 生成一个 2FA 密钥
secret = pyotp.random_base32(64)
# 生成基于密钥的验证码,有效时长 60 秒
totp = pyotp.TOTP(secret, interval=60)
code = totp.now()
print(f"Code: {code}\nsecret: {secret}\n")
user_input = input("输入验证码: ")
if totp.verify(user_input):
print("2AF Code is 有效.")
else:
print("2AF Code is 无效.")
if __name__ == "__main__":
verify_2af()
🖥️ paramiko 远程操作¶
SSHClient 类似 ssh(执行命令),SFTPClient 类似 sftp(传文件)。底层实现 SSHv2,fabric、ansible 的远程管理也基于它。
密码 / 密钥连接与执行命令:
import paramiko
ssh_client = paramiko.SSHClient()
# 自动把未知主机名与密钥加入本地 HostKeys,必须在 connect 之前调用
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname='192.168.137.105', port=22, username='root', password='123456')
stdin, stdout, stderr = ssh_client.exec_command('df -hT')
print(stdout.read().decode('utf-8'))
ssh_client.close()
# 密钥方式
private = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
ssh_client.connect(hostname='192.168.137.100', port=22, username='root', pkey=private)
SFTP 上传下载:
tran = paramiko.Transport(('192.168.137.100', 22))
tran.connect(username="root", password='123456')
sftp = paramiko.SFTPClient.from_transport(tran)
sftp.put("/home/1.txt", "/tmp/1.txt")
sftp.get("/tmp/1.txt", "/home/1.txt")
tran.close()
exec_command 无法应对交互式命令,用 invoke_shell():
import paramiko
import time
class UseSSH:
def __init__(self):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
private_key_path = "C:\\Users\\MIBO\\.ssh\\id_rsa"
ssh.connect(hostname="10.44.3.66", username="root", key_filename=private_key_path)
self.objssh = ssh
def exec_cmd(self, cmd):
stdin, stdout, stderr = self.objssh.exec_command(cmd)
print(stdout.read().decode())
def exec_invoke_cmd(self, k_andv: dict):
channel = self.objssh.invoke_shell()
for cmd, out in k_andv.items():
channel.send(cmd + '\n')
output = ""
while not channel.recv_ready(): # 无可读数据时阻塞
time.sleep(0.5)
while channel.recv_ready():
time.sleep(0.5) # 有些机器反应慢,多等一会
output += channel.recv(4096).decode()
print(f"** {cmd} - {'执行成功' if out in output else '执行失败'}")
print(output)
channel.close()
def __del__(self):
self.objssh.close()
通过 SOCKS5 代理转发 SSH(全部经 10.11.22.31:65157 转发到内网 192.168.124.191):
import socks, socket, paramiko
socks.set_default_proxy(socks.SOCKS5, "10.11.22.31", 65157)
socket.socket = socks.socksocket
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='192.168.124.191', port=22, username='root', password='anaxh2wn')
print(ssh.exec_command('ifconfig')[1].read().decode('utf-8'))
ssh.close()
🧰 调用系统命令:选型¶
| 方式 | 返回 | 说明 |
|---|---|---|
os.system(cmd) |
状态码(0 成功) | 拿不到输出 |
os.popen(cmd) |
文件对象 | read() 拿输出 |
subprocess.run() |
CompletedProcess |
官方推荐 |
subprocess.getstatusoutput(cmd) |
(状态码, 输出) |
直接返回元组 |
import subprocess
completed = subprocess.run(['ls', '-1'])
print('returncode:', completed.returncode)
ret, val = subprocess.getstatusoutput("ping www.baidu.com")
🗃️ 内置小型数据库(dbm / shelve)¶
| 库 | 说明 |
|---|---|
dbm |
键值对文件存储,只能持久化 bytes(字符串需 .encode()) |
shelve |
底层基于 pickle,可持久化任意对象,速度略慢 |
坑:shelve 修改可变对象(如 list.append)不改变内存地址,默认不会写回。要么整体替换,要么打开时指定 writeback=True(代价是全部对象读入内存、close() 时全部重写)。
sh = shelve.open("shelve")
sh["score"] = [80, 80, 80]
sh.close()
sh = shelve.open("shelve")
sh["score"].append(90) # 不生效
sh.close()
🔢 列表排序¶
两个长度一样的列表,一个是名称,一个是值,按值的大小排序:
🔎 过滤 filter()¶
filter(function, iterable):
function返回布尔值,用于判断iterable中的每个元素是否保留:返回True保留,返回False过滤掉。iterable是任意可迭代对象,例如列表、元组、集合等。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def is_even(number):
return number % 2 == 0
even_numbers = filter(is_even, numbers)
print(list(even_numbers)) # 输出 [2, 4, 6, 8, 10]
参考(思维导图):https://www.processon.com/embed/6615fb6b29eb742733d1ea3f
🗺️ 映射 map¶
def mul(n):
return n * n
num = [1, 2, 3, 4, 5]
result = list(map(mul, num)) # 使用 map 对列表 num 中每个元素应用 mul,返回新列表
print(result) # [1, 4, 9, 16, 25]
🧠 缓存装饰器¶
缓存函数的执行结果,并在过期时清除,以空间换时间:
import time
def auto_expiring_cache(expiration=300):
cache = {}
def decorator(func):
def wrapper(*args, **kwargs):
# 清除过期缓存
current_time = time.time()
for key, value in list(cache.items()): # list() 复制缓存条目,便于循环中删除
if current_time - value['timestamp'] >= expiration:
del cache[key]
# 生成缓存键
key = (func.__name__, str(args), str(kwargs))
print(key)
# 检查缓存中是否已有该键
if key in cache:
return cache[key]['value']
# 调用原始函数
result = func(*args, **kwargs)
# 更新缓存
cache[key] = {
'timestamp': current_time,
'value': result
}
return result
return wrapper
return decorator
@auto_expiring_cache(300)
def ccc(x, y, z):
print(f'计算中...{z}')
return x + y
🔁 生成器¶
🛡️ 异常处理装饰器¶
import traceback
def decorator(func):
# 装饰器方法:函数执行失败时打印错误信息再抛出异常
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# 打印出错的位置
print(traceback.format_exc())
return wrapper
🧵 线程¶
import threading
import time
# 第一个工作函数
def worker1():
for i in range(5):
print("Worker 1: ", i)
time.sleep(1)
# 第二个工作函数
def worker2():
for i in range(15):
print("Worker 2: ", i)
# 创建两个线程
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
📄 文件读写¶
open 模式 · tell/seek · 大文件分块读。打开模式、指针操作与读取大文件。
打开模式¶
| 模式 | 意义 | 注意事项 |
|---|---|---|
r |
只读模式打开,读文件内容的指针在文件开头 | — |
rb |
二进制只读,一般用于图片、音频等非文本文件 | — |
r+ |
读写,可从开头读也可从开头写,写入的新内容会覆盖等长原有内容 | 文件必须存在 |
rb+ |
二进制读写,指针在开头,通常针对非文本文件(如音频) | — |
w |
只写,文件存在时打开会清空原有内容 | — |
wb |
二进制只写,一般用于非文本文件(如音频) | 文件存在则清空,否则新建 |
w+ |
读写,打开后清空原有内容 | — |
wb+ |
二进制读写,一般用于非文本文件 | — |
a |
追加写,只有写入权限,指针在文件末尾;文件不存在则新建 | — |
ab |
二进制追加写,只有写权限,指针在文件末尾;文件不存在则新建 | — |
a+ |
读写追加,指针在文件末尾;文件不存在则新建 | — |
ab+ |
二进制读写追加,指针在文件末尾;文件不存在则新建 | — |
读取方式:
| 函数 | 说明 |
|---|---|
read() |
逐个字节或字符读取文件内容 |
readline() |
逐行读取 |
readlines() |
一次性读取多行,返回 list |
fileinput 模块 |
逐行读取多个文件 |
linecache 模块 |
读取文件指定行 |
文件对象提供 tell() 与 seek():tell() 判断文件指针当前位置,seek() 移动指针到指定位置。
offset 是偏移量;whence 是指针所在位置,默认为 0(开头),1 表示当前位置,2 表示文件尾。
f1 = open(file='read.txt', encoding='utf-8')
f1.read(5)
print(f1.tell()) # 指针位置为 5
f1.seek(9) # 设置指针位置为 9
print(f1.tell()) # 指针位置为 9
f1.close()
读取大文件(GB)¶
read() 会一次性读取文件的全部内容,readlines() 一次读取所有内容并按行返回 list。文件过大(如 10G)会造成 MemoryError 内存溢出。正确做法是借助 with ... as ... 上下文管理器,反复调用 read(size),每次指定读取的字节数,从而避免因文件太大而出问题。
📋 浅拷贝和深拷贝¶
| 拷贝类型 | 说明 | 示例代码 | 适用场景 |
|---|---|---|---|
| 浅拷贝 | 只复制对象的最外层,内部子对象仍为原始对象的引用 | shallow_copy = copy.copy(original_list) |
简单数据结构的复制、性能优化、保留原始数据结构的引用 |
| 深拷贝 | 递归地复制对象的所有层级,生成完全独立的新对象 | deep_copy = copy.deepcopy(original_list) |
复杂数据结构的复制、避免原始数据的修改、数据独立性要求高 |
✨ 魔术方法¶
| 魔术方法 | 作用 |
|---|---|
__new__、__init__、__del__ |
创建和销毁对象相关 |
__add__、__sub__、__mul__、__div__、__floordiv__、__mod__ |
算术运算符相关 |
__eq__、__ne__、__lt__、__gt__、__le__、__ge__ |
关系运算符相关 |
__pos__、__neg__、__invert__ |
一元运算符相关 |
__lshift__、__rshift__、__and__、__or__、__xor__ |
位运算相关 |
__enter__、__exit__ |
上下文管理器协议 |
__iter__、__next__、__reversed__ |
迭代器协议 |
__int__、__long__、__float__、__oct__、__hex__ |
类型/进制转换相关 |
__str__、__repr__、__hash__、__dir__ |
对象表述相关 |
__len__、__getitem__、__setitem__、__contains__、__missing__ |
序列相关 |
__copy__、__deepcopy__ |
对象拷贝相关 |
__call__、__setattr__、__getattr__、__delattr__ |
其他魔术方法 |
📝 日志 logging¶
自定义消息格式、记录日志的文件:
import logging
from logging.config import fileConfig
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='example.log', encoding='utf-8', level=logging.WARNING)
logging.basicConfig(format='%(asctime)s %(funcName)s%(lineno)d - %(levelname)s - %(message)s',
datefmt='%H:%M:%S')
conf = fileConfig("logging.conf")
配置文件 logging.conf:
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
参考文档:
⚠️ 踩坑速记¶
multiprocessing.Queue.qsize() · decode('gbk') · fork 启动方式。多进程与子进程输出的三个坑。
- 多进程队列长度不可靠:
multiprocessing.Queue.qsize()、empty()、full()在多进程/多线程上下文中结果不可信。 - 子进程读输出的编码:Windows 外部程序输出用
decode('gbk'),Linux 一般utf-8。 - 启动方式:Python 3.12 起
fork不再是多进程默认启动方式,3.14 起默认不再为 fork;多线程环境下os.fork()会触发 DeprecationWarning。