numpy
indexing
indexing 操作指的是 x[obj]
这种形式获取参数, 其中 x
是 np.ndarray
对象, 注意: x[1, 2]
与 x[(1, 2)]
是完全等价的, 只是语法趟, 按 obj
的不同类型, 可以区分为如下几类:
Basic Indexing
Basic Indexing 触发的条件如下:
Copy np.newaxis is None # True
Ellipsis is ... # True
# Basic Indexing 总是返回一个 view
obj: Union[int, slice, Ellipsis, np.newaxis, List[Union[int, slice, Ellipsis, np.newaxis]]]
注意 Basic Indexing 的返回的结果总是原数据的一个 View , 这暗示了一个副作用:
Copy x = np.arange(1000000)
y = x[1]
del x # 不会释放 x, 只是不能使用 x 这个标识符
一些例子:
Copy x = np.arange(24)
x.shape = (2, 3, 4)
x[:, np.newaxis, :, :, None].shape # (2, 1, 3, 4, 1)
x[..., 1:2].shape # (2, 3, 1), 只能最多出现一个 Ellipsis, 并且 1:2 这种写法会保留这个维度本身
x[..., 1].shape # (2, 3)
# slice(i, j, k), 首先总是将 i 和 j 转为整数, 然后再区分 k 为正数还是负数, 但区间总是左开右闭: [i, j)
x[0, 0, -1:-3:-1] # slice(i=-1, j=-3, k=-1), 首先将 i, j 转换为整数, 转换方式为: i = -1 + x.shape[2] = 3, j = -3 + x.shape[2] = 1
# 等价于 x[0, 0, 3:1:-1], 因此取出: [x[0, 0, 3], x[0, 0, 2]] 得到数组 [3, 2]
# 空数组情形
x[1:1, ...].shape # (0, 3, 4)
Advanced Indexing 【很复杂,待续】
触发条件:
Advanced indexing is triggered when the selection object, obj, is a non-tuple sequence object, an ndarray (of data type integer or bool), or a tuple with at least one sequence object or ndarray (of data type integer or bool). There are two types of advanced indexing: integer and Boolean.
如果 obj
本身是序列类型(但不是元组)或是数组(数据类型可以是bool或int), 或者 obj
是一个元组, 但元组至少有一个元素是序列类型或是数组(数据类型可以是bool或int)
Advanced indexing always returns a copy of the data (contrast with basic slicing that returns a view).
Advanced Indexing 总是返回复制
一些例子:
Copy x = np.arange(24)
x.shape = (2, 3, 4)
x[np.array([[0, 1], [1, 0, 0]])].shape # (2, 3, 3, 4)
y = np.arange(35).reshape(5, 7)
y[np.array([0, 2, 4]), np.array([0, 1, 2])] # (0, 15, 30)
奇怪的 id
Copy x = np.array([1, 2])
id(x[0]) == id(x[0]) # 两次取 id 的结果不一样 !!!
topk
Copy idx = np.argpartition(x, k, axis=1) # (m, n) -> (m, n)
x[np.range(x.shape[0]), idx[:, k]] # (m,) 每行的第k大元素值
save & load
Copy # numpy保存
np.save("xx.npy", arr)
np.load(open("xx.npy"))
pandas
pandas 的 apply 系列
apply: DataFrame的方法, 可指定axis,应用于行或列
args用于指定额外参数, 但这些参数对于每行或每列是相同 的
Copy DataFrame.apply(func, axis=0, broadcast=None, raw=False, reduce=None, result_type=None, args=(), **kwds)
applymap: DataFrame的方法, 应用于每一个元素
DataFrame.applymap(self, func)
DataFrame.apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds)
Series.apply(self, func, convert_dtype=True, args(),**kwds)
Series.map(self, arg, na_action=None)
pandas 分组操作
官方指南
总体上, 从效果上来说分为三个步骤:
Splitting: 数据分组, 对应的 API 是 pd.DataFrame.groupby
Applying: 一般来说有如下几类, 更一般地, 可以使用 splitting 的结果调用 apply
函数
Aggregation: 分组后, 对每一组计算一个统计值
Transformation: 分组后, 对每一组分别应用于一个变换, 例如对于 A 分组, 将空值填充为 "A", 对 B 分组, 将空值填充为 "B"
Filtration: 分组后, 根据一些条件筛选分组内的数据或者筛选整个组地数据, 例如如果一个分组的行数小于 10, 则删除整个分组的数据; 每个分组都只取前 3 行.
Combining: 将 Applying 的各个分组的结果合并在一次做返回
涉及的 API:
Splitting: pd.DataFrame.groupby
: 返回一个 pandas.api.typing.DataFrameGroupBy
对象, 此对象有 groups
, get_groups
等基础方法/属性, 也具有下面的 Applying 步骤中的 agg
/aggregation
, transform
, filter
, apply
等方法
Applying:
Aggregation: 内置的方法例如: mean
, std
, 更一般地可以使用 agg
/aggregation
(这两个方法是一样的, agg
只是 short-hand 写法)
Transformation: 内置的方法例如: cumsum
, 更一般地可以使用 transform
Filtration: 内置的方法例如: head
, 用于取每组的前几行, 使用自定义函数可以用 filter
, 但注意 filter
只能将整组全部去掉或保留
上面 3 种都不满足时, 可使用 apply
函数
Splitting: groupby 与 pandas.api.typing.DataFrameGroupBy 对象的方法
Copy df = pd.DataFrame({
"A": ["a", "a", "b", "b"],
"B": [1, 9, 2, 4],
"C": [4, 6, 1, 10],
})
grouped = df.groupby('A')
for name, group in grouped:
print(name) # "a"/"b"
print(group) # (2, 3) shape DataFrame
grouped.get_group("a") # (2, 3) shape DataFrame
grouped.groups # {'a': [0, 1], 'b': [2, 3]}
Applying: agg
、 transform
, filter
, apply
agg
、transform
, filter
, apply
传参时的自定义函数 (UDF: User-Defined Function) 的输入输出条件 (官方文档似乎对此语焉不详) 不尽相同
这几个方法最终 Combining 之后的 DataFrame 的 index 的形式有所不同, 这里只讨论 group(..., group_keys=True)
的情况:
apply
在 UDF 的出参是一个 DataFrame 的情况下, 会把 groupby 的列作为索引并保留原始索引以构成两级索引
transform
会把 groupby 的列丢弃, 原本的索引依然作为索引
agg
会把 groupby 的列作为索引, 原本索引丢弃
filter
除去索引可能会变少外, groupby 列被保留为列
太长不看系列 (transform
, agg
, filter
都可用 apply
实现):
Copy # transform
fn = lambda x: x+1 if x.name=='a' else x-100
df.groupby('State', group_keys=True).transform(fn) # Input: Series, Output: Series(List) (Same length with Input)
apply_fn = lambda x: pd.DataFrame([fn(x[c]) for c in x.columns]).T
df.groupby('State', group_keys=False).apply(apply_fn) # Input: DataFrame, Output: DataFrame
# agg
fn = lambda x: x.sum() if x.name=='a' else 0
df.groupby('State', group_keys=True).agg(fn) # Input: Series, Output: Scalar
apply_fn = lambda x: pd.Series({c: fn(x[c]) for c in x.columns if c not in ["State"]})
df.groupby('State', group_keys=False).apply(apply_fn) # Input: DataFrame, Output: Series
# filter
fn = lambda x: True
df.groupby('State', group_keys=True).filter(fn) # Input: DataFrame, Output: bool
apply_fn = lambda x: x if fn(x) else []
df.groupby('State', group_keys=False).apply(apply_fn) # Input: DataFrame, Output: DataFrame
# apply
df.groupby('State', group_keys=True).apply(lambda x: x+1) # Input: Dataframe, Output: Scalar/Series(List)/DataFrame
# 当UDF Output是标量时, apply 的最终结果是 Series
df.groupby('State', group_keys=False).apply(lambda x: 1) # Final Output: Series
参考资料:
UDF 的输入输出对比
transform
输入: 每组的每一列(Series)作为输入 输出: 与输入同等长度的列表/Series
agg
输入: 每组的每一列(Series)作为输入 输出: 标量
filter 输入: 每组的DataFrame作为输入 输出: True/False
apply 输入: 每组的DataFrame作为输入 输出: 标量/Series(不必与输入同等长度)/DataFrame
为了弄清这些函数的 UDF 的输入输出, 可以构造类似如下的测试代码
Copy import pandas as pd
import numpy as np
from IPython.display import display
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
'a':[4,5,1,3], 'b':[6,10,3,11]})
def subtract_two(x):
display(x)
print()
y = x['a'] - x['b']
display(y)
print()
return y
result = df.groupby('State').apply(subtract_two)
display(result)
print()
print(result.to_numpy())
print(result.index)
输出结果如下:
Copy State a b
2 Florida 1 3
3 Florida 3 11
2 -2
3 -8
dtype: int64
State a b
0 Texas 4 6
1 Texas 5 10
0 -2
1 -5
dtype: int64
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
[-2 -8 -2 -5]
MultiIndex([('Florida', 2),
('Florida', 3),
( 'Texas', 0),
( 'Texas', 1)],
names=['State', None])
pandas 可能会自动做些优化 (与numba有关), 所以有些像上面那种测试代码可能会有比较诡异的结果, 例如:
Copy df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
'a':[4,5,1,3], 'b':[6,10,3,11]})
def subtract_two(x):
if x.name == 'a':
y = x + 1
else:
y = x + 2
display(type(x), type(y), "\n", x, "\n", y)
print()
return y
# def inspect(x):
# print(type(x))
# raise
result = df.groupby('State').transform(subtract_two)
display(result)
print()
print(result.to_numpy())
print(result.index)
pandas读写excel文件
参考链接1 , 参考链接2
pandas读写excel依赖xlrd, xlwt包, (ps: 可以尝试直接使用这两个包直接进行读写excel文件)
Copy df1 = pd.DataFrame({"A": [1, 2, 3]})
df2 = pd.DataFrame({"B": [2, 0, 3]})
df3 = pd.DataFrame({"C": [3, 2, 3]})
with pd.ExcelWriter("path_to_file.xlsx", engine="openpyxl") as writer:
df1.to_excel(writer, sheet_name='Sheet1')
df2.to_excel(writer, sheet_name="页2")
with pd.ExcelWriter("path_to_file.xlsx", engine="openpyxl", mode="a") as writer:
df3.to_excel(writer, sheet_name="Sheet3", index=False)
test = pd.read_excel("path_to_file.xlsx", sheet_name=[0, "Sheet3"])
print(type(test)) # <class 'dict'>
print(test.keys()) # dict_keys([0, 'Sheet3'])
直接使用xlrd包示例: 参考链接
Copy # 直接使用xlrd包
import xlrd
wb = xlrd.open_workbook("path_to_file.xlsx")
sheet = wb.sheet_by_index(0)
sheet = wb.sheet_by_name("Sheet3")
# <class 'xlrd.book.Book'> <class 'xlrd.sheet.Sheet'> 4 1
print(type(wb), type(sheet), sheet.nrows, sheet.ncols)
for i in range(sheet.nrows):
for j in range(sheet.ncols):
print(sheet.cell_value(i, j), end=" ")
print()
直接使用xlwt包示例: 参考链接
Copy # Writing to an excel sheet using Python 3.x. or earlier
import xlwt as xw
# Workbook is created
wb = xw.Workbook()
# add_sheet is used to create sheet.
sheet1 = wb.add_sheet('Sheet 1')
# Specifying style of the elements
style_value1= xw.easyxf('font: bold 1')
style_value2 = xw.easyxf('font: bold 1, color blue;')
# Input data into rows
sheet1.write(1, 0, 'Code Speedy', style_value1)
sheet1.write(2, 0, 'Sarque Ahamed Mollick', style_value2)
# Input data into columns
sheet1.write(0, 1, 'Position')
sheet1.write(0, 2, 'No of Posts')
# 似乎不能写为以.xlsx为后缀的文件(运行不报错, 但使用Excel2019打不开)
wb.save('xlwt codespeedy.xls') # .xls文件能用Excel2019打开
某些情况下.xlsx
被自动转为了.xlsm
格式, 可以用pandas进行修复, 注意下面的例子也演示了如何获取一个excel文档的所有sheet名称
Copy x = pd.ExcelFile(r"C:\Users\chenbx\Desktop\调优\默认值.xlsm")
sheet_names = x.sheet_names
y = pd.ExcelWriter(r"C:\Users\chenbx\Desktop\调优\默认值.xlsx")
for sheet in sheet_names:
df = pd.read_excel(r"C:\Users\chenbx\Desktop\调优\默认值.xlsm", sheet_name=sheet)
df.to_excel(y, sheet_name=sheet)
y.save()
pandas index相关的操作
Copy # DataFrame.set_index(keys, drop=True, append=False, inplace=False, verify_integrity=False)
df.set_index("key", drop=True) # 将df["key"]这一列作为新的index, 将原有的index丢弃
df.reset_index(drop=True) # 将原有的index丢弃, 新的index为默认的[0,1,...], 丢弃的index不作为新列
df.reindex(list_or_index, fill_value=0) # 只保留list_or_index中的行, 用0填补不存在的行
df.rename(index={1: -1}, columns={"a": "b"}, inplace=False) # 对行或列重命名
merge, join技巧
Copy # 需求: df1与df2, 需要按指定的列名col1, col2做内连接, 希望输出两个dataframe:
# new_df1: 能连上的df1中的部分, 行的相对顺序与df1保持一致, 且列名与df1完全一致
# new_df2: 能连上的df2中的部分, 列名与df2完全一致
# 注:
# 1) 为什么不能用普通的表连接: pandas的dataframe不允许两列的列名相同(实际需求中, df1的列与df2中的列名可能有重复, 并且这些列代表着完全不同的含义)
# 2) col1与col2可以相同, 也可以不同
# 在df1和df2的index都不重要时, 可以使用如下方法
def mymerge(df1, df2, col1, col2):
df1_new = pd.merge(df1, df2[[col2]].set_index(col2, drop=True), left_on=col1, right_on=col2, how="inner")
df2_new = pd.merge(df2, df1[[col1]].set_index(col1, drop=True), left_on=col2, right_on=col1, how="inner")
return df1_new, df2_new
easydict/addict/dotmap
这几个包均是对 python 字典这一基本数据类型的封装,使得字典的属性可以使用点来访问,具体用法及区别待补充:
一些开源项目对这些包的使用情况:
语言检测模块
langdetect
, langid
等
发送邮件模块
未仔细校对过
压缩与解压模块
zipfile模块
参考链接: https://www.datacamp.com/community/tutorials/zip-file#EZWP
Copy # 解压文件
import zipfile
zipname = r'D:\work0126\aa.zip'
out_dir = r"C:\work0126"
pswd = '12345'
with zipfile.ZipFile(zipname) as file:
# password you pass must be in the bytes you converted 'str' into 'bytes'
file.extractall(path=out_dir, pwd = bytes(pswd, 'utf-8'))
# 打包为zip
pyhanlp
安装说明(1.7.8版本)
step 1
首先安装JPype1==0.7.0(版本号必须完全一致)
Copy pip install JPype1-0.7.0-cp37-cp37m-win_amd64.whl
step 2
接下来安装pyhanlp(直接去网站 下载代码pyhanlp-master.zip , 注意项目名为pyhanlp)
并下载: jar与配置文件hanlp-1.7.8-release.zip , 数据文件data-for-1.7.5.zip
注意data1.7.5是被1.7.8版本hanlp兼容的(实际上也没有data1.7.5版本), 至此原料已经准备齐全
首先将pyhanlp-master.zip解压, 并进入该目录用如下方式安装
Copy python setup.py install
接下来进入安装位置例如:
C:\Users\54120\anaconda3\envs\hanlp_copy\Lib\site-packages\pyhanlp-0.1.66-py3.7.egg\pyhanlp\static
将data-for-1.7.5.zip
解压后的data
文件夹, hanlp-1.7.8-release.zip
解压后的hanlp-1.7.8-sources.jar
, hanlp-1.7.8.jar
, hanlp.properties
都放入上述目录下, 最终此目录的结构为:
Copy static
|- data
|- dictionary
|- model
|- test (后续示例代码可能将数据下载到这个目录)
|- README.url
|- version.txt (内容为1.7.5)
|- hanlp-1.7.8-sources.jar
│- hanlp-1.7.8.jar
│- hanlp.properties
│- hanlp.properties.in
│- index.html
│- README.url
│- __init__.py
step 3
修改hanlp.properties
文件的内容
Copy root=C:/Users/54120/anaconda3/envs/hanlp_copy/Lib/site-packages/pyhanlp-0.1.66-py3.7.egg/pyhanlp/static
step 4
检查, 在命令行输入
Copy hanlp -v
jar 1.7.8-sources: C:\Users\54120\anaconda3\envs\hanlp_copy\lib\site-packages\pyhanlp-0.1.66-py3.7.egg\pyhanlp\static\hanlp-1.7.8-sources.jar
data 1.7.5: C:\Users\54120\anaconda3\envs\hanlp_copy\Lib\site-packages\pyhanlp-0.1.66-py3.7.egg\pyhanlp\static\data
config : C:\Users\54120\anaconda3\envs\hanlp_copy\lib\site-packages\pyhanlp-0.1.66-py3.7.egg\pyhanlp\static\hanlp.properties
另外, python中应该也要确保可以正常导入
Copy python -c "import pyhanlp"
注意
上述繁琐的过程使得环境迁移时除了拷贝envs还要修改配置文件.
spacy
下载模型
解决类似如下命令因为网络原因失效的方法:
Copy python -m spacy download en_core_web_sm
去https://github.com/explosion/spacy-models/查看相应的版本号 , 下载类似如下链接的文件
Copy https://github.com/explosion/spacy-models/releases/download/de_core_news_sm-3.0.0/de_core_news_sm-3.0.0.tar.gz
Copy pip install xxx.tar.gz
更为详细的解释如下(spacy2.1.9版本源码分析)
执行 python -m spacy download en_core_web_sm
实际调用 site-packages/spacy/__main__.py
。之后调用了
Copy # res = requests.get("https://raw.githubusercontent.com/explosion/spacy-models/master/shortcuts-v2.json")
# res.json()
# 确定下载的模型版本
res = requests.get("https://raw.githubusercontent.com/explosion/spacy-models/master/compatibility.json")
version = res.json()["spacy"]["2.1.9"]['en_core_web_sm'] # 2.1.0为2.1.9版本相匹配的en_core_web_sm模型
# 最后实际调用了
# python -m pip install --no-cache-dir --no-deps <download-url>
download_url = "https://github.com/explosion/spacy-models/releases/download/"
+ "en_core_web_sm-2.1.0/en_core_web_sm-2.1.0.tar.gz#egg=en_core_web_sm==2.1.0"
m = "en_core_web_sm"
v = "2.1.0"
format = "{m}-{v}/{m}-{v}.tar.gz#egg={m}=={v}"
基本使用
模型下载目录
设置模型下载位置可参见官网介绍 , 摘抄如下:
Caching models
This library provides pretrained models that will be downloaded and cached locally. Unless you specify a location with cache_dir=...
when you use methods like from_pretrained
, these models will automatically be downloaded in the folder given by the shell environment variable TRANSFORMERS_CACHE
. The default value for it will be the Hugging Face cache home followed by /transformers/
. This is (by order of priority):
shell environment variable HF_HOME
shell environment variable XDG_CACHE_HOME
+ /huggingface/
default: ~/.cache/huggingface/
So if you don’t have any specific environment variable set, the cache directory will be at ~/.cache/huggingface/transformers/
.
Note: If you have set a shell environment variable for one of the predecessors of this library (PYTORCH_TRANSFORMERS_CACHE
or PYTORCH_PRETRAINED_BERT_CACHE
), those will be used if there is no shell environment variable for TRANSFORMERS_CACHE
.
开源模型
发现有英翻中的模型, 开源模型目录 , 搜索zh
, (https://huggingface.co/Helsinki-NLP/opus-mt-en-zh )
使用方法:
Copy # 前三行参照模型地址
# https://huggingface.co/Helsinki-NLP/opus-mt-en-zh/tree/main
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
tokenizer = AutoTokenizer.from_pretrained("Helsinki-NLP/opus-mt-en-zh")
model = AutoModelForSeq2SeqLM.from_pretrained("Helsinki-NLP/opus-mt-en-zh")
# 后面三行参照transformers文档
# https://huggingface.co/transformers/task_summary.html#translation
inputs = tokenizer.encode("translate English to German: Hugging Face is a technology company based in New York and Paris", return_tensors="pt")
outputs = model.generate(inputs, max_length=40, num_beams=4, early_stopping=True)
print(tokenizer.decode(outputs[0]))
离线模型下载实例
EncoderClassifier
中有如下注释:
Copy classifier = EncoderClassifier.from_hparams(
... source="speechbrain/spkrec-ecapa-voxceleb",
... savedir=tmpdir,
... )
离线下载模型步骤如下:
Copy # 需要先安装git-lfs
git clone https://huggingface.co/speechbrain/spkrec-ecapa-voxceleb
# 将hyperparams.yaml中的pretrained_path修改为/home/buxian/Desktop/spkrec-ecapa-voxceleb
这样便可以直接使用如下方式导入模型(完全绕过默认路径 ~/.cache/huggingface/hub
)
Copy from speechbrain.pretrained import EncoderClassifier
classifier = EncoderClassifier.from_hparams(source="/home/buxian/Desktop/spkrec-ecapa-voxceleb")
备注:此处的 git clone 这一方法在离线下载时具有通用性,而修改 pretrain_path
是 speechbrain
包的内部的逻辑造成的。如果不修改 pretrain_path
,将无法绕过默认下载路径 ~/.cache/huggingface/hub
。
读写excel(xlsxwriter与pandas)
pandas与xlsxwriter均支持给输出的excel自定义格式.
Copy # 注意workbook指的是一个excel文件, 而worksheet指的是excel文件当中的一个sheet
import xlsxwriter
workbook = xlsxwriter.Workbook('filename.xlsx')
worksheet = workbook.add_worksheet()
worksheet.write(0, 0, 'Hello Excel')
workbook.close()
Copy # 在生成的excel中操作: “条件格式->管理规则”就可以看到这里定义的规则
import pandas as pd
df = pd.DataFrame({'Data': [1, 1, 2, 2, 3, 4, 4, 5, 5, 6]})
writer = pd.ExcelWriter('conditional.xlsx', engine='xlsxwriter')
df.to_excel(writer, sheet_name='Sheet1', index=False)
workbook = writer.book
worksheet = writer.sheets['Sheet1']
format1 = workbook.add_format({'bg_color': '#FFC7CE', # 粉色
'font_color': '#9C0006'}) # 深红色
format2 = workbook.add_format({'bg_color': '#C6EFCE', # 青色
'font_color': '#006100'}) # 深绿色
worksheet.conditional_format('A1:A8', {'type': 'formula', 'criteria': '=MOD(ROW(),2)=0', 'format': format1})
worksheet.conditional_format('A1:A8', {'type': 'formula', 'criteria': '=MOD(ROW(),2)=1', 'format': format2})
writer.save()
对单元格的一些字符变成红色字符, 另一些字符仍然为黑色
Copy import xlsxwriter
workbook = xlsxwriter.Workbook('x.xlsx')
worksheet = workbook.add_worksheet()
cell_format_red = workbook.add_format()
cell_format_red.set_font_color('red')
cell_format_black = workbook.add_format()
cell_format_black.set_font_color('black')
worksheet.write_rich_string(0, 0, cell_format_red, "我", "不", cell_format_black, "是", cell_format_red, "谁")
workbook.close()
数据验证(单元格内的值必须满足一定的条件)
Copy import xlsxwriter
workbook = xlsxwriter.Workbook('x.xlsx')
worksheet = workbook.add_worksheet()
validation = {"validate": "list", "value": ["apple", "banana", "orange"]}
# 对一个区域内的单元格设置条件, 注意start与end也包括在内
worksheet.data_validation(start_row, start_col, end_row, end_col, validation)
html转pdf的(pdfkit)
依赖于wkhtmltopdf , 安装后(windows上需添加至环境变量)可以利用pdfkit包进行html到pdf的转换, 实际体验感觉对公式显示的支持不太好.
Copy # pip install pdfkit
import pdfkit
pdfkit.from_url('https://www.jianshu.com','out.pdf')
black(自动将代码规范化)
black模块可以自动将代码规范化(基本按照PEP8规范), 是一个常用工具
Copy pip install black
black dirty_code.py
albumentations(待补充)
基于opencv的数据增强包
natsort
Copy from natsort import natsorted
x = ["1.png", "10.png", "2.png"]
sorted_x = natsorted(x)
# sorted_x: ["1.png", "2.png", "10.png"]
yacs
作者为 faster rcnn 的作者 Ross Girshick,用于解析 yaml 文件
timeout_decorator
超时自动退出装饰器
redis
python调用redis服务主要是如下两个第三方包:
包名(pip install): redis, import时的模块名: redis
包名(pip install): redis-py-cluster, import时的模块名: rediscluster
以上两个包存在一些版本兼容性问题:
redis-py-cluster依赖于redis, 但目前redis已经集成了redis-py-cluster的所有内容, 使用方式为:
Copy from rediscluster import RedisCluster # 旧版, redis-py-cluster 2.x, 依赖于redis 3.x
from redis.cluster import RedisCluster # redis 4.x
redis-py-cluster github README
In the upstream package redis-py that this librar extends, they have since version * 4.1.0 (Dec 26, 2021) ported in this code base into the main branch.
redis-py readthdocs
The cluster client is based on Grokzen’s redis-py-cluster, has added bug fixes, and now supersedes that library. Support for these changes is thanks to his contributions
redis包在旧版本中存在Redis
与StrictRedis
两个类, 但在目前的 3.x
及以上版本已经合并为一个类, 源码中有如下代码:
关于StrictRedis与Redis的讨论参考stackoverflow
结论: 不要使用redis-py-cluster
, 直接安装redis 4.x
及以上版本, 使用redis.Redis
和redis.cluster.RedisCluster
类, 不要使用redis.StrictRedis
具体使用方式为:
Copy from redis import Redis
from redis.cluster import ClusterNode, RedisCluster
# decode_respose为True表示利用get得到的数据类型为字符串
redis_service = Redis(
host="127.0.0.1",
port=6379, # redis默认端口为6379
decode_responses=True,
password="xxx"
)
redis_service = RedisCluster(
startup_nodes=[
ClusterNode("127.0.0.1", 6379)
],
decode_responses=True,
password="xxx"
)
# 以下操作适用于Redis与RedisCluster
key = "test001"
redis_service.exists(key)
redis_service.set(key, json.dumps(["text1", "text2"]))
redis_service.set(key, json.dumps(["text1", "text2", "text3"]))
value = redis_service.get(key)
redis_service.delete(key)
pytest
经过阅读多篇相关的博客, 总结如下, python 包的项目组织形式严格按照如下方式进行
备注: 很多项目其实未必采用了“正确”的方式组织代码,“正确”的含义也会随着时间的推移而改变
Copy - src
- package_name
- submodule1/ # 全部加上__init__.py
- module_a/
- __init__.py
- some.py
somename.py
- __init__.py
- tests/
- __init__.py
- test_a/
- test_1.py
- __init__.py
- test_b/
- test_2.py
- __init__.py
test_c.py
setup.py # 尽量不写setup.py, 完全由pyproject.toml配置
setup.cfg
pyproject.toml
关于安装
关于 pyproject.toml
与 setup.py
与 setup.cfg
:这三个文件与 pip install -e .
或 pip install
的行为相关。
pyproject.toml
:可以配置打包工具,也可以配置包的一些基本信息。
当打包工具项配置为 setuptools.build_meta
时,那么 pip
会去按照 setup.py
的逻辑去执行安装命令,pyproject.toml
和 setup.cfg
的其余配置项也会被自动用于 setup.py
的执行逻辑里
当打包工具配置为其他选项例如:hatchling.build
时,那么 pip
会忽略 setup.py
,而按照 pyproject.toml
和 setup.cfg
的其余配置项执行安装命令。
setup.py
与 pyproject.toml
同时存在时,执行 pip install .
命令时会按照 pyproject.toml
来执行。当然,执行 python setup.py install
安装时则忽略 pyproject.toml
。但一般情况下,推荐使用 pip install .
而非 python setup.py install
。
关于测试
关于 __import__
、import
、importlib.import_module
:stackoverflow问答
import
实际上最终是调用了 __import__
,而 __import__
在底层是直接使用了 C 代码来实现。importlib.import_module
本质上的行为跟 __import__
比较类似,只是实现方式上是使用纯 Python 来实现的。具体解释如下:
Copy tests
- __init__.py
- test_a/
- __init__.py
- test_b.py
import tests.test_a.test_b
:sys.modules
增加 tests
、tests.test_a
、tests.test_a.test_b
这几个模块,当前文件可以使用 tests.test_a.test_b
这个命名空间,即可以使用 tests.test_a.test_b.xx
。
mod = importlib.import_module("tests.test_a.test_b")
:sys.modules
增加 tests
、tests.test_a
、tests.test_a.b
这几个模块,当前文件可以使用 mod
这个命名空间,即可以使用 mod.xx
。
在进行了上面两种方式之一进行导入后,可以直接使用 sys.modules
获取 tests.test_a
:
Copy test_a_mod = sys.modules['tests.test_a']
print(test_a_mod.xxx)
pytest 在处理 import 的问题时, 支持了三种方式 prepend
, append
与 importlib
。但每种方式都有各自的缺点。目前的最佳实践是:
包放在 src
目录下 ,所有的模块各个目录应显式地添加 __init__.py
。放入 src
目录最主要的目的是使得本地开发环境与 release 到 PyPI 后别人使用 pip install
的方式安装的环境相同。
测试代码完全按包的形式组织 ,即各个目录显式地添加 __init__.py
,独立于包之外,即与 src
目录同级方式 tests
文件夹。
测试的目的是包在安装之后的行为是否正常 ,测试前应该以 pip install -e .
或 pip install .
的方式将包安装。这也是包要放在 src
目录下的原因,即保证不会意外导入当前路径下的代码(很多情况下,sys.path
变量会把当前路径添加至模块搜索路径,这样子可能会意外导入)。
以默认的 prepend
作为 pytest 的导入方式 。注意:按照 pytest 内部的逻辑,使用 prepend
作为导入方式,不可避免地会修改 sys.path
,但测试代码完全按包的形式组织,已经可以尽可能小的避免了 sys.path
的修改,但好处是 tests
目录下的各个文件之间可以相互 import。import 的方式应为 import tests.xx.yy
。然而使用 importlib
作为导入方式,测试文件之间无法进行相互 import,这是一个重要的缺点。
执行 pytest
命令 (即不要以 python -m pytest
的方式启动):使用 python -m pytest
会将当前目录添加至 sys.path
目录,因此要避免使用。
代码片段
Copy def int2str(x):
return tuple(str(i) if isinstance(i, int) else int2str(i) for i in x)
x = ((1, 2), 1, 3)
int2str(x) # 输出(('1', '2'), '1', '3')
# 一个综合的例子
from functools import wraps
def to_string(func):
@wraps(func)
def wrapper(*args, **kwargs):
def int2str(x):
return tuple([str(i) if isinstance(i, int) else int2str(i) for i in x])
return int2str(func(*args, **kwargs))
return wrapper
@to_string
def f():
"""asd"""
return ((1, 2), 1, 3)
f.__doc__
Copy # for else字句, 若正常结束, 则执行else语句
for n in range(2, 8):
for x in range(2, n):
if n % x == 0:
print( n, 'equals', x, '*', n/x)
break
else:
# loop fell through without finding a factor
print(n, 'is a prime number')
Copy # 慎用, 速度很慢!!
df = pd.DataFrame({"A": [1, 2, 3], "B": [0, 1, 2]})
df[["E", "F"]] = df["A"].apply(lambda x: pd.Series((x, x)))
# 快的方式待整理
Copy # 用两个列表创建字典的较快方式(似乎快于字典推导式)
x = dict(zip(key, value))
打印带颜色的文本
Copy # [0;31m 红色 [0;32m 绿色 [0;33m 黄色
s = "红色"
s = "\033[0;31m" + s + "\033[0m"