一 功能简介
①后端逻辑【cry使用。】
1.登录
1.读取redis,判断是否需要使用验证码
2.读取redis,校验用户登录ip是否在黑名单内,异常退出
3.读取redis,校验用户是否被锁定,异常退出
4.判断是否开启验证码校验,如果开启:读取redis,判断是否验证码失效/验证码错误,异常退出
5.读取mysql,校验用户是否存在,异常退出
6.输入的密码进行bcrypt加密校验(单向不可解密、只能校验明文和DB的密文是否匹配),异常退出
7.密码输入错误次数达到上限,写入redis锁定,并异常退出
8.用户停用(前面mysql查询到的),异常退出
9.JWT加密(载荷包含用户信息、session_id(uuid)、过期时间),生成token,存入redis(key为session_id)
10.更新mysql用户信息(最新登录时间)
JWT用来登录的原因:因此支持跨域(头部传输)、安全(使用签名保证数据完整)、灵活(载荷自定义)
2.退出
1.解码token
2.删除redis中,session_id对应的token
3.验证码
1.创建并返回验证码图片、答案数据
2.正确结果存入redis,验证码图片转base64传给用户
4.导出
1.数据查询,并转换成 list(dict())的格式
2.替换excel值(例如:0->男 1->女)
3.替换excel标题(英文转中文)
4.将需要导出的list数据,转化为excel对象,对应的二进制数据流
5.前端对二进制数据流进行格式转换成excel
5.导入
1.读取excel数据,并转换成二进制数据流
2.修改导入excel的列名称
3.替换excel值(例如:男->0 女->1)
4.循环新增数据,根据请求参数“是否更新”,存入db
6.上传图片
1.后端
a.设置存储位置和图片名称
b.将请求参数 图片二进制数据 存入指定目录
c.修改用户数据
二 常用工具/包
1.pip
配置pip永久使用清华源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
清华源安装
pip install flask_pymongo -i https://pypi.tuna.tsinghua.edu.cn/simple
查看镜像
pip config list
升级pip
pip install pip -U
更新包
pip install --upgrade mysql-connector-python
导出包
pip freeze > requirements.txt
导入包
pip install -r requirements.txt
pip install bcrypt==4.1.2
pip list
更新pip
python.exe -m pip install --upgrade pip
清理缓存
python.exe -m pip cache purge
2.虚拟环境
python -m venv venv
.\venv\Scripts\activate.bat # 激活了这个虚拟环境,使得在这个虚拟环境中运行的 Python 程序可以独立于系统中的其他 Python 程序
python -m pip install -r requirement.txt
创建一个名为venv的Python虚拟环境
运行并激活该虚拟环境
安装依赖包
(python -m:执行模块)
3.随机
1.生成指定长度的字符串(大小写字母+数字):‘’.join(random.choices(string.ascii_letters + string.digits, k=10))
2.随机 [1, 3] 整数:random.randint(1,3)
3.随机 [1, 4] 小数,保留6位:round(random.uniform(1, 4), 6)
4.随机列表中的值:random.choice([1,2,3])
4.文件操作
# 获取项目根路径 E:\sz_autotest
1、os.getcwd()
2、os.path.dirname(__file__)
# 判断当前某个目录是否有指定文件(logs),没有就创建
log_path = os.path.join(os.getcwd(), 'logs')
if not os.path.exists(log_path):
os.mkdir(log_path)
# 项目路径+文件名,组成文件绝对路径 C:\Users\Administrator\Desktop\cts\assets\font\Arial.ttf(当前文件在cts目录下)
print(os.path.join(os.getcwd(), 'assets', 'font', 'Arial.ttf'))
# 获取函数路径
import inspect
file_path = inspect.getfile(func)
# 获取相对路径 documents\file.txt
path = "/home/user/documents/file.txt"
start = "/home/user"
relative_path = os.path.relpath(path, start)
print(relative_path)
# 是否存在demo目录/路径
os.path.exists("demo")
# 创建demo目录
os.makedirs("demo")
# 分隔绝对路径为 路径+文件名
directory, filename = os.path.split('C:\\Users\\Administrator\\Desktop\\jmeter.png')
# 文件重命名
os.rename(photo_local_path, new_path)
# 文件重命名
directory, filename = os.path.split(photo_local_path)
new_path = os.path.join(directory, str(datetime.datetime.now()).replace(":", "_") + ".jpg")
os.rename(photo_local_path, new_path)
5.通知
1.飞书(常用:飞书开放平台、消息卡片搭建工具)
import datetime
import time
from requests_toolbelt import MultipartEncoder
import requests
import json
import os
import shutil
dir_addr = "feishu" # 文件存储地址
photo_addr = "photo" # 图片存储地址
run_log_addr = "run_log.txt" # 日志文件名称
def get_feishu_tenant_access_token(app_id: str, app_secret: str) -> str:
"""
(第一步)获取应用令牌
:param app_id: App ID(开发者后台 - 企业自建应用 - 应用凭证)
:param app_secret: App Secret(同)
:return tenant_access_token
"""
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
data = {
"app_id": app_id,
"app_secret": app_secret
}
response = requests.post(url, data=data)
print(f"\033[94m获取应用令牌:\033[0m{response.text}")
if not os.path.exists(dir_addr): os.makedirs(dir_addr)
with open(os.path.join(dir_addr, run_log_addr), "a") as f:
f.write(f"\n{datetime.datetime.now()} - 获取应用令牌:{response.text}")
return response.json().get("tenant_access_token")
def get_feishu_open_id(mobile_list: list, tenant_access_token: str) -> str:
"""
(第二步)获取用户在应用中的 open_id(同一个用户在不同应用中的 Open ID 不同)
:param mobile_list: 手机号,例如 ["15267292029"]
:param tenant_access_token: 应用令牌
"""
url = "https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id"
headers = {
'Authorization': f'Bearer {tenant_access_token}',
'Content-Type': "application/json; charset=utf-8"
}
data = {
"mobiles": mobile_list, # ou_76eae976ce42b4b86fe04cad7e482b36
"include_resigned": True
}
response = requests.post(url, headers=headers, json=data)
print(f"\033[94m获取用户在应用中的 open_id:\033[0m{response.json()}")
if not os.path.exists(dir_addr): os.makedirs(dir_addr)
with open(os.path.join(dir_addr, run_log_addr), "a") as f:
f.write(f"\n{datetime.datetime.now()} - 获取用户在应用中的 open_id:{response.json()}")
return response.json().get("data").get("user_list")[0].get("user_id")
def feishu_upload_photo(tenant_access_token: str, photo_local_path: str) -> str:
"""
上传图片至飞书
:param tenant_access_token: 应用令牌
:param photo_local_path: 图片本地地址。例如:'C:\\Users\\Administrator\\Desktop\\jmeter.png'
:return: img_key,用于消息卡片展示照片
"""
# 1、更新文件名称
directory, filename = os.path.split(photo_local_path)
new_path = os.path.join(directory, str(datetime.datetime.now()).replace(":", "_") + ".jpg")
os.rename(photo_local_path, new_path)
# 2、请求
url = "https://open.feishu.cn/open-apis/im/v1/images"
form = {
'image_type': 'message',
'image': (open(new_path, 'rb'))
}
headers = {
'Authorization': f'Bearer {tenant_access_token}'
}
multi_form = MultipartEncoder(form)
headers['Content-Type'] = multi_form.content_type
response = requests.request("POST", url, headers=headers, data=multi_form)
# print(response.headers['X-Tt-Logid']) # for debug or oncall
print(f"\033[94m上传照片至飞书:\033[0m{response.json()}")
if not os.path.exists(dir_addr): os.makedirs(dir_addr)
with open(os.path.join(dir_addr, run_log_addr), "a") as f:
f.write(f"\n{datetime.datetime.now()} - 上传照片至飞书:{response.json()}")
return response.json().get("data").get("image_key")
def feishu_robot_send_message(webhook_addr: str, send_content: dict = None):
"""
飞书群机器人发送消息
:param webhook_addr: 群机器人 webhook
:param send_content: 发送内容
"""
if not send_content:
send_content = {
"msg_type": "interactive",
"card": {
"config": {
"wide_screen_mode": True
},
"elements": [
{
"alt": {
"content": "",
"tag": "plain_text"
},
"img_key": "img_v2_cb03ec35-a638-4b93-9e6f-5e2d0e549deg",
"tag": "img"
},
{
"tag": "div",
"text": {
"content": "📬 填写问卷,晒出你的珍藏好书\n😍 想知道其他人都推荐了哪些好书?马上[入群围观](https://open.feishu.cn/)\n📝 用[读书笔记模板](https://open.feishu.cn/)(桌面端打开),记录你的心得体会\n🙌 更有惊喜特邀嘉宾 4月12日起带你共读",
"tag": "lark_md"
}
}
],
"header": {
"template": "green",
"title": {
"content": "📊 如果表格权限更细化,你希望怎么用它?",
"tag": "plain_text"
}
}
}
}
return_data = requests.post(webhook_addr, json=send_content)
print(f"\033[94m飞书群机器人发送消息:\033[0m{return_data.json()}")
if not os.path.exists(dir_addr): os.makedirs(dir_addr)
with open(os.path.join(dir_addr, run_log_addr), "a") as f:
f.write(f"\n{datetime.datetime.now()} - 飞书群机器人发送消息:{return_data.json()}")
def feishu_send_message(tenant_access_token: str, open_id: str, message: str):
"""
飞书应用机器人发送消息
:param tenant_access_token: 应用令牌
:param open_id: 用户在应用中的 open_id
"""
url = "https://open.feishu.cn/open-apis/im/v1/messages"
params = {"receive_id_type": "open_id"}
message_content = {
"text": message,
}
req = {
"receive_id": open_id,
"msg_type": "text",
"content": json.dumps(message_content)
}
headers = {
'Authorization': f'Bearer {tenant_access_token}',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, params=params, headers=headers, data=json.dumps(req))
print(f"\033[94m飞书应用机器人发送消息:\033[0m{response.json()}")
if not os.path.exists(dir_addr): os.makedirs(dir_addr)
with open(os.path.join(dir_addr, run_log_addr), "a") as f:
f.write(f"\n{datetime.datetime.now()} - 飞书应用机器人发送消息:{response.json()}")
if __name__ == '__main__':
""" 1、获取飞书应用令牌(苏州工厂) """
# get_feishu_tenant_access_token("cli_a5f17d4b93389013", "ZbfbEOteWCOPwxlw3Gxt0fIwmDsuTqZV")
""" 2、获取用户在应用中的 open_id(苏州工厂) """
# tenant_access_token = get_feishu_tenant_access_token("cli_a5f17d4b93389013", "ZbfbEOteWCOPwxlw3Gxt0fIwmDsuTqZV")
# get_feishu_open_id(["15267292029"], tenant_access_token)
""" 【3】、飞书群机器人发送消息(监控测试群1) """
# send_content = None
# feishu_robot_send_message("https://open.feishu.cn/open-apis/bot/v2/hook/5605425e-f6ac-4560-9349-6f394b3dc2ed", send_content)
""" 【4】、上传图片,然后3 """
# tenant_access_token = get_feishu_tenant_access_token("cli_a5f17d4b93389013", "ZbfbEOteWCOPwxlw3Gxt0fIwmDsuTqZV")
# img_key = feishu_upload_photo(tenant_access_token, r"C:\Users\Administrator\Pictures\1.jpg")
# send_content = {
# "msg_type": "interactive",
# "card": {
# "config": {
# "wide_screen_mode": True
# },
# "elements": [
# {
# "alt": {
# "content": "",
# "tag": "plain_text"
# },
# "img_key": "img_v3_028o_1fbfe24a-d610-4493-91e6-878d7d0de26g",
# "tag": "img"
# },
# {
# "tag": "div",
# "text": {
# "content": "📬 填写问卷,晒出你的珍藏好书\n😍 想知道其他人都推荐了哪些好书?马上[入群围观](https://open.feishu.cn/)\n📝 用[读书笔记模板](https://open.feishu.cn/)(桌面端打开),记录你的心得体会\n🙌 更有惊喜特邀嘉宾 4月12日起带你共读",
# "tag": "lark_md"
# }
# }
# ],
# "header": {
# "template": "green",
# "title": {
# "content": "📊 如果表格权限更细化,你希望怎么用它?",
# "tag": "plain_text"
# }
# }
# }
# } # 替换其中的img_key
# feishu_robot_send_message("https://open.feishu.cn/open-apis/bot/v2/hook/5605425e-f6ac-4560-9349-6f394b3dc2ed", send_content)
""" 【5】、应用发送消息,需要1+2(苏州工厂) """
tenant_access_token = get_feishu_tenant_access_token("cli_a5f17d4b93389013", "ZbfbEOteWCOPwxlw3Gxt0fIwmDsuTqZV")
open_id = get_feishu_open_id(["15267292029"], tenant_access_token)
message = "测试文本"
feishu_send_message(tenant_access_token, open_id, message)
6.字符提取
1、分隔
# 是否r_开头
ch.startswith('r_')
# 按_分隔成多个字符串列表
ch.split('_')
2/、正则
re.findall(r'XXX', content)
# 提取出$开头、$结尾的中间内容(例如:$90$,返回['90'],多个时同样适用!)
re.findall(r"\$([^\$]+)\$", value)
去除字符串中,所有以#开头的内容(替换的模式,替换后的内容,要处理的字符串)
# content = re.sub(r'#.*', '', content)
0次或多次:.*
# (例如:"random-1:10:14")
r'\d+' # 匹配出所有数字,['1', '10', '14']
r'(\d+):(\d+)' # (\d+)匹配出一个或多个数字,[('1', '10')]
r'(\d+)(?::(\d+))*' # [('1', '14')]
7.时间
from datetime import datetime, timedelta
# 字符串转datetime,并规范格式(2024-03-13 19:44:00)
time = datetime.strptime("20240313194400", '%Y%m%d%H%M%S')
# datetime格式时间,递增
current_time += timedelta(minutes=3)
days: 表示天数
seconds: 表示秒数
microseconds: 表示微秒数
milliseconds: 表示毫秒数
hours: 表示小时数
weeks: 表示周数
# datetime转字符串
time.strftime('%Y-%m-%d %H:%M:%S')
8.图像
1.生成base64图像(参考字体网址:https://izihun.com/fontxiazai/ziti-66.html)
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont
import io
import os
import random
import base64
def create_captcha_image_service():
"""
通过字体文件,例如:Arial.ttf,生成图片(可用于验证码)
"""
# 创建空白图像
image = Image.new('RGB', (160, 60), color='#EAEAEA') # 宽160、高60、背景灰色
# 创建绘图对象
draw = ImageDraw.Draw(image)
# 设置字体
font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'Arial.ttf'), size=30)
# 生成两个0-9之间的随机整数
num1 = random.randint(0, 9)
num2 = random.randint(0, 9)
# 从运算符列表中随机选择一个
operational_character_list = ['+', '-', '*']
operational_character = random.choice(operational_character_list)
# 根据选择的运算符进行计算
if operational_character == '+':
result = num1 + num2
elif operational_character == '-':
result = num1 - num2
else:
result = num1 * num2
# 绘制文本
text = f"{num1} {operational_character} {num2} = ?"
draw.text((25, 15), text, fill='blue', font=font) # 左上角为零点,向右25,向下15
# 将图像数据保存到内存中
buffer = io.BytesIO()
image.save(buffer, format='PNG')
# 将图像数据转换为base64字符串
base64_string = base64.b64encode(buffer.getvalue()).decode()
return [base64_string, result]
if __name__ == '__main__':
print(create_captcha_image_service())
9.编码/加密
1.字符串转换MD5(32位字符串)
import hashlib
data = “1”
md = hashlib.md5() # 创建一个 MD5 对象
md.update(data.encode(‘utf-8’)) # 将请求中的密码进行 UTF-8 编码后,更新到 MD5 对象中
print(md.hexdigest()) # 打印 MD5 对象的哈希值
2.生成UUID(包含 32 个十六进制数字,以连字符分隔成五段)
import uuid
print(uuid.uuid4()) # f1b28e75-1b60-4d08-ba02-8c3c5c3d44c3
print(str(uuid.uuid4()).replace(“-”, “”))
3.base64
import base64
# 原始数据库连接字符串
original_string = '123'
# 编码字符串
encoded_bytes = base64.b64encode(original_string.encode('utf-8'))
encoded_string = encoded_bytes.decode('utf-8')
print("加密后的字符串:", encoded_string)
# 解码字符串
decoded_bytes = base64.b64decode(encoded_string.encode('utf-8'))
decoded_string = decoded_bytes.decode('utf-8')
print("解密后的字符串:", decoded_string)
4.bcrypt
import bcrypt
password = "admin123"
# 使用bcrypt库对密码进行哈希
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
# hashed_password = "$2b$12$YANgUhWqLaP0Xs3gwMi1H.u1ZXhlmnv6o8o/uTmVH4VnEUFRWmb0i"
print(hashed_password)
# 验证密码是否匹配哈希后的密码
if bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8')):
print("密码匹配")
else:
print("密码不匹配")
5.jwt
from jose import jwt # 编码方法
import uuid
from datetime import timedelta, datetime
from typing import Union
class JwtSettings:
jwt_secret_key: str = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55'
jwt_algorithm: str = 'HS256'
jwt_expire_minutes: int = 1440
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
"""
根据登录信息创建当前用户token
:param data: 登录信息
:param expires_delta: token有效期
:return: token
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=30)
to_encode.update({"exp": expire})
print(data)
# 签名(载荷、秘钥、算法)
encoded_jwt = jwt.encode(to_encode, JwtSettings.jwt_secret_key, algorithm=JwtSettings.jwt_algorithm)
return encoded_jwt
if __name__ == '__main__':
# 编码
access_token_expires = timedelta(minutes=JwtSettings.jwt_expire_minutes) # 一个时间间隔,表示访问令牌的过期时间为从当前时间开始的指定分钟数(1 day, 0:00:00 <class 'datetime.timedelta'>)
access_token = create_access_token(
data={
"user_id": str("1"),
"user_name": "ch",
"dept_name": "深圳研发部门",
"session_id": str(uuid.uuid4()),
"login_info": None
},
expires_delta=access_token_expires
)
print(access_token)
print("——————————————————")
# 解码
payload = jwt.decode(access_token, JwtSettings.jwt_secret_key, algorithms=[JwtSettings.jwt_algorithm])
print(payload)
"""
{
"alg": "HS256",
"typ": "JWT"
}
{
"user_id": "1",
"user_name": "ch",
"dept_name": "深圳研发部门",
"session_id": "f981d4f7-98bb-48a2-a88a-b86dcff17e35",
"login_info": null,
"exp": 1712323289
}
"""
10.数据库
1.mysql
import mysql.connector
from decimal import Decimal
from datetime import datetime
# 1、连接到MySQL数据库
mydb = mysql.connector.connect(
host="10.44.218.28",
port="5606",
user="root",
password="Server@2014Server",
database="intellforge_c2m"
)
# 2、创建一个游标对象
mycursor = mydb.cursor()
# 3、查询
mycursor.execute("SELECT * FROM t_order") # 执行查询
result = mycursor.fetchall() # 获取所有行
mycursor.execute("SHOW COLUMNS FROM t_order;")
columns = mycursor.fetchall()
# 4、处理Decimal和datetime数据类型
def convert_data(value):
if isinstance(value, Decimal):
return float(value)
elif isinstance(value, datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
else:
return value
# 5、打印字段信息
print([column[0] for column in columns])
for row in result:
formatted_row = [convert_data(value) for value in row]
print(formatted_row)
# 6、关闭游标和数据库连接
mycursor.close()
mydb.close()
11.日志
1.loguru(cry使用。配置少,功能齐全)
import os
import time
from loguru import logger
log_path = os.path.join(os.getcwd(), 'logs')
if not os.path.exists(log_path):
os.mkdir(log_path)
# 设置日志地址
log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log')
# 配置:50MB轮转、enqueue=True启用异步写入(提高性能)、设置日志文件的压缩格式为ZIP(日志文件进行轮转时,旧的日志文件将被压缩为ZIP格式)
logger.add(log_path_error, rotation="50MB", encoding="utf-8", enqueue=True, compression="zip")
if __name__ == '__main__':
logger.debug("输出") # 输出调试信息
logger.info("输出") # 输出信息性消息
logger.success("输出") # 输出成功信息
logger.warning("输出") # 输出警告信息
logger.error("输出") # 输出错误信息
try:
a = 1 / 0
except Exception as e:
logger.exception(e) # 输出错误(异常)信息
logger.critical("输出") # 输出严重错误信息
2.logging.config(配置较多)
"""
time: 2023-12-30
author: chenghao
function: 日志配置
"""
import logging.config
import os
import colorlog
log_addr = "demo"
log_file_name = 'log_demo.log'
log_file_count = 10
log_file_maxBytes = 1024 * 1024 * 10 # 10MB
def log_file_name_func(log_addr, log_file_name):
""" 目录不存在时创建目录 """
if not os.path.exists(log_addr): os.makedirs(log_addr)
return log_addr + "/" + log_file_name
LOGGING_DIC = {
'version': 1.0,
'disable_existing_loggers': False, # 禁用已存在的日志记录器
'formatters': {
'console_color': {
'()': colorlog.ColoredFormatter,
# 2024-03-29 13:43:30 MainThread:3640 [运行日志] INFO [C:\Users\Administrator\Desktop\cts\测试3.py:93] [<module>] 运行消息
'format': '%(log_color)s%(asctime)s %(threadName)s:%(thread)d [%(name)s] %(levelname)s [%(pathname)s:%(lineno)d] [%(funcName)s] %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
'log_colors': {
'DEBUG': 'cyan',
'INFO': 'green',
'ERROR': 'red',
}
},
'file': {
'format': '%(asctime)s %(threadName)s:%(thread)d [%(name)s] %(levelname)s [%(pathname)s:%(lineno)d] [%(funcName)s] %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'console_color_simple': {
'()': colorlog.ColoredFormatter,
'format': '%(log_color)s%(asctime)s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
'log_colors': {
'DEBUG': 'cyan',
'INFO': 'green',
'ERROR': 'red',
}
},
'file_simple': {
'format': '%(asctime)s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
'handlers': {
'console_handler': {
'level': 10, # 日志处理的级别限制
'class': 'logging.StreamHandler', # 输出到终端
'formatter': 'console_color' # 日志格式
},
'file_handler': {
'level': 10,
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,日志轮转
'filename': log_file_name_func(log_addr, log_file_name),
'maxBytes': log_file_maxBytes, # 日志大小
'backupCount': log_file_count, # 日志文件保存数量限制
'encoding': 'utf-8',
'formatter': 'file',
},
'console_simple_handler': {
'level': 10, # 日志处理的级别限制
'class': 'logging.StreamHandler', # 输出到终端
'formatter': 'console_color_simple' # 日志格式
},
'file_simple_handler': {
'level': 10,
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,日志轮转
'filename': log_file_name_func(log_addr, log_file_name),
'maxBytes': log_file_maxBytes, # 日志大小
'backupCount': log_file_count, # 日志文件保存数量限制
'encoding': 'utf-8',
'formatter': 'file_simple',
},
},
'loggers': {
'调试日志': {
'handlers': ['console_handler'],
'level': 'DEBUG',
'propagate': False,
},
'运行日志': {
'handlers': ['console_handler', 'file_handler'],
'level': 'INFO',
'propagate': False,
},
'错误日志': {
'handlers': ['console_handler', 'file_handler'],
'level': 'ERROR',
'propagate': False,
},
'启动日志': {
'handlers': ['console_simple_handler', 'file_simple_handler'],
'level': 'INFO',
'propagate': False,
},
}
}
if __name__ == '__main__':
logging.config.dictConfig(LOGGING_DIC)
LOG_DEBUG = logging.getLogger('调试日志')
LOG_DEBUG.debug('调试消息')
LOG_RUN = logging.getLogger('运行日志')
LOG_RUN.info('运行消息')
LOG_ERROR = logging.getLogger('错误日志')
LOG_ERROR.error('错误消息')
LOG_START = logging.getLogger('启动日志')
LOG_START.info(r"""
____ ____ __ __
/\ _`\ /\ _`\ /\ \ /\ \
\ \ \/\_\\ \ \L\ \ `\`\\/'/
\ \ \/_/_\ \ , /`\ `\ /'
\ \ \L\ \\ \ \\ \ `\ \ \
\ \____/ \ \_\ \_\ \ \_\
\/___/ \/_/\/ / \/_/
* CRY Service Startup""")
2.cry日志装饰器
# -*- coding: utf-8 -*-
from functools import wraps
from fastapi import Request
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
import inspect
import os
import json
import time
from datetime import datetime
import requests
from user_agents import parse # 解析用户代理字符串
from typing import Optional
from module_admin.service.login_service import LoginService
from module_admin.service.log_service import OperationLogService, LoginLogService
from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel
def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'):
"""
日志装饰器, 获取 被装饰的函数、请求、响应、运行时间等信息, 并存入数据库
:param title: 当前日志装饰器装饰的模块标题
:param business_type: 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据)
:param log_type: 日志类型(login表示登录日志,为空表示为操作日志)
:return:
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
# 获取被装饰函数的文件路径
file_path = inspect.getfile(func)
# 获取项目根路径
project_root = os.getcwd()
# 处理文件路径,去除项目根路径部分
relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.')
# 获取当前被装饰函数所在路径 当前文件相对路径.使用装饰器的函数()
func_path = f'{relative_path}{func.__name__}()'
# 获取上下文信息
request: Request = kwargs.get('request') # fastapi中,路由函数【async def delete_system_user(request: Request,】的请求参数
token = request.headers.get('Authorization')
query_db = kwargs.get('query_db')
request_method = request.method
user_agent = request.headers.get('User-Agent')
# Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
operator_type = 0 # web还是移动端
if "Windows" in user_agent or "Macintosh" in user_agent or "Linux" in user_agent:
operator_type = 1
if "Mobile" in user_agent or "Android" in user_agent or "iPhone" in user_agent:
operator_type = 2
# 获取请求的url
oper_url = request.url.path
# 获取请求的ip及ip归属区域
oper_ip = request.headers.get("X-Forwarded-For")
oper_location = '内网IP'
# 根据ip获取位置
try:
if oper_ip != '127.0.0.1' and oper_ip != 'localhost':
ip_result = requests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={oper_ip}')
# {"code": "Success",
# "data": {"continent": "亚洲", "country": "中国", "zipcode": "215100", "timezone": "UTC+8", "accuracy": "区县", "owner": "中国电信",
# "isp": "中国电信", "source": "数据挖掘", "areacode": "CN", "adcode": "320506", "asnumber": "4809", "lat": "31.249176",
# "lng": "120.628282", "radius": "48.9087", "prov": "江苏省", "city": "苏州市", "district": "吴中区"}, "charge": true,
# "msg": "查询成功", "ip": "222.92.231.44", "coordsys": "WGS84"}
if ip_result.status_code == 200:
prov = ip_result.json().get('data').get('prov')
city = ip_result.json().get('data').get('city')
if prov or city:
oper_location = f'{prov}-{city}'
else:
oper_location = '未知'
else:
oper_location = '未知'
except Exception as e:
oper_location = '未知'
print(e)
finally:
# 根据不同的请求类型使用不同的方法获取请求参数
content_type = request.headers.get("Content-Type")
if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type):
payload = await request.form()
oper_param = "\n".join([f"{key}: {value}" for key, value in payload.items()])
else:
payload = await request.body()
# 通过 request.path_params 直接访问路径参数
path_params = request.path_params
oper_param = {}
if payload:
oper_param.update(json.loads(str(payload, 'utf-8')))
if path_params:
oper_param.update(path_params)
oper_param = json.dumps(oper_param, ensure_ascii=False)
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
if len(oper_param) > 2000:
oper_param = '请求参数过长'
# 获取操作时间
oper_time = datetime.now()
# 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息
login_log = {}
if log_type == 'login':
user_agent_info = parse(user_agent) # PC / Windows 10 / Chrome 111.0.0
browser = f'{user_agent_info.browser.family} {user_agent_info.browser.version[0]}' # Chrome 111
system_os = f'{user_agent_info.os.family} {user_agent_info.os.version[0]}' # Windows 10
login_log = dict(
ipaddr=oper_ip, # 请求ip
loginLocation=oper_location, # 请求位置
browser=browser,
os=system_os,
loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S')
)
kwargs['form_data'].login_info = login_log
# 调用原始函数
result = await func(*args, **kwargs)
# 获取请求耗时
cost_time = float(time.time() - start_time) * 100
# 判断请求是否来自api文档
request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
# 根据响应结果的类型使用不同的方法获取响应结果参数
if isinstance(result, JSONResponse) or isinstance(result, ORJSONResponse) or isinstance(result, UJSONResponse):
result_dict = json.loads(str(result.body, 'utf-8'))
else:
if request_from_swagger or request_from_redoc:
result_dict = {}
else:
if result.status_code == 200:
result_dict = {'code': result.status_code, 'message': '获取成功'}
else:
result_dict = {'code': result.status_code, 'message': '获取失败'}
json_result = json.dumps(result_dict, ensure_ascii=False)
# 根据响应结果获取响应状态及异常信息
status = 1
error_msg = ''
if result_dict.get('code') == 200:
status = 0
else:
error_msg = result_dict.get('msg')
# 根据日志类型向对应的日志表插入数据
if log_type == 'login':
# 登录请求来自于api文档时不记录登录日志,其余情况则记录
if request_from_swagger or request_from_redoc:
pass
else:
user = kwargs.get('form_data')
user_name = user.username
login_log['loginTime'] = oper_time
login_log['userName'] = user_name
login_log['status'] = str(status)
login_log['msg'] = result_dict.get('msg')
LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log))
else:
current_user = await LoginService.get_current_user(request, token, query_db)
oper_name = current_user.user.user_name
dept_name = current_user.user.dept.dept_name if current_user.user.dept else None
operation_log = OperLogModel(
# 装饰器输入参数
title=title,
businessType=business_type,
method=func_path,
requestMethod=request_method,
operatorType=operator_type,
operName=oper_name,
deptName=dept_name,
operUrl=oper_url,
operIp=oper_ip,
operLocation=oper_location,
operParam=oper_param,
jsonResult=json_result,
status=status,
errorMsg=error_msg,
operTime=oper_time,
costTime=int(cost_time)
)
OperationLogService.add_operation_log_services(query_db, operation_log)
return result
return wrapper
return decorator
@log_decorator("测试", business_type=0)
async def func_demo():
return "测试"
if __name__ == '__main__':
import asyncio
result = asyncio.run(func_demo())
print(result)
12.颜色文本
print(“\033[31m打印内容\033[0m”)
- 前景色(文本):
- 30:黑色
- 31:红色
- 32:绿色
- 33:黄色
- 34:蓝色
- 35:洋红色
- 36:青色
- 37:白色
- 背景色:
- 40:黑色
- 41:红色
- 42:绿色
- 43:黄色
- 44:蓝色
- 45:洋红色
- 46:青色
- 47:白色
13.excel
1.导出【cry使用。】(较复杂,可以使用方式2)
from typing import List
import pandas as pd
import io
def export_list2excel(list_data: List):
"""
工具方法:将需要导出的list数据转化为对应excel的二进制数据
:param list_data: 数据列表
:return: 字典信息对应excel的二进制数据
"""
# 创建表格处理对象
df = pd.DataFrame(list_data) # 将数据列表 list_data 转换为 Pandas 的 DataFrame 对象 df。DataFrame 是 Pandas 中用于处理表格数据的主要数据结构
# 创建存储二进制数据的对象
binary_data = io.BytesIO() # 用于在内存中存储二进制数据
# 将 DataFrame df 写入到 Excel 文件格式,并将结果存储在 binary_data 中
df.to_excel(binary_data, index=False, engine='openpyxl') # 参数表示不包含行索引、使用 openpyxl 引擎来处理 Excel 文件
# 转换为二进制形式
binary_data = binary_data.getvalue()
return binary_data
class UserService:
"""
用户管理模块服务层
"""
@staticmethod
def export_user_list_services(user_list: List):
"""
导出用户信息service
:param user_list: 用户信息列表
:return: 用户信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
"userId": "用户编号",
"userName": "用户名称",
"nickName": "用户昵称",
"deptName": "部门",
"email": "邮箱地址",
"phonenumber": "手机号码",
"sex": "性别",
"status": "状态",
"createBy": "创建者",
"createTime": "创建时间",
"updateBy": "更新者",
"updateTime": "更新时间",
"remark": "备注",
}
data = user_list
for item in data:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
if item.get('sex') == '0':
item['sex'] = '男'
elif item.get('sex') == '1':
item['sex'] = '女'
else:
item['sex'] = '未知'
# 替换key为中文键(列表首行)
new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data]
# 将需要导出的list数据转化为对应excel的二进制数据
binary_data = export_list2excel(new_data)
return binary_data
if __name__ == '__main__':
# 创建一个示例用户列表
user_list = [
{
"userId": "1",
"userName": "Alice",
"nickName": "Ali",
"deptName": "IT",
"email": "alice@example.com",
"phonenumber": "1234567890",
"sex": "0",
"status": "0",
"createBy": "Admin",
"createTime": "2022-01-01",
"updateBy": "Admin",
"updateTime": "2022-01-02",
"remark": "Example user"
},
{
"userId": "2",
"userName": "Bob",
"nickName": "Bobby",
"deptName": "HR",
"email": "bob@example.com",
"phonenumber": "9876543210",
"sex": "1",
"status": "1",
"createBy": "Admin",
"createTime": "2022-01-03",
"updateBy": "Admin",
"updateTime": "2022-01-04",
"remark": "Another example user"
}
]
# 使用 UserService 类导出用户信息并获取二进制数据
binary_data = UserService.export_user_list_services(user_list)
# 将二进制数据读取为 DataFrame
excel_data = io.BytesIO(binary_data)
df = pd.read_excel(excel_data, engine='openpyxl')
# 将 DataFrame 写入到 Excel 文件
df.to_excel("output.xlsx", index=False, engine='openpyxl')
2.简版导出
import pandas as pd
import io
from typing import List
def export_list2excel(list_data: List):
"""
工具方法:将需要导出的list数据转化为对应excel的二进制数据
:param list_data: 数据列表
:return: 字典信息对应excel的二进制数据
"""
df = pd.DataFrame(list_data)
binary_data = io.BytesIO()
df.to_excel(binary_data, index=False, engine='openpyxl')
binary_data = binary_data.getvalue()
return binary_data
if __name__ == '__main__':
data = export_list2excel([{"name": 2, "id": 1}, {"name": 3, "id": 4}]) # key为首行
print(data)
with open('output.xlsx', 'wb') as file:
file.write(data)
14.爬虫
15.Mysql
import mysql.connector
from decimal import Decimal
from datetime import datetime
class Mysql:
def __init__(self, config_dict):
# 1、连接到MySQL数据库
self.mydb = mysql.connector.connect(**config_dict)
# 2、创建一个游标对象
self.mycursor = self.mydb.cursor()
def select(self):
"""
查询数据,
不同场景需要修改!!
"""
self.mycursor.execute("SELECT * FROM t_order") # 执行查询
result = self.mycursor.fetchall() # 获取所有行
self.mycursor.execute("SHOW COLUMNS FROM t_order;")
columns = self.mycursor.fetchall()
# 处理Decimal和datetime数据类型
def convert_data(value):
if isinstance(value, Decimal):
return float(value)
elif isinstance(value, datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
else:
return value
# 打印字段信息(列表)
# print([column[0] for column in columns])
# for row in result:
# formatted_row = [convert_data(value) for value in row]
# print(formatted_row)
# (字典)
for row_index in range(len(result)):
row_dict = {}
for index, column in enumerate(columns):
row_dict.update({column[0]: convert_data(result[row_index][index])})
print(row_dict)
def insert(self):
"""
插入数据,
不同场景需要修改!!
"""
sheet_head_str = "id, first_inspection_code, erp_order_code, batch_number, product_code, product_name, product_specifications, work_line_code, equipment_code, inspection_result, total_quantity, right_quantity, exception_quantity, op_user_number, op_user_name, role, inspection_completion_time, create_user, create_time, update_user, update_time, virtual_create_time, virtual_update_time, env_id"
sql = "INSERT INTO t_first_inspection_batch (" + sheet_head_str + ") VALUES (" + ", ".join(['%s'] * len(sheet_head_str.split(', '))) + ")"
val = []
for index in range(8, 22):
val.append((str(index), "MESSJ2024041800056", "IQEOC202404180005", "IQMBC2024041800106", "1778337265580380160", "联想笔记本001", "222",
"1778326199429369856", "", "2", "3", "1", "2", "wb00780", "刘双双", "质检员", "2024-04-22 17:14:57", "wb00780", "2024-04-22 13:16:59",
"wb00780", "2024-04-25 13:47:11", "2024-04-22 13:16:59", "2024-04-25 13:47:11", "0"))
print(val)
self.mycursor.executemany(sql, val)
self.mydb.commit()
def print_sheet_head_str(self):
"""
打印表各字段吗,例如:id, first_inspection_code
不同场景需要修改!!
"""
self.mycursor.execute("SHOW COLUMNS FROM t_first_inspection_batch")
print(", ".join(["'" + column[0] + "'" for column in self.mycursor.fetchall()]))
def exit(self):
# 6、关闭游标和数据库连接
self.mycursor.close()
self.mydb.close()
if __name__ == '__main__':
m = Mysql({"host": "10.44.218.28", "port": "5606",
"user": "root", "password": "Server@2014Server", "database": "intellforge_mes"})
m.insert() # 不同增删改查 需要修改这个方法
m.exit()
三 基础语法
1 python基础
1. Pydantic 模型
功能
数据验证和转换(不存入模型中没有的属性,取模型中没有的属性返回None)
序列化和反序列化
ORM 模型
案例
步骤:定义模型数据结构、创建对象、访问对象数据
注意点:定义和打印单个对象,使用下划线;创建对象时,使用小驼峰
额外
创建空实例:empty_dept1 = DeptModel()、empty_dept2 = DeptModel(**{}),两者等价,如果模型没有默认值,会报错
实例对象转换格式:dict(实例对象)
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel # 用于转换成小驼峰
from typing import List, Optional
class PageResponseModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel) # 属性的下划线,会被转换成小驼峰命名
rows: List = []
page_num: Optional[int] = None
page_size: Optional[int] = None
total: int
has_next: Optional[bool] = None
# 创建一个示例对象
page_response = PageResponseModel(
rows=[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}],
pageNum=1,
pageSize=2,
total=10,
hasNext=True
)
# 访问示例对象的属性
print(page_response.rows) # 输出: [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
print(page_response.page_num) # 输出: 1
print(page_response.page_size) # 输出: 2
print(page_response.total) # 输出: 10
print(page_response.has_next) # 输出: True
1.响应格式(API或返回)cry使用。
from typing import Any, Dict, Optional # 用于方法类型
from pydantic import BaseModel, Field # 用于模型
from datetime import datetime
class CustomModel(BaseModel):
A: str = Field(alias="B") # 如果用别名,传入参数就要用别名
class ResponseUtil:
"""
响应工具类
"""
@classmethod
def success(cls, msg: str = '操作成功', data: Optional[Any] = None, rows: Optional[Any] = None,
dict_content: Optional[Dict] = None, model_content: Optional[BaseModel] = None):
"""
成功响应方法
:param msg: 可选,自定义成功响应信息
:param data: 可选,成功响应结果中属性为data的值
:param rows: 可选,成功响应结果中属性为rows的值
:param dict_content: 可选,dict类型,成功响应结果中自定义属性的值
:param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值【code、msg、success、time】
:return: 成功响应结果
"""
result = {
'code': 200,
'msg': msg
}
if data is not None:
result['data'] = data
if rows is not None:
result['rows'] = rows
if dict_content is not None:
result.update(dict_content)
if model_content is not None:
result.update(model_content.model_dump(by_alias=True)) # 使用别名的字典返回
result.update({'success': True, 'time': datetime.now()})
print(result)
if __name__ == '__main__':
ResponseUtil.success()
ResponseUtil.success(msg="自定义响应内容")
ResponseUtil.success(data="data字段的值")
ResponseUtil.success(rows="rows字段的值")
ResponseUtil.success(dict_content={"key1": "加入多个键值对", "key2": 2})
ResponseUtil.success(model_content=CustomModel(B="输入是一个模型"))
2.字典以小驼峰,传入模型
from typing import Any, Dict, Optional, List # 用于方法类型
from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel
class UserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: Optional[int] = None
user_name: Optional[str] = None
role_ids: Optional[List] = []
data = {
'userId': 30,
'userName': 'Alice',
'roleIds': [1, 2, 3]
}
if __name__ == '__main__':
add_user = UserModel.parse_obj(data)
print(add_user)
3.模型以小驼峰,传入模型
from typing import Any, Dict, Optional, List
from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel
class UserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: Optional[int] = None
user_name: Optional[str] = None
class AddUserModel(UserModel):
role_ids: Optional[List] = []
if __name__ == '__main__':
page_object = AddUserModel(userId=1, user_name='Alice', roleIds=[1, 2, 3])
# 两者一样
add_user = UserModel(**page_object.dict(by_alias=True))
print(add_user)
add_user = UserModel(**page_object.model_dump(by_alias=True))
print(add_user)
# 返回:
# user_id=None user_name=None
# user_id=None user_name=None
4.@model_validator
from typing import Optional
from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel
class UserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: Optional[int] = None
admin: Optional[bool] = False
@model_validator(mode='after') # after: 后验证器,它会在模型实例化后执行
def check_admin(self) -> 'UserModel':
if self.user_id == 1:
self.admin = True
else:
self.admin = False
return self
if __name__ == '__main__':
user1 = UserModel(userId=1)
print(user1.dict())
user2 = UserModel(userId=2)
print(user2.dict())
# 返回:
# {'user_id': 1, 'admin': True}
# {'user_id': 2, 'admin': False}
2 类
1.call
class Adder:
def __init__(self, num):
self.num = num
def __call__(self, x):
return self.num + x
# 创建一个 Adder 实例
adder = Adder(5)
# 调用 Adder 实例,实际上调用了 __call__ 方法
result = adder(10)
print(result) # 输出: 15
2.类方法和静态方法
@classmethod
@staticmethod
class MyClass:
class_variable = "I am a class variable"
def __init__(self, value):
self.instance_variable = value
@staticmethod
def static_method():
print("This is a static method")
@classmethod
def class_method(cls): # 可以访问和修改类属性
print(f"This is a class method. Class variable: {cls.class_variable}")
# 调用静态方法
MyClass.static_method()
# 调用类方法
MyClass.class_method()
返回:
This is a static method
This is a class method. Class variable: I am a class variable
3.:关键字参数传递
这里的,表示后面必须以关键字方式传递
def streaming(cls, *, data=None):
3.字典
1.dict构建字典
dict(id=“100”, name=“张三”)
返回:{‘id’: ‘100’, ‘name’: ‘张三’}
2.2
4.lambda、zip、map、filter、解包
1.lambda
1、筛选,列表中的字典,最小id的字典
data = [{‘id’: ‘100’, ‘name’: ‘张三’}, {‘id’: ‘101’, ‘name’: ‘李四’}]
print(min(data, key=lambda x: x[‘id’]))
2、排序,按列表中,各字段长度降序
names = [“Alice”, “Bob”, “Charlie”, “David”]
names.sort(key=lambda x: len(x), reverse=True)
print(names)
2.zip
将多个列表打包成元组的迭代器
拓展
1、如果a和b内元素数量不一致,多出来的那个舍去
2、可以导入zip_longest包,舍去的使用None代替(或者使用fillvalue设置默认值)
from itertools import zip_longest
print(list(zip_longest(a, b, fillvalue=3)))
a = [1, 2, 3]
b = [2, 3, 4]
print(zip(a, b)) # 可以通过循环,打印出每个元组
print(list(zip(a, b)))
返回
<zip object at 0x000002887E949C40>
[(1, 2), (2, 3), (3, 4)]
3.map
接收可迭代对象,进行计算
numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares)) # [1, 4, 9, 16, 25]
4.filter
numbers = [1, 2, 3, 4, 5]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens)) # [2, 4]
5解包
class UserModel:
def init(self, name, age, email):
self.name = name
self.age = age
self.email = email
data = {
‘name’: ‘Alice’,
‘age’: 30,
‘email’: ‘alice@example.com’
}
if name == ‘main’:
# 使用 ** 解包操作符将字典解包为关键字参数,不然会报异常!!!
add_user = UserModel(**data)
print(add_user.name) # 输出: Alice
print(add_user.age) # 输出: 30
print(add_user.email) # 输出: alice@example.com
复杂解包
# 示例数据
query_result = {
'model_dump': {'name': 'Alice', 'age': 30},
'rows': [({'id': 1, 'name': 'Bob'}, 'HR'), ({'id': 2, 'name': 'Charlie'}, 'IT')]
}
# 使用两个**进行字典解包操作
combined_data = {
**query_result['model_dump'], # !!!字典的键值作为新字典的键值
'rows': [{**row[0], 'dept': row[1]} for row in query_result['rows']]
}
print(combined_data) # {'name': 'Alice', 'age': 30, 'rows': [{'id': 1, 'name': 'Bob', 'dept': 'HR'}, {'id': 2, 'name': 'Charlie', 'dept': 'IT'}]}
5装饰器
1、运行时间
# -*- coding: utf-8 -*-
import time
# 定义装饰器函数
def calculate_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
print(f"函数 '{func.__name__}' 的运行时间是 {time.time() - start_time} s.")
return result
return wrapper
# 使用装饰器
@calculate_time
def example_function(n):
total = 0
for i in range(n):
total += i
return total
if __name__ == '__main__':
print(example_function(1000000))
2、@wraps:保留装饰函数的元数据,使得可以正确调用
from functools import wraps
def my_decorator(func):
@wraps(func) # 保留被装饰函数的元数据,如函数名、文档字符串、参数签名等
def wrapper(*args, **kwargs):
print('开始装饰器')
return func(*args, **kwargs)
return wrapper
@my_decorator
def example_function():
""" 功能函数 """
print('Hello, world!')
# 使用装饰后的函数
example_function()
# 打印函数名和文档字符串
print(example_function.__name__) # 输出: example_function 否则: wrapper
print(example_function.__doc__) # 输出: 功能函数 否则: None
## 6 args和kwargs
def func_demo(*args, **kwargs):
print(args, kwargs)
print(kwargs.get("a"))
if __name__ == '__main__':
func_demo(1, "2", a="3", b=4)
# 返回:
# (1, '2') {'a': '3', 'b': 4}
# 3
7对象
1、对象转字典
model_dump()函数的作用是将一个对象转储为字典形式
参数1,exclude_unset=True:仅保留存在字段
from typing import Any, Dict, Optional, List
from pydantic import BaseModel, ConfigDict, model_validator
from pydantic.alias_generators import to_camel
class UserModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
user_id: Optional[int] = None
user_name: Optional[str] = None
user_data: Optional[str] = None
class AddUserModel(UserModel):
role_ids: Optional[List] = []
if __name__ == '__main__':
page_object = AddUserModel(userId=1, user_name='Alice', roleIds=[1, 2, 3])
add_user = page_object.model_dump(by_alias=True)
print(add_user)
# 去掉多余字段
add_user = page_object.model_dump(exclude_unset=True, by_alias=True)
print(add_user)
# 返回:
# {'userId': 1, 'userName': None, 'userData': None, 'roleIds': [1, 2, 3]}
# {'userId': 1, 'roleIds': [1, 2, 3]}
2、getattr:获取对象的属性值
from pydantic import BaseModel
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 使用 getattr 获取属性的值
person = Person("Alice", 30)
print(getattr(person, 'name', 'Unknown')) # 输出: Alice 如果属性不存在,返回默认值
class Person_two(BaseModel):
name: str = None
person_two = Person_two(name="Bobo")
print(getattr(person_two, 'name', 'Unknown')) # 输出: Bobo
8 减少if
mapping_dict = {
"1": lambda: print(1),
"2": lambda: print(2)
}
data = "2"
if mapping_dict.get(data):
mapping_dict[data]()
9 异常
手动触发异常:raise Exception(“Error message”)
②fastapi/flask
特征FastAPIFlask架构基于 Starlette 框架,支持 ASGI 接口基于 WSGI 接口,较传统性能高性能,支持异步处理性能较低,同步处理请求类型安全利用Python 类型提示和 Pydantic 进行数据验证较少类型安全支持文档生成自动生成交互式 API 文档需要额外插件或手动编写文档依赖注入内置依赖注入系统需要使用插件或手动管理依赖关系异步支持支持异步请求处理不支持异步处理
10 fastapi
1.包
1.分组路由:from fastapi import APIRouter
2.依赖注入工具:from fastapi import Depends
2.sql
汇总
- 查询:.filter
- 模糊查询:表模型属性.like
- 排序:.order_by
- 去重:.distinct
- 范围
- 查询全部:.all()
- 查询首个:.first()
- 新增:.add(对象)
- 删除:.delete()
- 更新:.update({表模型.属性:更新值, …})
- 其他(cls, query_db: Session,
- 提交:query_db.commit()
- 回滚:query_db.rollback()
查询模板
SysDept:表模型
dept_info:查询模型
dept_result = db.query(SysDept)
.filter(SysDept.status == 0,
SysDept.del_flag == 0,
SysDept.dept_name.like(f’%{dept_info.dept_name}%') if dept_info.dept_name else True,
eval(data_scope_sql))
.order_by(SysDept.order_num.asc())
.distinct().all()
新增模板
db_user = SysUser(**user.model_dump(exclude={‘admin’}))
db.add(db_user)
db.flush()
3.实现 - 增
- 新增:1、给传入的新增模型对象赋值;2、查询名称是否已存在;3、循环新增外键数据;
- 上传图片:
- 批量导入:
- 删:1、通过删除模型创建删除对象;2、循环删除id(删除关联关系、修改实现软删除);
- 改
- 修改【较复杂】:
- 重置/切换状态(修改部分数据):
- 通过用户修改对应角色信息:
- 查
- 返回树结构:
- 分页:
- 获取当前用户详细数据:
- 获取某个用户数据:
- 获取导入模板:
- 导出:
- 通过用户获取角色信息:
4.路由函数输入参数
async def batch_import_system_user(request: Request, file: UploadFile = File(…), update_support: bool = Query(alias=‘updateSupport’), query_db: Session = Depends(get_db), current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
1.request:表示 FastAPI 中的请求对象,包含了客户端发送的所有请求信息,如请求头、查询参数、请求体等(普通请求通常有,但是不用,一般用于获取请求/响应信息,例如日志装饰器)
a.from fastapi import Request
2.各类上传参数
a.file: UploadFile = File(…):上传的文件对象,用于接收客户端上传的文件,上传excel使用
i.from fastapi import UploadFile
b.avatarfile: bytes = File():文件上传的二进制数据,上传图片使用
i.from fastapi import File
3.update_support:获取指定请求参数值(请求体不行),(例如上传excel时,前端定义一个字段,用于表示是否更新旧数据)
4.query_db:数据库连接对象,服务层提交/回滚、dao层orm对象
5.current_user:当前用户对象
5.5
2flask
四 原理汇总
1.通讯
1.ASGI、WSGI
ASGI:异步服务器网关接口,解决 WSGI 在处理异步请求和长连接时的限制。
WSGI:Web服务器网关接口,介于HTTP请求/响应 和 python程序之间
2.2
2.编码加密
1.base64
通过使用64个字符(A-Z、a-z、0-9和"+“、”/")来表示二进制数据
26+26+10+2=64
2.JWT
1.传统Cookie&Session模型(常见BS架构中)
a.流程
i.客户端发送账号密码
ii.服务端保存session,返回唯一的session_id
iii.客户端收到session_id后,将其存储在Cookie中,通常是在响应头中设置一个名为session_id的Cookie,值为服务端返回的唯一session_id。
iv.客户端在后续的请求中会将这个session_id作为Cookie的一部分发送给服务端,通常是在请求头中包含名为Cookie的字段,其中包含了session_id的值。
v.服务端接收到包含session_id的请求后,会根据session_id找到对应的session数据,从而识别和验证用户身份。服务端可以通过session_id来检索与该用户相关的会话信息,如用户身份、权限等。
vi.当用户注销或会话过期时,服务端会清除相应的session数据,使得该session_id失效,用户需要重新登录以获取新的session_id。
a.缺点:不支持分布式
2.JWT:json web token
a.缺点
i.没办法修改 已传递内容 的参数,例如过期时间字段
ii.不加密,通过解码可以查看JWT的头部和载荷部分,但无法更改签名部分。只有持有正确的私钥的人才能够生成正确的签名,因此可以用于登录(秘钥不对无法登录)。
iii.使用中,我们会把头部、荷载、签名转成Base64,给到前端,占用网络较大
a.传输图例
b.数据结构
3.Bcrypt
1.简介
a.一种可生成随机盐值的单向Hash加密算法
b.同一种明文,每次被加密后的密文都不一样,并且不可反向破解生成明文,破解难度非常大
2.原理
3.结构:$是分割符, 2y是BCrypt加密版本号,10是cost的值,紧随其后的前22位是盐值(salt),最后的字符串就是密码的密文了
4.二进制数据(ZIP)
\x48 表示十六进制值,0x48,对应ASCII字符 ‘H’,例如:b’PK\x03\x04\x14
5.5
3.3
五 常用程序
1.根据列表信息生成树形嵌套数据
def list_to_tree(permission_list: list) -> list:
"""
工具方法:根据列表信息生成树形嵌套数据
"""
# 重新定义各个字段key名
permission_list = [dict(id=item["dept_id"], label=item["dept_name"], parentId=item["parent_id"]) for item in permission_list]
# 实现 id、字典数据对应关系(用于id快速检索数据)
mapping: dict = dict(zip([i['id'] for i in permission_list], permission_list))
# 树容器
container: list = []
for d in permission_list:
# 如果找不到父级项,则是根节点
parent: dict = mapping.get(d['parentId']) # 父节点的信息 字典
if parent is None:
container.append(d)
else:
children: list = parent.get('children') # 获取父级,已有子集列表
if not children:
children = []
children.append(d) # 父级的子集列表,加入当前
parent.update({'children': children})
return container
if __name__ == '__main__':
permission_list = [{
'dept_id': 100,
'dept_name': '集团总公司',
'parent_id': 0
}, {
'dept_id': 200,
'dept_name': '营销部',
'parent_id': 100
}, {
'dept_id': 203,
'dept_name': '运营部',
'parent_id': 202
}, {
'dept_id': 202,
'dept_name': '研发部',
'parent_id': 100
}, {
'dept_id': 204,
'dept_name': '设计部',
'parent_id': 202
}]
print(list_to_tree(permission_list))
# 返回:
# [{
# 'id': 100,
# 'label': '集团总公司',
# 'parentId': 0,
# 'children': [{
# 'id': 200,
# 'label': '营销部',
# 'parentId': 100
# }, {
# 'id': 202,
# 'label': '研发部',
# 'parentId': 100,
# 'children': [{
# 'id': 203,
# 'label': '运营部',
# 'parentId': 202
# }, {
# 'id': 204,
# 'label': '设计部',
# 'parentId': 202
# }]
# }]
# }]
2.小驼峰和下划线转换
import re
from sqlalchemy.engine.row import Row # SQLAlchemy 的行数据
from pydantic import BaseModel
class CamelCaseUtil:
"""
小驼峰形式(camelCase)与下划线形式(snake_case)互相转换工具方法
"""
@classmethod
def camel_to_snake(cls, camel_str):
"""
小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case)
:param camel_str: 小驼峰形式字符串
:return: 下划线形式字符串
"""
# 在大写字母前添加一个下划线,然后将整个字符串转为小写
words = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower()
@classmethod
def snake_to_camel(cls, snake_str):
"""
下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase)
:param snake_str: 下划线形式字符串
:return: 小驼峰形式字符串
"""
# 分割字符串
words = snake_str.split('_')
# 小驼峰命名,第一个词首字母小写,其余词首字母大写
return words[0] + ''.join(word.capitalize() for word in words[1:])
@classmethod
def transform_result(cls, result):
"""
针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法
:param result: 输入数据
:return: 小驼峰形式结果
"""
if result is None:
return result
# 如果是字典,直接转换键
elif isinstance(result, dict):
return {cls.snake_to_camel(k): v for k, v in result.items()}
# 如果是一组字典或其他类型的列表,遍历列表进行转换
elif isinstance(result, list):
return [cls.transform_result(row) if isinstance(row, (dict, Row)) else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in
result]
# 如果是sqlalchemy的Row实例,遍历Row进行转换
elif isinstance(result, Row):
return [cls.transform_result(row) if isinstance(row, dict) else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row) for row in result]
# 如果是其他类型,如模型实例,先转换为字典
else:
return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns})
if __name__ == '__main__':
print(CamelCaseUtil.camel_to_snake("myVariableNameDemo")) # 小驼峰转下划线 输出: my_variable_name_demo
print(CamelCaseUtil.snake_to_camel("my_variable_name_demo")) # 下划线转小驼峰 输出: myVariableNameDemo
result = {
"first_name": "John_mon",
"last_name": "Doe",
"age": 30
}
print(CamelCaseUtil.transform_result(result)) # 字典key转小驼峰
print(CamelCaseUtil.transform_result([result, result])) # 列表[字典]key转小驼峰
# 或数据库模型实例
3.存储单位转换
输入xxxB,转换成最大存储单位
def bytes2human(n, format_str="%(value).1f%(symbol)s"):
symbols = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')
prefix = {}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i + 1) * 10
# prefix: {'KB': 1024, 'MB': 1048576, 'GB': 1073741824, 'TB': 1099511627776, 'PB': 1125899906842624, 'EB': 1152921504606846976, 'ZB': 1180591620717411303424, 'YB': 1208925819614629174706176}
for symbol in reversed(symbols[1:]):
if n >= prefix[symbol]:
value = float(n) / prefix[symbol]
return format_str % locals() # locals: 将format_str中的value和symbol替换成对应的内容
return format_str % dict(symbol=symbols[0], value=n)
if __name__ == '__main__':
print(bytes2human(3)) # 输出: '3.0B'
print(bytes2human(2048)) # 输出: '2.0K'
print(bytes2human(10000)) # 输出: '9.8K'
print(bytes2human(100001221)) # 输出: '95.4M'
print(bytes2human(5000000000)) # 输出: '4.7GB'
六 算法
import random
alg_arr = ["堆排序", "冒泡排序", "插入排序", "归并排序", "快速排序", "kmp", "01背包"]
print(alg_arr[random.randint(0, len(alg_arr) - 1)])
① 排序
时间复杂度空间复杂度排序方式稳定性1w 从小到大100w 从小到大1000w 找最大10个1y 找最大10个Heapnlogk1in-place不稳定0.83469.4048Bubblen2,最好情况n1in-place稳定4.6337Insertionn2,最好情况n1in-place稳定2.1109Mergenlognnout-place稳定0.02303.5450Quicknlogn,最坏情况n2nin-place不稳定0.01501.5865选择n21in-place不稳定希尔nlogn,最坏情况n21in-place不稳定计数n+kkout-place稳定桶n+k,最坏情况n2nout-place稳定基数nkn+kout-place稳定
1.Heap 堆
问题:取1亿个数据,最大的10个,从高到低
1.时间复杂度为O(nlogk)
其中n为原始数据列表的长度,k为要查询的最大数的数量
(这是因为在堆中插入和弹出元素的操作的时间复杂度为O(logk)(因为是完全二叉树),而这些操作在最坏情况下会执行n次)
2.内存占用
内存占用与要查询的最大数的数量成正比(使用一个最小堆来存储数据,堆的大小为要查询的最大数的数量)
3.优势
不需要对整个数据进行排序;
时间复杂度相对较低,特别是在需要找出的最大数数量较小的情况下,效率较高。
4.劣势
该算法需要额外的空间来存储堆,因此在内存占用上可能会有一定的开销
由于堆的特性,无法保证最终输出的顺序是原始数据中的顺序,可能需要额外的操作来保持原始数据的顺序。
其他情况:
1.取1亿个数据,最小的10个,从低到高
a.只需要修改入堆的数为相反数,取出时再取反即可。
import heapq
import random
import time
def find_largest(max_num: int, raw_data: list):
"""
最小堆:查询最大的 max_num 个数
max_num:查询数量
data:原始数据列表
"""
heap = []
for num in raw_data:
if len(heap) < max_num:
heapq.heappush(heap, num) # 当前单个数据num,推入堆heap中
else:
heapq.heappushpop(heap, num) # 当前单个数据num,推入堆heap中,并弹出最小的数据
largest_num_list = [heapq.heappop(heap) for _ in range(max_num)] # 弹出筛选出来的所有元素,转换成列表(heap里为空)
largest_num_list.sort(reverse=True) # 将堆中的数据按照从大到小的顺序排列
return largest_num_list
# 1、创建大量随机数据
randomNumberNum = 100 # 随机数数量
data = [random.randint(1, 1000) for _ in range(randomNumberNum)]
start_time = time.time()
maxNum = 3 # 输出最大数据的数量
largestNumList = find_largest(maxNum, data) # 2、开始排序
print(f"{randomNumberNum}个随机数中,最大的{maxNum}个数据:{largestNumList}")
print(f"运行时间:{time.time() - start_time}")
2.Bubble 冒泡
时间复杂度为O(n^2)
内存占用:无需额外空间,但是有大量比较和交换操作
劣势:大规模数据效率低,频繁使用内存
原理:循环n次(长度),每次 第一个到最后第二个 和右边的比较,交换。
import random
import time
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
# 从第一个元素到倒数第二个元素
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
# 如果没有发生交换,表示列表已经有序,可以提前结束
if not swapped:
break
return arr
if __name__ == '__main__':
# 1、创建大量随机数据
randomNumberNum = 10000 # 随机数数量
arr = [random.randint(1, 1000) for _ in range(randomNumberNum)]
start_time = time.time()
sorted_arr = bubble_sort(arr) # 2、开始排序
print(f"{randomNumberNum}个随机数中,从小到大排序后的数据:{sorted_arr}")
print(f"运行时间:{time.time() - start_time}")
3.Insertion 插入
时间复杂度为O(n^2)
内存占用:无需额外空间,但是有大量比较和交换操作
优势:稳定,无需额外空间,最好情况下(列表有序)的时间复杂度为O(n)
劣势:大规模数据效率低,频繁使用内存
原理:循环1~n(长度),每次把 循环的数,移到左侧已排序好的位置。
import random
import time
def insertion_sort(arr):
n = len(arr)
# 从第二个元素开始遍历
for i in range(1, n):
key = arr[i] # 当前要转移的数
j = i - 1
# 非最左边 且要转移的数小于已排序的数
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j] # 已排序的数向右移动
j -= 1
arr[j + 1] = key # 要转移的数归位
return arr
if __name__ == '__main__':
# 1、创建大量随机数据
randomNumberNum = 10000 # 随机数数量
arr = [random.randint(1, 1000) for _ in range(randomNumberNum)]
start_time = time.time()
sorted_arr = insertion_sort(arr) # 2、开始排序
print(f"{randomNumberNum}个随机数中,从小到大排序后的数据:{sorted_arr}")
print(f"运行时间:{time.time() - start_time}")
4.Merge 归并
时间复杂度为O(n log n)
内存占用:需要额外的内存空间来存储临时数据(合并的两个列表),因此其空间复杂度为 O(n)
优势:效率高
劣势:需要额外的内存空间来存储临时数据,因此其空间复杂度为 O(n)
原理:将列表分成两个子列表,然后递归地对子列表进行排序,最后将排好序的子列表合并成一个有序列表。归并排序的时间复杂度为 O(n log n),其中 n 是列表的长度,递归调用的次数是 log n。
import random
import time
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
merge_sort(left_half) # 返回的值就是left_half。列表是可变对象,函数内部对列表的修改会影响到原始列表
merge_sort(right_half)
i = j = k = 0
# 合并两个子列表
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
while i < len(left_half):
arr[k] = left_half[i]
i += 1
k += 1
while j < len(right_half):
arr[k] = right_half[j]
j += 1
k += 1
return arr
if __name__ == '__main__':
# 1、创建大量随机数据
randomNumberNum = 10000 # 随机数数量
arr = [random.randint(1, 1000) for _ in range(randomNumberNum)]
start_time = time.time()
sorted_arr = merge_sort(arr) # 2、开始排序
print(f"{randomNumberNum}个随机数中,从小到大排序后的数据:{sorted_arr}")
print(f"运行时间:{time.time() - start_time}")
5.Quick 快速
问题:1000个数据,从低到高排序
1.时间复杂度为O(n log n)
2.内存占用:空间复杂度n
3.优势:效率高
4.劣势:
a.每次选择基准刚好为最大或最小值时,时间复杂度为 O(n^2),且可能导致栈溢出问题
b.相等元素可能会被分到不同的子数组中,导致排序结果中相等元素的相对位置不稳定。
原理:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = [arr[0]]
right = []
left = []
for i in range(1, len(arr)):
if arr[0] > arr[i]:
left.append(arr[i])
elif arr[0] == arr[i]:
pivot.append(arr[i])
else:
right.append(arr[i])
return quick_sort(left) + pivot + quick_sort(right)
if __name__ == '__main__':
print(quick_sort([1, 6, 3, 7, 12, 89, 4, 9, 4, 7]))
② 字符串匹配
时间复杂度1w 从小到大100w 从小到大1000w 找最大10个1y 找最大10个KMPm+n m为子串长度,n为主串长度0.83469.4048Boyer-Moore
1.KMP
Knuth-Morris-Pratt算法是一种高效的字符串匹配算法。
它利用已经匹配过的信息来避免重复比较,从而提高匹配效率。
时间复杂度:O(m+n),其中m为子串长度,n为主串长度。
原理
查询最长前缀后缀(LPS)数组(未全部匹配时,指针回退数)
例如ABCABCABDABC:[0, 0, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3]
[[‘A’, 0], [‘B’, 0], [‘C’, 0], [‘A’, 1], [‘B’, 2], [‘C’, 3], [‘A’, 4], [‘B’, 5], [‘D’, 0], [‘A’, 1], [‘B’, 2], [‘C’, 3]]
以索引为5的C为例,计算方式如下:
ABCABC的前缀为A、AB、ABC、ABCA、ABCAB
ABCABC的后缀为C、BC、ABC、CABC、BCABC
最长公共后缀为ABC,因此为长度3(其他同理)
利用LPS数组进行匹配
在文本串中从左到右逐个字符与模式串进行比较,当发生不匹配时,根据LPS数组的值来决定模式串的移动位置。
def compute_lps_array(pattern):
"""
查询最长前缀后缀(LPS)数组
"""
length = 0 # 用于表示当前已匹配的最长前缀和后缀的长度。
index = 1 # 用于遍历模式串pattern的索引。
lps = [0] * len(pattern) # 一个数组,用于存储每个位置的最长公共前缀和后缀的长度。
while index < len(pattern): # [1,-1]
# 找到了更长的公共前缀和后缀
if pattern[index] == pattern[length]:
length += 1
lps[index] = length
index += 1
else:
if length != 0:
length = lps[length - 1]
# 没有匹配的前缀和后缀
else:
lps[index] = 0
index += 1
return lps
def kmp_search(text, pattern):
lps = compute_lps_array(pattern)
i = 0 # 文本串 text 的当前比较位置
j = 0 # 模式串 pattern 的当前比较位置
matches = []
while i < len(text):
# 匹配上对应字符,计数+1
if pattern[j] == text[i]:
i += 1
j += 1
# 找到了(pattern全部匹配)
if j == len(pattern):
matches.append(i - j)
j = lps[j - 1]
# 未匹配上
elif i < len(text) and pattern[j] != text[i]:
if j != 0: # 从pattern的索引位置开始
j = lps[j - 1]
else: # text继续往后
i += 1
return matches
if __name__ == '__main__':
text = "ABCABCABDABC"
pattern = "AB"
matches = kmp_search(text, pattern)
if matches:
print("索引:", matches)
for index, matche in enumerate(matches):
print(f"{text[:matches[index]]}\033[31m"
f"{text[matches[index]:(matches[index] + len(pattern))]}"
f"\033[0m{text[matches[index] + len(pattern):]}")
else:
print("未匹配")
2.Boyer-Moore
③ 动态规划
1.0-1背包问题
假设有一个背包最大承重为 10,现有以下物品列表:
物品 1:重量 2,价值 6
物品 2:重量 3,价值 5
物品 3:重量 4,价值 8
物品 4:重量 5,价值 9
请问在不超过背包最大承重的情况下,如何选择物品放入背包,使得背包内物品的总价值最大化?
def func(deep, max_weight):
"""
0-1背包问题
:param deep: 行索引,从物品0开始,每次加一个物品
:param max_weight: 背包最大容量
:return:
"""
if deep == 0:
dp = [0] * weight[0] + [value[0]] * (max_weight - weight[0])
return dp
else:
dp = [0] * max_weight
dp_front = func(deep - 1, max_weight)
for j in range(max_weight): # 循环每列(背包容量从0~最大)
if j - weight[deep] >= 0: # 当前位置,存的下本行新增物品
dp[j] = max(dp_front[j], value[deep] + dp_front[j - weight[deep]])
else:
dp[j] = dp_front[j]
return dp
if __name__ == '__main__':
weight = [2, 3, 4, 5]
value = [6, 5, 8, 9]
print(func(deep=0, max_weight=10))
print(func(deep=1, max_weight=10))
print(func(deep=2, max_weight=10))
print(func(deep=3, max_weight=10))
解题思路
总价值0总重量 j123456789100物品索引 i0066666666610066611111111111120066811max(arr[i-1, j]), value[i]+arr[i-1][j-weight[i]])3
④ 字符匹配
1.深度优先搜索(DFS)