Debug/Profile/Devlop Tools of PTA

导言

  • 在 Ascend Extension for PyTorch 的相关开发时,最基础的能力就是debug和性能测试。
  • 这里只列出、整理工具,具体需要看内部文档。
  • 避免重复造轮子。
Read more

Pip Package

导言

之前其实开发了自己的包,想写点轮子。但是那个时候并没有。按照对象编程的思想,打算重构并拓展常用内容 from PIA(uniPIM) Project。

Read more

Python Graph & Visualization

数据科学

matplotlib

基础语法与概念

plotly

matplotlib VS plotly

  1. matplotlib can use custom color and line style
  2. plotly is more easily to quickly built up.

基础语法与概念

线性颜色柱的选择

https://plotly.com/python/builtin-colorscales/

same in matplotlib

plt.show 转发图像到本地

使用Dash: A web application framework for your data., 默认部署在localhost:8050端口

本地机器打通ssh隧道

1
ssh -L 8050:127.0.0.1:8050 -vN -f -l shaojiemike 202.38.72.23

科研画图

In scientific research plotting, it’s always bar charts instead of line charts.

Possible reasons:

  1. The visual footprint of point lines is relatively small compared to bar
  2. Line charts are aggregated together when there is a meaningful comparison of data along the y-axis.

global setting

font things, Attention, global settings have higher priority to get work

1
2
3
4
5
6
7

matplotlib.rcParams.update({
"pgf.texsystem": "pdflatex",
'font.family': 'serif',
# 'text.usetex': True, # comment to support bold font in legend, and font will be bolder
# 'pgf.rcfonts': False,
})

layout

  1. picture size
1

  1. figure size (adjust x,y tick distance)
1
2
3
4
5
6
7
8
# mpl
fig.set_size_inches(w= 0.5 * x_count * (group_count+1.5), h=5.75 * 0.8) #(8, 6.5)

# plotly
fig.update_layout(height=350, width=50 * graph_data.size(),
margin=dict(b=10, t=10, l=20, r=5),
bargap=0.2 # x tick distance, 1 is the normalize-distance of adjacent bars
)
  1. relative position
1
2
# mpl: Adjust the left margin to make room for the legend, left & right chart vertical line position from [0,1]
plt.subplots_adjust(left=0.1, right=0.8)

set x axis

1
2
3
4
5
6
7
8
9
10
11
12
# mpl:
x = np.arange(len(graph_data.x)) # the label locations, [0, 1, 2, 3, 4, 5, 6, 7]
# set the bar move to arg1 with name arg2
ax.set_xticks(x + (group_count-1)*0.5*width, graph_data.x)
plt.xticks(fontsize=graph_data.fontsize+4)
# Adjust x-axis limits to narrow the gap
plt.xlim(-(0.5+gap_count)*width,
x_count - 1 + (group_count-1)*width + (0.5+gap_count)*width)


# plotly

set y axis

  1. vertical grid line
  2. dick size
  3. and range size
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# mpl
ax.set_ylabel(yaxis_title,
fontsize=graph_data.fontsize,
fontweight='bold')
plt.grid(True, which='major',axis='y', zorder=-1.0) # line and bar seems need to set zorder=10 to cover it
plt.yticks(np.arange(0, max_y ,10), fontsize=graph_data.fontsize) # step 10
plt.yscale('log',base=10) # or plt.yscale('linear')
ax.set_ylim(0.1, max_y)
## highlight selected y-label: https://stackoverflow.com/questions/73597796/make-one-y-axis-label-bold-in-matplotlib

# plotly
fig.update_layout(
yaxis_range=[0,maxY],
yaxis=dict(
rangemode='tozero', # Set the y-axis rangemode to 'tozero'
dtick=maxY/10,
gridcolor='rgb(196, 196, 196)', # grey
gridwidth=1,
),
)

Bolden Contour Lines

  1. Entire Figure
1
2
3
4
5
6
7
8
# mpl ?

# plotly: Add a rectangle shape to cover the entire subplot area
fig.add_shape(type="rect",
xref="paper", yref="paper",
x0=0, y0=0,
x1=1, y1=1,
line=dict(color="black", width=0.7))
  1. and Each Bar Within
1
2
3
4
# mpl: Create a bar chart with bold outlines
plt.bar(categories, values, edgecolor='black', linewidth=2)

# plotly: ?

legend box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mpl: 
ax.legend(loc='upper left', ncols=3, fontsize=graph_data.fontsize)
# legend out-of-figure, (1.02, 1) means anchor is upper right corner
plt.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)

# Calculate the legend width based on the figure width
fig_size = plt.gcf().get_size_inches()
fig_width = fig_size[0]
# Move the legend to the center above the ceiling line
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1),
ncol=2, # ncol=2 to have labels in one line
frameon=False, # frameon=False removes the legend box outline
columnspacing=fig_width, # distance between each label
handlelength=1.0, # label-box width (unit is text fontsize)
handleheight=1.0, # label-box heigh (unit is text fontsize)
prop={'size': 20, 'weight': 'bold'} # text fontsize
)

bar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# mpl: white hugo hatch with black bar edge.
# Problem: because the bug of mpl. hatch color follow the edge color
# Solved: draw bar twice, first the white hugo hatch with white bar edge. Second empty figure with black bar edge.
# white doubel ref: https://stackoverflow.com/questions/38168948/how-to-decouple-hatch-and-edge-color-in-matplotlib
# ref2: https://stackoverflow.com/questions/71424924/how-to-change-the-edge-color-of-markers-patches-in-matplotlib-legend
color_palette = ['black', (193/255, 1/255, 1/255), 'w', (127/255, 126/255, 127/255), 'blue']
pattern_list = ["", "/", "+", "\\", "x"]
edgecolor_list = ['w', 'w', (0/255, 176/255, 80/255), 'w', 'w']

ax.bar(x + offset, measurement, width, label=species_name,
color=color_palette[idx],
hatch = pattern_list[idx],
edgecolor=edgecolor_list[idx],
linewidth=1,
)
ax.bar(x + offset, measurement, width, label=species_name,
color = "none",
edgecolor='black', linewidth=1,
)
# related legend: https://stackoverflow.com/questions/71424924/how-to-change-the-edge-color-of-markers-patches-in-matplotlib-legend
handles1, labels1 = ax.get_legend_handles_labels()
plt.legend([handles1[2*idx]+handles1[2*idx+1] for idx in range(group_count)],
[labels1[2*idx] for idx in range(group_count)],
loc='upper center', bbox_to_anchor=(0.5, 1.12),
ncol=group_count, # have labels in one line
frameon=False,
# bbox_transform=plt.gcf().transFigure,
columnspacing=legend_width,
# handlelength=1.0,
handleheight=1.2,
prop={'size': graph_data.fontsize,
'weight': 'heavy'}
)


# plotly: find the overflow
overflow_pattern = ["/" if y > maxY else "" for y in entry[1]]
fig.add_bar(x=x,y=yList,
name=barName,
marker=dict(
color=color_list[i],
pattern_shape = overflow_pattern,
line=dict(color='black', width=2)
),
textfont=dict(size=graph_data.fontsize),
)
# legend
legend_num = len( barDict.items())
fig.update_layout(barmode="relative",

# legend_title="Legend Title",
legend = dict(
entrywidthmode='fraction', # https://plotly.com/python/legend/
entrywidth= 0.2,
x=0.5 - 0.5 * legend_num * 0.2, # Set x to 0.5 for the center
y=1.2, # Set y to a value greater than 1 to move it above the plot
orientation="h", # Display legend items in a single line
),
)

Out-box text

To draw symmetry chart, we need to special highlight the overflow bar number.

If the ancher point locate in the plot box, it’s easy to show text above the ceil line using textposition="bottom" like option. In the opposite scenario, plotly and mathplotlib all will hide the out-box text.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# plotly
fig.add_annotation(
x=[x[0][i],x[1][i]], # 注释的 x 坐标为 "bc"
y=min(maxY,entry), # 注释的 y 坐标为该列的最大值
text=f"{entry:.2f}", # 注释的文本内容
# valign = "bottom", # text position in text box(default invisible)
yanchor = "bottom", # text box position relative to anchor
showarrow=False, # 显示箭头
# bgcolor="rgba(255, 255, 255, 0.8)", # 注释框背景颜色
font=dict(size=graph_data.fontsize+2) # 注释文本字体大小
)

# mathplotlib
# Create labels for overflowed values
for i, value in enumerate(values):
if value > maxY:
ax.annotate(f'Overflow: {value:.2f}', (i, maxY), ha='center', va='bottom', fontsize=12)

But mlb can write text out box.

1
2
3
4
5
ax.text(1, -1.6, 'Increasing', ha="center")

# first parameter is text, xy is the anchor point, xytext is the text,
# xytext 2 xy is a relative distance
ax.annotate('yahaha', xy=(0, -0.1), xycoords='axes fraction', xytext=(1, -0.1))

out-box line

mathplotlib(mpl) can achieve this using ref, but there are few blogs about plotly.

1
2
3
4
5
6
7
8
9
10
# mpl: from 1*1 size full-graph (0.5,0.2) to point (1,0.8)
# transform=gcf().transFigure : gcf() stands for "get current figure," and .transFigure indicates that the coordinates provided in [0.5, 0.5], [0, 1] are in figure-relative coordinates. This means that the line's position is defined relative to the entire figure, not just the axes
# clip_on=False : This setting means that the line is not clipped at the edges of the axes. It allows the line to extend beyond the axes' boundaries.
from pylab import *
plot([0.5, 1], [0.2, 0.8], color='lightgreen', linestyle='--', lw=1 ,transform=gcf().transFigure, clip_on=False)

# mpl: arrow from xy 2 xytext
# xycoords='figure fraction' to Add annotation to the figure (full graph)
ax.annotate('', xy=(0, -0.1), xycoords='axes fraction', xytext=(1, -0.1),\
arrowprops=dict(arrowstyle="->", color='violet'))

in-box line

1
2
3
4
5
6
7
# mpl:
ax.axhline(y=12, color='red', linestyle='--', label='Horizontal Line at y=12')
ax.axvline(x=3, color='green', linestyle='-.', label='Vertical Line at x=3')

# plotly ref: https://plotly.com/python/horizontal-vertical-shapes/
fig.add_vline(x=2.5, line_width=3, line_dash="dash", line_color="green") # dot
fig.add_hline(y=0.9)

实践

气泡图

二维无向图

教程

3D图

dash + plotly

如果防火墙是关闭的,你可以直接部署在external address上。使用docker也是可行的办法

1
app.run_server(debug=True, host='202.38.72.23')

3D 散点图,拟合曲面与网格图

实际案例

折线图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt

X = [str(i) for i in metricValue]
Y = accuracyResult

# 设置图片大小
fig, ax = plt.subplots(figsize=(10, 6)) # 指定宽度为10英寸,高度为6英寸

plt.plot(X, Y, marker='o')
plt.xlabel('Threshold Percentage(%)', fontsize=12) # 设置x轴标签字体大小为12
plt.ylabel('Average Execution Time of Static Method', fontsize=12) # 设置y轴标签字体大小为12
plt.title('Tuning load store pressure', fontsize=14) # 设置标题字体大小为14

for i in range(len(X)):
plt.text(X[i], Y[i], Y[i],
fontsize=10, # 设置文本字体大小为10
ha='center', # 设置水平对齐方式
va='bottom') # 设置垂直对齐方式

# 保存图片
plt.savefig(glv._get("resultPath") + f"tuning/{tuningLabel}/loadStorePressure.png", dpi=300) # 设置dpi为300,可调整保存图片的分辨率

plt.show() # 显示图片
plt.close()

Heatmap

实例

Stacked Bar & grouped compare bar

Example

Error Bars

在柱状图中,用于表示上下浮动的元素通常被称为“误差条”(Error Bars)。误差条是用于显示数据点或柱状图中的不确定性或误差范围的线条或线段。它们在柱状图中以垂直方向延伸,可以显示上下浮动的范围,提供了一种可视化的方式来表示数据的变化或不确定性。误差条通常通过标准差、标准误差、置信区间或其他统计指标来计算和表示数据的浮动范围。

Errorbars + StackedBars stacked 的过程中由于向上的error线的会被后面的Bar遮盖,然后下面的error线由于arrayminus=[i-j for i,j in zip(sumList,down_error)]导致大部分时间说负值,也不会显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fig = go.Figure()

# color from https://stackoverflow.com/questions/68596628/change-colors-in-100-stacked-barchart-plotly-python
color_list = ['rgb(29, 105, 150)', \
'rgb(56, 166, 165)', \
'rgb(15, 133, 84)',\
'rgb(95, 70, 144)']
sumList = [0 for i in range(len(x[0]))]
for i, entry in enumerate( barDict.items()):
barName=entry[0]
yList = entry[1]
ic(sumList,yList)
sumList = [x + y for x, y in zip(yList, sumList)]
fig.add_bar(x=x,y=yList,
name=barName,
text =[f'{val:.2f}' for val in yList],
textposition='inside',
marker=dict(color=color_list[i]),
error_y=dict(
type='data',
symmetric=False,
color='purple',
array=[i-j for i,j in zip(up_error,sumList)],
arrayminus=[i-j for i,j in zip(sumList,down_error)],
thickness=2, width=10),
textfont=dict(size=8)
)

Candlestick

类似股票上下跳动的浮标被称为”Candlestick”(蜡烛图)或”OHLC”(开盘-最高-最低-收盘)图表。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,暂时没有校验其可靠性(看上去貌似说得通)。

[1] Saket, B., Endert, A. and Demiralp, Ç., 2018. Task-based effectiveness of basic visualizations.IEEE transactions on visualization and computer graphics,25(7), pp.2505-2512.

Python

解释性语言

解释型语言没有严格编译汇编过程,由解释器将代码块按需要变运行边翻译给机器执行。因此解释型语言一度存在运行效率底,重复解释的问题。但是通过对解释器的优化!可以提高解释型语言的运行效率。

Python 与大多数解释型语言一样,确实是将源代码编译为一组虚拟机指令,并且 Python 解释器是针对相应的虚拟机实现的。这种中间格式被称为 “字节码”。

Python 字节码

Python 以 .pyc 结尾的 “ 字节码(bytecode)” 文件(二进制文件),一般位于__pycache__ 的子目录中,可以避免每次运行 Python 时去重新解析源代码。

1
python -m py_compile file.py  # 生成单个pyc文件

python程序运行中的 Python 字节码 是什么时候产生的,我修改py文件,其对应的字节码文件会在何时更新

gpt3.5: Python字节码是由Python解释器在执行Python源代码之前生成的中间表示形式。它是将源代码翻译为机器可以理解的指令序列的一种形式。字节码文件的扩展名通常为.pyc(Python 3)或.pyo(Python 2)。

当你运行Python程序时,如果存在对应的字节码文件(.pyc或.pyo),解释器会首先检查字节码文件的时间戳。如果字节码文件的时间戳比源代码文件的时间戳更晚,且没有进行代码的修改,解释器会直接加载和执行字节码,跳过源代码的编译过程。

当你修改了Python源代码文件时,字节码文件不会立即更新。Python解释器会在下一次运行程序时,检测到源代码文件的修改,并重新编译生成新的字节码文件。这样,更新后的字节码文件将反映源代码的更改,并用于后续的执行。

访问和理解 Python 字节码

经过编译的python文件可以提高程序的运行速度,一定程度上也对源代码起到了保护作用。然而如果我们只有编译过的python字节码文件,就给我们审查源码造成了一定的困难,这就引出了python字节码反编译的需求。

如果你想玩转字节码,那么,Python 标准库中的 dis 模块将对你有非常大的帮助;dis 模块为 Python 字节码提供了一个 “反汇编”,它可以让你更容易地得到一个人类可读的版本,以及查找各种字节码指令。

知道如何去访问和阅读 Python 字节码将让你很容易回答为什么某些结构比其它结构运行的更快这样的问题(比如,为什么 {} 比 dict() 快)(尝试对比一下: dis.dis(“{}”) 与 dis.dis(“dict()”) 就会明白)。

pyo优化文件

pyo文件是源代码文件经过优化编译后生成的文件,是pyc文件的优化版本。编译时需要使用-O和-OO选项来生成pyo文件。在Python3.5之后,不再使用.pyo文件名,而是生成文件名类似“test.opt-n.pyc的文件。

1
python -O -m py_compile test.py

Python 虚拟机

CPython 使用一个基于栈的虚拟机。(你可以 “推入” 一个东西到栈 “顶”,或者,从栈 “顶” 上 “弹出” 一个东西来)。

CPython 使用三种类型的栈:

  1. 调用栈(call stack)。这是运行 Python 程序的主要结构。它为每个当前活动的函数调用使用了一个东西 —— “ 帧(frame)”
  2. 在每个帧中,有一个 **计算栈(evaluation stack)**(也称为 数据栈(data stack))。这个栈就是 Python 函数运行的地方,运行的 Python 代码大多数是由推入到这个栈中的东西组成的,操作它们,然后在返回后销毁它们。
  3. 在每个帧中,还有一个**块栈(block stack)**。它被 Python 用于去跟踪某些类型的控制结构:循环、try / except 块、以及 with 块,全部推入到块栈中,当你退出这些控制结构时,块栈被销毁。

C vs Python

运行流程区别

python的传统运行执行模式:录入的源代码转换为字节码,之后字节码在python虚拟机中运行。代码自动被编译,之后再解释成机器码在CPU中执行。

c编译器直接把c源代码编译成机器码。过程比python执行过程少了字节码生成和虚拟机执行字节码过程。所以自然比python快。

深、浅拷贝

Python append() 与深拷贝、浅拷贝

python赋值只是引用,别名

1
2
3
4
5
6
7
8
list.append('Google')   ## 使用 append() 添加元素
alist.append( num ) # 浅拷贝 ,之后修改num 会影响alist内的值

import copy
alist.append( copy.deepcopy( num ) ) # 深拷贝

# delete
del list[2]

for循环迭代的元素 也是 引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
original_list = [1, 2, 3]

for item in original_list:
item *= 2 # 每个元素是不可变的

print(original_list)

original_list = [[1,2,3], [2], [3]]

for item in original_list:
item.append("xxx") # 每个元素是可变的

print(original_list)

# [1, 2, 3]
# [[1, 2, 3, 'xxx'], [2, 'xxx'], [3, 'xxx']]

[函数传参是引用,但是能通过切片来得到类似指针](https

参数的传递
函数声明时的形参,使用时,等同于函数体内的局部变量。由于Python中一切皆为对象。因此,参数传递时直接传递对象的地址,但具体使用分两种类型:

  1. 传递不可变对象的引用(起到其他语言值传递的效果) 数字,字符串,元组,function等
  2. 传递可变对象的引用(起到其他语言引用传递的效果) 字典,列表,集合,自定义的对象等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def fun0(a):
a = [0,0] # a在修改后,指向的地址发生改变,相当于新建了一个值为[0,0]

def fun(a):
a[0] = [1,2]

def fun2(a):
a[:] = [10,20]

b = [3,4]
fun0(b)
print(b)
fun(b)
print(b)
fun2(b)
print(b)

# [3, 4]
# [[1, 2], 4]
# [10, 20]

return 返回值, 可变对象的也是引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fun1(l):
l.append("0")
return l

def fun2(l):
return l

if __name__=="__main__":
l = [1,2,3,4,5]

rel2 = fun2(l)
print(rel2)
rel1 = fun1(l)
print(rel1)
print(rel2)

# [1, 2, 3, 4, 5]
# [1, 2, 3, 4, 5, '0']
# [1, 2, 3, 4, 5, '0']

逻辑

setup

setup安装包的过程,请看pip package一文。

import

命名空间(namespace)可以基本理解成每个文件是一个,通过import来使用

触发 __init__.py

  • 当你导入一个包时,Python 会执行该包目录下的 __init__.py 文件。如果没有这个文件,Python 会认为这个目录不是一个包,因此 import 语句会失败。
  • __init__.py 负责初始化这个包,可以定义一些包级别的变量、函数或导入包的其他子模块。

行为

  • 每次导入包时,__init__.py 文件只会在第一次导入时被执行一次。如果模块已经被导入到当前的命名空间,再次 import 不会重新执行 __init__.py,除非你强制重新加载(比如用 importlib.reload())。
  • import 的执行会触发模块的初始化,类似于 C++ 中构造函数的概念,但不是在对象级别,而是在模块级别。
1
2
3
4
5
# example/__init__.py
print("Initializing the package")

def hello():
print("Hello from the package")
1
2
3
4
import example
# 输出 "Initializing the package"
example.hello()
# 输出 "Hello from the package"

入口

  • 在Python中,if __name__ == "__main__"这种写法通常出现在模块中,它的作用是控制模块的执行流程。
  • 当一个模块被导入时,Python解释器会自动将这个模块的__name__属性设置为模块名称。但是如果模块是被直接运行的,则__name__属性会被设置为字符串__main__。
  • 所以if name == “main”可以用来区分模块是被导入运行还是被直接运行:
  • 如果模块是被导入的,if语句不会执行。因为模块的__name__不等于__main__。
  • 如果模块是被直接运行的,if语句会执行。因为模块的__name__等于__main__。

清理与释放

程序结束时的清理行为(类似析构函数的操作)

在 Python 中,并没有像 C++ 那样显式的析构函数。模块或对象的清理一般通过以下方式实现:

  • 对象的析构:当一个 Python 对象的引用计数降为零时,Python 会自动调用该对象的 __del__ 方法进行资源清理。这个机制类似于 C++ 的析构函数,但触发时机取决于 Python 的垃圾回收机制。
1
2
3
4
5
6
7
8
9
class MyClass:
def __init__(self):
print("Object created")

def __del__(self):
print("Object destroyed")

obj = MyClass()
# 程序结束时,或者当 obj 的引用计数降为 0 时,触发 __del__()
  • 模块的清理:当程序结束时,Python 会尝试清理已加载的模块。这个过程会调用模块内一些特殊的钩子函数来进行必要的清理工作。虽然 Python 没有直接为模块提供析构函数,但是你可以使用 atexit 模块来注册一个函数,确保在程序结束时执行。

示例:使用 atexit 实现模块级别的清理操作

1
2
3
4
5
6
7
8
9
import atexit

def cleanup():
print("Cleaning up resources before program exit")

# 注册一个清理函数,在程序结束时自动调用
atexit.register(cleanup)

print("Program is running")

输出

1
2
Program is running
Cleaning up resources before program exit
  • atexit 模块允许你注册多个函数,它们会在解释器关闭之前按注册顺序依次执行。
  • 这种机制相当于 C++ 中的全局或静态对象析构函数的功能,确保在程序结束时执行一些清理工作。

模块的生命周期总结

  • 初始化:当模块被导入时,Python 会执行模块的顶层代码,包括 __init__.py 文件。这相当于模块的 “构造” 过程。
  • 对象的析构:在 Python 中,通过垃圾回收机制和 __del__ 方法来管理对象的生命周期。通常情况下,当对象不再被引用时,会自动触发清理。
  • 程序结束时的清理:Python 提供了 atexit 模块来执行程序结束时的资源清理操作。你可以在模块中注册一些函数,确保在程序退出时执行清理任务。

与 C++ 的比较

  • Python 的模块和包机制类似于 C++ 中的构造函数,但它的作用范围是模块级别的,而不是对象级别的。
  • Python 通过垃圾回收和 __del__ 方法来处理对象的清理,而不是像 C++ 中的显式析构函数。
  • Python 提供了 atexit 模块来实现程序级别的清理操作,这类似于 C++ 中全局/静态对象的析构行为,但更加灵活。

语法

装饰器 decorator

@能在最小改变函数的情况下,包装新的功能。^1

1
2
3
4
5
6
7
8
9
10
11
12
def use_logging(func):

def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper

@use_logging
def foo():
print("i am foo")

foo()

下划线

单下划线、双下划线、头尾双下划线说明:

  • __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 init() 之类的。
  • _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
  • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

函数传参

解包

  • 解包是指将一个容器(如列表、元组或字典)的内容拆分并分配给多个变量或作为参数传递给函数。
  • Python 提供了简洁的语法来实现这一点,使用 *** 分别解包可迭代对象和字典。

*args 和 **kwargs

在 Python 中,*args**kwargs 是非常强大的工具,用于处理可变数量的参数。它们使得函数可以接收任意数量的位置参数和关键字参数,并将这些参数传递给其他函数或方法。让我们详细解释一下你提供的代码片段:

1
2
def __call__(self, *input, **kwargs):
result = self.forward(*input, **kwargs)

为什么使用 *** 可以传递参数

  1. 收集参数
  • *input:收集所有未命名的位置参数(非关键字参数),并将它们打包成一个元组。
  • **kwargs:收集所有未明确列出的关键字参数,并将它们打包成一个字典。
  1. 解包参数
  • 在调用 self.forward 时,*input 将之前收集的位置参数解包为单独的参数传递给 forward 方法。
  • 同样,**kwargs 将之前收集的关键字参数解包为单独的关键字参数传递给 forward 方法。

具体工作原理

当你调用 __call__ 方法时,你可以传递任意数量的位置参数和关键字参数。例如:

1
2
obj = SomeClass()
obj(1, 2, 3, key1='value1', key2='value2')

在这个例子中:

  • 1, 2, 3 被收集到 *input 中,形成元组 (1, 2, 3)
  • key1='value1', key2='value2' 被收集到 **kwargs 中,形成字典 {'key1': 'value1', 'key2': 'value2'}

解包可迭代对象

函数定义中的 *

  • 位置参数收集

  • 在函数定义中,*args 用于收集所有未命名的位置参数(非关键字参数),并将它们打包成一个元组。

  • 强制关键字参数

  • 如果在参数列表中使用了单独的 *,那么 * 后面的所有参数必须以关键字形式传递。

示例

1
2
3
4
5
def example_function(a, b, *args):
print(f"a: {a}, b: {b}")
print("Additional positional arguments:", args)

example_function(1, 2, 3, 4, 5)

输出:

1
2
a: 1, b: 2
Additional positional arguments: (3, 4, 5)

强制关键字参数

1
2
3
4
5
6
7
8
def another_function(a, b, *, x, y):
print(f"a: {a}, b: {b}, x: {x}, y: {y}")

# 下面的调用会报错,因为 x 和 y 必须是关键字参数
# another_function(1, 2, 3, 4)

# 正确的调用方式
another_function(1, 2, x=3, y=4)

输出:

1
a: 1, b: 2, x: 3, y: 4

函数调用中的 *

1
2
3
4
5
6
def sum_three_numbers(x, y, z):
return x + y + z

numbers = [1, 2, 3]
result = sum_three_numbers(*numbers)
print(result) # 输出:6

解包字典

  • 在函数定义中,** 用于将传入的关键字参数打包成一个字典;
  • 而在函数调用中,** 则用于将字典解包为关键字参数。

函数定义中的 **kwargs

当你在函数定义中使用 **kwargs 时,所有未明确列出的关键字参数都会被收集到一个名为 kwargs 的字典中。

1
2
3
4
5
def example_function(a, b, **kwargs):
print(f"a: {a}, b: {b}")
print("Additional arguments:", kwargs)

example_function(1, 2, x=3, y=4)

输出:

1
2
a: 1, b: 2
Additional arguments: {'x': 3, 'y': 4}

函数调用中的 ** 解包字典

当你在函数调用中使用 ** 时,它会将字典中的键值对解包为关键字参数传递给函数。这意味着字典的键会成为参数名,对应的值会成为参数值。

1
2
3
4
5
def another_function(a, b, x, y):
print(f"a: {a}, b: {b}, x: {x}, y: {y}")

args_dict = {'x': 3, 'y': 4}
another_function(1, 2, **args_dict)

输出:

1
a: 1, b: 2, x: 3, y: 4

在这个例子中,args_dict 是一个字典,包含键 xy 及其对应的值。通过 **args_dict,这些键值对被解包为关键字参数传递给 another_function

DEBUG

段错误

  1. 开启 Python 的调试模式
    通过设置环境变量启用 Python 的调试信息,这有助于捕获异常和详细的堆栈信息。

    1
    export PYTHONMALLOC=debug
  2. 使用 faulthandler 模块
    Python 提供了一个 faulthandler 模块,可以用来捕获段错误并打印堆栈信息。你可以在程序的开头添加以下代码来启用它:

    1
    2
    import faulthandler
    faulthandler.enable()

    这将会在段错误发生时输出堆栈跟踪。

  3. 查看 Python 调试输出
    启动 Python 程序时,通过 faulthandler 打印堆栈信息,或通过 GDB 调试 Python 解释器。如果 Python 解释器发生崩溃,faulthandler 会帮助你定位错误。

doctest

函数的单元测试

打印当前堆栈

traceback.print_stack()

VizTracer时间性能分析

1
2
3
4
5
6
7
8
9
10
from viztracer import VizTracer

tracer = VizTracer(max_stack_depth=2) # 限制记录的调用栈深度为2,常用为 50和120
tracer.start()

# 你的代码
your_function()

tracer.stop()
tracer.save("result.json")

icecream for debug

rich 库是icecream的上位替代

  • rich:功能更全:支持任意对象的详细信息,包括method; 支持log;支持进度条;支持打印堆栈。
  • rich:打印更华丽

pprint 也不错

pprint 是 Python 的 pprint 模块中的一个函数,全称是 pretty-print(漂亮打印)。它用于以更易读的格式打印数据结构,如字典、列表等。

1
2
from pprint import pprint
pprint(obj)
  • 优雅打印对象:函数名,结构体
  • 打印行号和栈(没用输入时
  • 允许嵌套(会将输入传递到输出
  • 允许带颜色ic.format(*args)获得ic打印的文本
  • debug ic.disable()and ic.enable()
  • 允许统一前缀 ic.configureOutput(prefix='Debug | ')
  • 不用每个文件import
1
2
3
4
5
6
7
from icecream import ic
ic(STH)

from icecream import install
install()

ic.configureOutput(prefix='Debug -> ', outputFunction=yellowPrint)

icecream 是实时打印

普通print不是实时的,可能会出现,代码顺序在后面的ic反而打印在print前面。为此需要print(xxx,flush=True)

prefix 打印代码位置和时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import datetime
import inspect
from icecream import ic

def ic_with_timestamp(*args):
# Get the current frame's information
frame = inspect.currentframe().f_back # Get the caller's frame
filename = frame.f_code.co_filename # File where the function is called
lineno = frame.f_lineno # Line number where the function is called

timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

# Format the output to include timestamp, file, and line number
return '\n\n%s %s:%d shaojieLog >| ' % (timestamp, filename, lineno)

# Configure icecream to use this custom output function
ic.configureOutput(prefix=ic_with_timestamp)

# Example usage
ic("This is a test message.")

prefix 添加时间

1
2
3
4
5
import datetime
def ic_with_timestamp(*args):
return '\n\n%s shaojieLog >| ' % datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

ic.configureOutput(prefix=ic_with_timestamp)

打印ic间时间间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import datetime
import inspect
from icecream import ic

# Initialize a variable to store the time of the last ic call
last_ic_time = None
initial_ic_time = datetime.datetime.now() # Set the initial time when the script starts

# Define a custom function that prepends the time since the last ic call, file name, and line number
def ic_with_timestamp(*args):
global last_ic_time
current_time = datetime.datetime.now()

# Calculate the time difference if there was a previous ic call
if last_ic_time is not None:
time_diff = current_time - last_ic_time
time_diff_str = f" (+{time_diff.total_seconds():.2f}s)"
else:
time_diff_str = ""

# Calculate the time since the initial call
time_since_initial = current_time - initial_ic_time
time_since_initial_str = f" [Total time: {time_since_initial.total_seconds():.2f}s]"

# Update last_ic_time to the current time
last_ic_time = current_time

return f'\n\n{current_time.strftime("%Y-%m-%d %H:%M:%S")}{time_diff_str}{time_since_initial_str} shaojieLog |> '
ic.configureOutput(prefix=ic_with_timestamp)

torchrun等多进程环境,利用dist.rank==0来保证只有一个打印

1
2
3
4
5
6
7
8
# Disable icecream
ic.disable()

# This message will be hidden
ic("This message will NOT be shown")

# Re-enable icecream
ic.enable()

ic()的输出无法被tee的log文件捕获

这个问题与 icecream 库的 ic() 函数的默认输出机制有关。icecream 默认将输出发送到标准错误(stderr),而 tee 命令的默认行为是只捕获标准输出(stdout)。因此,ic() 的输出不会被 tee 捕获到。

要解决这个问题,你可以采取以下几种方式:

  1. 使用 ic() 输出到标准输出
  2. 你可以配置 icecream 的输出流,使其输出到标准输出,而不是默认的标准错误。这样,tee 就可以捕获 ic() 的输出。
1
2
3
4
from icecream import ic
import sys

ic.configureOutput(outputFunction=sys.stdout.write)

这样,ic() 的输出就会被发送到标准输出,然后可以被 tee 命令捕获到。

  1. tee 捕获标准错误和标准输出

你也可以让 tee 捕获标准错误(stderr)和标准输出(stdout),这样无需修改 icecream 的配置。

在你的命令中,可以使用如下方式:

1
python3.8 setup.py build bdist_wheel 2>&1 | tee compile.log

在这个命令中,2>&1 将标准错误重定向到标准输出,因此 tee 可以同时捕获两者。

  1. 使用 tee 捕获标准错误单独输出
如果你只想捕获标准错误的输出,并将其保存到日志文件,可以使用以下命令:

1
python3.8 setup.py build bdist_wheel 1>&2 | tee compile.log
或将 `stderr` 和 `stdout` 单独重定向:
1
python3.8 setup.py build bdist_wheel 2>compile.log

性能优化 与 可视化

定位 Python 中 setup.py 脚本运行缓慢的 热点,可以通过多种方式进行性能分析,具体步骤取决于你想了解的性能细节。以下是几种常见的方法来定位性能瓶颈。

方法 1: 使用 cProfile 进行性能分析

cProfile 是 Python 标准库中用于进行性能分析的工具。你可以用它来跟踪 setup.py 执行时的函数调用并找到性能瓶颈。

cProfile + snakeviz + gprof2dot

1
./gprof2dot.py -f pstats Diff.status | dot -Tpng -o ./output/Diff.png

1.1 使用 cProfile 分析 setup.py

你可以通过 cProfile 运行 setup.py 并生成分析报告:

1
python -m cProfile -o setup.prof setup.py install

这将运行 setup.py 并将性能分析结果保存到 setup.prof 文件中。

1.2 可视化分析报告

使用 pstats 或者第三方工具 snakeviz 来分析 setup.prof

  1. 使用 pstats 来查看分析结果:

    1
    python -m pstats setup.prof

    然后,你可以在 pstats 交互式界面中输入命令,比如:

    • sort cumtime 按总耗时排序。
    • stats 查看函数调用的分析结果。
  2. 安装 snakeviz 来生成Web图形化报告:

    1
    pip install snakeviz

    运行 snakeviz 来可视化分析结果:

    1
    snakeviz setup.prof # deploy to 127.0.0.1:8080

    这样可以生成一个图形化的界面,显示每个函数的执行时间以及调用关系,让你更直观地看到性能瓶颈。

  3. 使用 gprof2dot 生成调用关系图片:

    安装 gprof2dot 工具:pip install gprof2dot

    使用 gprof2dot 将 cProfile 生成的 output.prof 转换为 .dot 文件:gprof2dot -f pstats output.prof | dot -Tsvg -o output.svg

    这里的 -f pstats 表示输入的格式是 cProfile 生成的 pstats 文件。这个命令会将结果转换为 SVG 格式的火焰图,保存为 output.svg。

    打开生成的 SVG 文件,查看火焰图。

  4. 生成火焰图: flameprof

    1. 正常的火焰图说明了上到下的调用关系,倒置火焰图说明了底层最耗时的元素。
    2. python flameprof.py input.prof > output.svg
  5. 生成火焰图(有详细文件路径): flamegraph

    1. flameprof --format=log requests.prof | xxx_path/flamegraph.pl > requests-flamegraph.svg

方法 3: 使用 line_profiler 进行逐行性能分析

如果你想深入了解 setup.py 的某个函数或一组函数的逐行性能,可以使用 line_profiler 工具来分析代码的逐行执行时间。

3.1 安装 line_profiler

1
pip install line_profiler

3.2 添加装饰器

首先,在 setup.py 中找到你想要分析的函数,添加 @profile 装饰器(在 line_profiler 中的分析模式下使用):

1
2
3
@profile
def some_function():
# Your function code

3.3 运行 line_profiler

你可以使用 kernprof.py 来运行 setup.py 并生成逐行性能报告:

1
kernprof -l -v setup.py install

这将运行 setup.py 并生成一份逐行性能分析报告,显示每一行代码的耗时。

方法 4: 使用 Py-Spy 进行实时性能分析(推荐!!!)

Py-Spy 是一个 Python 的取样分析器,它可以在不修改代码的情况下对 Python 程序进行性能分析,并生成实时的性能报告。

py-spy top — xxx 有时会卡住

4.1 安装 Py-Spy

1
pip install py-spy

4.2 运行 Py-Spysetup.py 进行分析

你可以在执行 setup.py 的同时运行 Py-Spy 进行取样分析:

1
py-spy top -- python setup.py install

这会生成一个实时的报告,类似于 top 命令,显示当前正在运行的 Python 函数以及其消耗的 CPU 时间。

4.3 生成火焰图

如果你希望生成一个更直观的火焰图,可以使用 py-spy 生成火焰图文件:

1
py-spy record -o profile.svg -- python setup.py install

然后你可以打开 profile.svg 文件,查看一个交互式的火焰图,清晰展示函数调用的时间分布。

方法 5: 使用 strace 分析系统调用

如果 setup.py 涉及大量的 I/O 操作(比如读写文件或安装依赖包),可能是这些操作导致了性能瓶颈。你可以使用 strace 来分析 setup.py 的系统调用,找到 I/O 操作的瓶颈。

1
strace -tt -T -o strace.log python setup.py install
  • -tt 选项会显示每个系统调用的时间戳。
  • -T 会显示每个系统调用耗时。
  • -o 将结果输出到 strace.log 文件中。

通过查看 strace.log,你可以找出系统调用中哪些操作耗时过长。


总结

  1. 使用 cProfilePy-Spy 进行函数级别的性能分析,找出执行慢的函数。
  2. 如果需要更细粒度的逐行分析,使用 line_profiler 来分析慢的部分。
  3. 如果怀疑是 I/O 问题,用 strace 来检查系统调用。
  4. 使用 time 在脚本中插入计时代码,快速定位长时间的执行步骤。

这些工具可以帮助你定位和修复 setup.py 运行缓慢的热点。

虚拟环境venv

1
2
3
4
5
6
7
8
python3 -m venv name

#在Windows上,运行:
name\Scripts\activate.bat # poweshell运行activate.ps1
#在Unix或MacOS上,运行:
source name/bin/activate
#(这个脚本是为bash shell编写的。如果你使用 csh 或 fish shell,你应该改用 activate.csh 或 activate.fish 脚本。)
python3 setup.py install

实践

  1. 并行调用shell命令,超时kill
  2. 基于Pipe的自定义多进程进度条

数据快速写入和读取文件

任意变量使用pickle

1
2
3
4
5
# 使用二进制
with open('my_dict.json', 'wb') as f:
pickle.dump(my_dict, f)
with open('my_dict.json', 'rb') as f:
loaded_dict = pickle.load(f)

可以序列化的使用json

1
2
3
4
5
6
7
8
import json
# 将 dict 保存为 JSON 格式
with open('my_dict.json', 'w') as f:
json.dump(my_dict, f)

# 加载 dict
with open('my_dict.json', 'r') as f:
loaded_dict = json.load(f)

多个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 将多个变量组织成字典或列表
data = {
"scaAvgTime": scaAvgTime,
"var2": var2,
"var3": var3
}

result_file = "result.json"

# 将数据写入JSON文件
with open(result_file, "w") as f:
json.dump(data, f)

# 读取JSON文件
with open(result_file, "r") as f:
data = json.load(f)

# 获取保存的变量值
scaAvgTime = data["scaAvgTime"]
var2 = data["var2"]
var3 = data["var3"]

参考文献

https://zhuanlan.zhihu.com/p/39259061

Python: DataStructure

开发基础

size 大小

1
len(day)

空值判断

strings, lists, tuples

1
2
3
4
5
6
7
# Correct:
if not seq:
if seq:

# Wrong:
if len(seq):
if not len(seq):

中断捕获

1
2
3
4
5
6
7
8
try:
# sth
except Exception as e:
# 可以使用rich包
pprint.pprint(list)
raise e
finally:
un_set()

for

间隔值

调参需要测试间隔值

1
2
for i in range(1, 101, 3):
print(i)

遍历修改值

  • 使用 enumerate 函数结合 for 循环遍历 list,以修改 list 中的元素。
  • enumerate 函数返回一个包含元组的迭代器,其中每个元组包含当前遍历元素的索引和值。在 for 循环中,我们通过索引 i 修改了列表中的元素。
1
2
3
4
5
# 对于 二维list appDataDict
baseline = appDataDict[0][0] # CPU Total
for i, line in enumerate(appDataDict):
for j, entry in enumerate(line):
appDataDict[i][j] = round(entry/baseline, 7)

itertools

itertools — 为高效循环而创建迭代器的函数

1
for a,b,c in permutations((a,b,c)):

小数位

x = round(x,3)# 保留小数点后三位

String 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
%c  格式化字符及其ASCII码
%s 格式化字符串
%d 格式化整数
%u 格式化无符号整型
%o 格式化无符号八进制数
%x 格式化无符号十六进制数
%X 格式化无符号十六进制数(大写)
%f 格式化浮点数字,可指定小数点后的精度
%e 用科学计数法格式化浮点数
%E 作用同%e,用科学计数法格式化浮点数
%g %f和%e的简写
%G %F 和 %E 的简写
%p 用十六进制数格式化变量的地址
1
print("My name is %s and weight is %d kg!" % ('Zara', 21))

string <-> list

' '.join(pass_list) and pass_list.split(" ")

对齐"\n".join(["%-10s" % item for item in List_A])

开头判断

1
2
3
4
5
6
text = "Hello, world!"

if text.startswith("Hello"):
print("The string starts with 'Hello'")
else:
print("The string does not start with 'Hello'")

格式化

Python2.6 开始,通过 {}: 来代替以前的 %

1
2
3
4
5
6
7
8
9
>>>"{} {}".format("hello", "world")    # 不设置指定位置,按默认顺序
'hello world'

>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'

# 字符串补齐100位,<表示左对齐
variable = "Hello"
padded_variable = "{:<100}".format(variable)

数字处理

1
2
3
4
print("{:.2f}".format(3.1415926)) # 保留小数点后两位

{:>10d} 右对齐 (默认, 宽度为10)
{:^10d} 中间对齐 (宽度为10)

NumPy

布尔索引

保留 frame_indices 中的值小于 max_frame 的元素。

1
frame_indices = frame_indices[frame_indices < max_frame]

容器:List

https://www.runoob.com/python/python-lists.html

初始化以及访问

1
2
3
list = ['physics', 'chemistry', 1997, 2000]
list = [] ## 空列表
print(list[0])

切片

格式:[start_index:end_index:step]

不包括end_index的元素

二维数组

1
2
3
4
5
6
7
8
list_three = [[0 for i in range(3)] for j in range(3)]

//numpy 创建连续的,可自动向量化,线程并行
import numpy as np
# 创建一个 3x4 的数组且所有值全为 0
x3 = np.zeros((3, 4), dtype=int)
# 创建一个 3x4 的数组,然后将所有元素的值填充为 2
x5 = np.full((3, 4), 2, dtype=int)

排序

1
2
3
4
5
6
7
8
9
10
11
12
# take second element for sort
def takeSecond(elem):
return elem[2]

LCData.sort(key=takeSecond)

# [1740, '黄业琦', 392, '第 196 场周赛'],
# [1565, '林坤贤', 458, '第 229 场周赛'],
# [1740, '黄业琦', 458, '第 229 场周赛'],
# [1509, '林坤贤', 460, '第 230 场周赛'],
# [1740, '黄业琦', 460, '第 230 场周赛'],
# [1779, '黄业琦', 558, '第 279 场周赛'],

对应元素相加到一个变量

1
2
3
tmp_list = [[],[],[],[]]
# 注意不需要右值赋值
[x.append(copy.deepcopy(entry)) for x,entry in zip(tmp_list, to_add)]

两个list对应元素相加

对于等长的

1
2
3
4
5
list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8, 9, 10]

result = [x + y for x, y in zip(list1, list2)]
print(result)

如果两个列表的长度不同,你可以使用zip_longest()函数来处理它们。zip_longest()函数可以处理不等长的列表,并使用指定的填充值填充缺失的元素。

1
2
3
4
5
6
7
from itertools import zip_longest

list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8]

result = [x + y for x, y in zip_longest(list1, list2, fillvalue=0)]
print(result)

如果是二维list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
list1 = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

list2 = [[10, 11, 12],
[13, 14, 15]]

rows = max(len(list1), len(list2))
cols = max(len(row) for row in list1 + list2)

result = [[0] * cols for _ in range(rows)]

for i in range(rows):
for j in range(cols):
if i < len(list1) and j < len(list1[i]):
result[i][j] += list1[i][j]
if i < len(list2) and j < len(list2[i]):
result[i][j] += list2[i][j]

print(result)

# 将一个二维列表的所有元素除以一个数A
result = [[element / A for element in row] for row in list1]

容器:元组Tuple

  • 元组和列表类似,但是不同的是元组不能修改,但可以对元组进行连接组合,元组使用小括号。
  • 元组中只包含一个元素时,需要在元素后面添加逗号,否则括号会被当作运算符使用。
1
2
3
4
5
#创建元组
tup = (1, 2, 3, 4, 5)
tup1 = (23, 78);
tup2 = ('ab', 'cd')
tup3 = tup1 + tup2

容器:Dict

初始化

1
2
3
>>> tinydict = {'a': 1, 'b': 2, 'b': '3'}
>>> tinydict['b']
'3'

empty dict

1
2
a= {}
a=dict()
1
2
3
4
5
6
7
8
9
10
11
12
13
a_dict = {'color': 'blue'}
for key in a_dict:
print(key)
# color
for key in a_dict:
print(key, '->', a_dict[key])
# color -> blue
for item in a_dict.items():
print(item)
# ('color', 'blue')
for key, value in a_dict.items():
print(key, '->', value)
# color -> blue

key 支持tuple元组

类似c++ 的 pair<int,int>

1
bblHashDict[(tmpHigherHash,tmpLowerHash)]=tmpBBL

但是这样就不支持json.dump, json.dump() 无法序列化 Python 中元组(tuple)作为字典的 key,这会导致 json.dump() 函数在写入此类字典数据时会进入死循环或陷入卡住状态

1
2
3
del tinydict['Name']  # 删除键是'Name'的条目
tinydict.clear() # 清空字典所有条目
del tinydict # 删除字典

1
2
3
4
tinydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

tinydict['Age'] = 8 # 更新
tinydict['School'] = "RUNOOB" # 添加

合并

1
2
3
4
5
6
7
dict1 = {'a': 10, 'b': 8} 
dict2 = {'d': 6, 'c': 4}

# dict2保留了合并的结果
dict2.update(dict1)
print(dict2)
{'d': 6, 'c': 4, 'a': 10, 'b': 8}

判断key 是否存在

以下是两种常用的方法:

方法一:使用in操作符: in操作符返回一个布尔值,True表示存在,False表示不存在。

1
2
3
4
5
6
7
8
Copy code
my_dict = {"key1": "value1", "key2": "value2", "key3": "value3"}

# 判断是否存在指定的键
if "key2" in my_dict:
print("Key 'key2' exists in the dictionary.")
else:
print("Key 'key2' does not exist in the dictionary.")

方法二:使用dict.get()方法: dict.get()方法在键存在时返回对应的值,不存在时返回None。根据需要选择适合的方法进行判断。

1
2
3
4
5
6
7
8
Copy code
my_dict = {"key1": "value1", "key2": "value2", "key3": "value3"}

# 判断是否存在指定的键
if my_dict.get("key2") is not None:
print("Key 'key2' exists in the dictionary.")
else:
print("Key 'key2' does not exist in the dictionary.")

这两种方法都可以用来判断字典中是否存在指定的键。

容器:set

无序不重复序列

初始化

1
2
3
4
5
a=  set() # 空set

thisset = set(("Google", "Runoob", "Taobao"))
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # 这里演示的是去重功能

1
thisset.add("Facebook")

1
2
3
4
s.remove( x )
# 使用 discard() 移除元素
my_set.discard(3) # 如果元素不存在则什么也不做。也不会报错 KeyError
a.clear()

合并

1
2
3
4
5
6
7
x = {"apple", "banana", "cherry"}
y = {"google", "runoob", "apple"}

z = x.union(y)

print(z)
# {'cherry', 'runoob', 'google', 'banana', 'apple'}

类型转换

list2set

1
setL=set(listV)

set2list

1
2
3
4
5
my_set = {'Geeks', 'for', 'geeks'}

s = list(my_set)
print(s)
# ['Geeks', 'for', 'geeks']

参考文献

https://blog.csdn.net/weixin_63719049/article/details/125680242

PythonRegex

pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
^	匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符
a| b 匹配a或b
[a-zA-Z0-9] 匹配任何字母及数字
\d 匹配数字。等价于[0-9]。
[aeiou] 匹配中括号内的任意一个字母
[^aeiou] 除了aeiou字母以外的所有字符
\w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'
(\s*) 或者 ([\t ]*) 来匹配任意TAB和空格的混合字符

\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'

重复

1
2
3
4
5
6
7
8
9
10
11
12
re*	匹配0个或多个的表达式。
re+ 匹配1个或多个的表达式。
re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n} 精确匹配 n 个前面表达式。
例如, o{2} 不能匹配 "Bob" 中的 "o",
但是能匹配 "food" 中的两个 o。
re{ n,} 匹配 n 个前面表达式。
例如, o{2,} 不能匹配"Bob"中的"o",
但能匹配 "foooood"中的所有 o。
"o{1,}" 等价于 "o+"。
"o{0,}" 则等价于 "o*"。
re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式

match exactlly str

1
2
3
4
5
6
7
# find should use \ to represent the (6|12|3)
$ find ~/github/gapbs/ -type f -regex '.*/kron-\(6\|12\|3\).*'
/staff/shaojiemike/github/gapbs/kron-12.wsg
/staff/shaojiemike/github/gapbs/kron-3.sg
/staff/shaojiemike/github/gapbs/kron-3.wsg
/staff/shaojiemike/github/gapbs/kron-6.sg
/staff/shaojiemike/github/gapbs/kron-6.wsg

re.match与re.search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;

而re.search匹配整个字符串,直到找到一个匹配。

re.match函数

从字符串的起始位置匹配

1
re.match(pattern, string, flags=0)

flags

多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M被设置成 I 和 M 标志:

1
2
3
re.I	使匹配对大小写不敏感
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 匹配包括换行在内的所有字符

group

1
2
3
4
5
6
7
8
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)

if matchObj:
print "matchObj.group() : ", matchObj.group()
print "matchObj.group(1) : ", matchObj.group(1)
print "matchObj.group(2) : ", matchObj.group(2)
else:
print "No match!!"

打印部分内容

1
2
3
matchObj.group() :  Cats are smarter than dogs
matchObj.group(1) : Cats
matchObj.group(2) : smarter

re.sub 替换

findall

返回元组,可以指定开始,与结束位置。

1
2
3
result = re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
print(result)
# [('width', '20'), ('height', '10')]

实例:objdump结果只提取汇编的命令

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

import re
# 打开x86汇编代码文件
with open(assembly) as f:
# 读取文件内容
content = f.read()

# 使用正则表达式匹配所有汇编指令,
pattern = r'\b([a-zA-Z]{3,6})\b.*'
# 匹配pattern,但是只将()内结果保存在matches中
matches = re.findall(pattern, content)

# 输出匹配结果
for match in matches:
print(match)

re.split

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://blog.csdn.net/weixin_39594191/article/details/111611346

https://www.runoob.com/python/python-reg-expressions.html

Python Class

简介

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。

面向对象技术简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

  • 实例化:创建一个类的实例,类的具体对象。

  • 方法:类中定义的函数。

    • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
    • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟”是一个(is-a)”关系(例图,Dog是一个Animal)。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

    • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
    • 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。

何时使用类

  1. 数据与操作紧密相关
  2. 对象有许多状态需要维护,可以使用类中的属性来保存状态。
  3. 需要生成多个仅在部分属性不同的实例,可以使用类作为模板。
  4. 不同对象存在公共parent-child的层次关系,可以使用继承来复用代码。
  5. 隐藏对象的实现细节,只对外公开接口。

类变量 与 实例变量

在Python中,类变量和实例变量是两个不同的概念:

  1. 类变量(Class Variable)
  • 定义在类内部,但不在任何方法之内
  • 被该类的所有实例对象所共享
  • 可以通过类名或实例对象访问
  • 用于定义与这个类相关的特征或属性
  1. 实例变量(Instance Variable)
  • 定义在类内部的方法之内
  • 每个实例对象拥有属于自己的变量副本
  • 只能通过实例对象访问
  • 用于定义实例对象的个性化特征或状态

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person:

species = 'Human' # 类变量

def __init__(self, name):
self.name = name # 实例变量

p1 = Person('John')
p2 = Person('Mary')

print(p1.species) # Human
print(p2.species) # Human

print(p1.name) # John
print(p2.name) # Mary

综上,类变量用于定义类的通用属性,实例变量用于定义实例的独特属性。区分二者是理解Python面向对象的关键。

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Employee:
'所有员工的基类'
empCount = 0 # 类变量

def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1

def displayCount(self):
print "Total Employee %d" % Employee.empCount

def displayEmployee(self):
print "Name : ", self.name, ", Salary: ", self.salary

类函数必有参数 ‘self’

必须有一个额外的第一个参数名称, 按照惯例它的名称是 self,self 不是 python 关键字,换成其他词语也行。

创建实例对象与访问

1
2
emp1 = Employee("Zara", 2000)
emp1.displayEmployee()

继承

通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。

继承语法 class 派生类名(基类名)

调用基类

调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
,这点在代码上的区别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base:
def base_method(self):
print("Base method")

class Derived(Base):
def derived_method(self):
# 调用基类方法要加类名前缀
Base.base_method(self)

# 调用普通函数
print("Hello")

d = Derived()
d.derived_method()

# 输出
Base method
Hello

在Derived类中:

  • 调用Base基类的方法base_method(),需要写成Base.base_method(self)

  • 调用普通函数print(),直接写函数名即可

区别在于:

  • 调用基类方法需要指明方法所属的基类
  • 基类方法需要传入self,指代实例自己

而对于普通函数,只需要直接调用即可,不需要self参数。

这与Python的名称空间和面向对象实现有关,是理解Python类继承的关键点。

运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方

PyTorch 中如果继承torch.nn.Module,执行__call__会转接到forward方法

torch.nn.Module__call__ 方法会调用 forward 方法,并且在调用 forward 之前和之后还会执行一些其他的操作,比如设置钩子(hooks)和调用 _check_forward_hooks 等。

以下是 torch.nn.Module__call__ 方法的简化实现逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Module:
def __call__(self, *input, **kwargs):
# 在调用 forward 之前执行一些操作
# 例如,调用 forward pre-hooks
for hook in self._forward_pre_hooks.values():
result = hook(self, input)
if result is not None:
if not isinstance(result, tuple):
result = (result,)
input = result

# 调用 forward 方法
result = self.forward(*input, **kwargs)

# 在调用 forward 之后执行一些操作
# 例如,调用 forward hooks
for hook in self._forward_hooks.values():
hook_result = hook(self, input, result)
if hook_result is not None:
result = hook_result

return result
  1. __call__ 方法:当你实例化一个 Module 并调用它时(例如 model(input)),Python 会调用 __call__ 方法。
  2. forward 方法__call__ 方法内部会调用 forward 方法,这是你需要在子类中重写的方法,用于定义前向传播的逻辑。
  3. 钩子(Hooks):在调用 forward 之前和之后,__call__ 方法会处理一些钩子。这些钩子可以用于调试、可视化或其他目的。

+=

在Python中可以通过特殊方法__iadd__来对+=符号进行重载。

__iadd__需要定义在类中,用于指定+=操作时的具体行为。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self

v1 = Vector(1, 2)
v2 = Vector(3, 4)

v1 += v2
print(v1.x, v1.y) # 4, 6

这里我们定义了__iadd__方法,用于实现两个Vector对象使用+=时的相加操作。

__iadd__方法的参数是另一个要相加的对象,在方法内部我们实现了两个向量的分量相加,并返回self作为结果。这样就实现了+=的运算符重载。

此外,Python还提供了__add__特殊方法用于重载+符号,但是__iadd__和__add__有以下区别:

  • __add__返回一个新的对象,不改变原有对象。
  • __iadd__在原有对象的基础上进行操作,并返回对原对象的引用。

所以对可变对象进行+=操作时,通常需要实现__iadd__方法。

参考文献

https://www.runoob.com/python/python-object.html

Python MPI

全局解释器锁(GIL,Global Interpreter Lock)

Python代码的执行由Python虚拟机(解释器)来控制。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。所以就会出现尽管你设置了多线程的任务,但是只能跑一个的情况。

但是I/O密集的程序(爬虫)相对好一点,因为I/O操作会调用内建的操作系统C代码,所以这时会释放GIL锁,达到部分多线程的效果。

通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

Read more

Latent Dirichlet Allocation (2003)

简介

该篇论文于2003年发表在“Journal of Machine Learning Research”期刊上,迄今引用次数已超过15000次,可见该论文对后来相关研究工作的影响之大。

首次正式将主题以隐变量的形式引入,形成一个三层贝叶斯模型,并且相比于之前和它最接近的pLSI文本模型,LDA的主题选取不再受训练集文本内容的束缚,是一个完全非监督且依据多个主题进行聚类的机器学习、数据挖掘领域的算法。

现实意义

在推荐系统的研究中,利用评论文本信息来提升推荐性能是近3-4年的一个热门研究领域,LDA及其改良的文本模型则是用来挖掘评论文本的主要方式。

早期文本模型

  1. TF-IDF文本模型(矩阵表示)
  2. LSI文本模型
    1. 第一个子矩阵代表了词与主题的关系,第二个子矩阵代表了主题本身,第三个子矩阵代表了主题与文档之间的关系。

LDA的建模介绍

  1. 用来训练文档的是基本块
  2. 每条指令说word
  3. 柏松分布

用变分推理求解LDA模型的参数

最重要的是LDA模型的两个参数,确定了后能在未知的文本里提取主题

Gensim简介、LDA编程实现、LDA主题提取效果图展示

  1. 统计词语出现的频率
  2. 为什么例子里的没有迭代次数呢?
  3. 调研为什么要pytorch tenceflow

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://zhuanlan.zhihu.com/p/28777266

https://blog.csdn.net/fish0058/article/details/25075591

https://blog.csdn.net/anqiu4023/article/details/102275607

https://pypi.python.org/pypi/lda

http://scikit-learn.org/dev/modules/generated/sklearn.decomposition.LatentDirichletAllocation.html#sklearn.decomposition.LatentDirichletAllocation

WebCrawler first try

常见的仿站软件尝试

  1. wget -c -r -np -k -L -p 递归下载
  2. webCopy
  3. WinHTTrack
  4. Octoparse
  5. Teleport pro

遇到的问题

尝试后下载了一些html\css\js文件。但是没有达到我的要求。

我猜测的爬取原理,根据网站返回的index.html以及文件里指向的新文件路径进行递归下载。

这样的问题有:

  1. 无法对json文件里指向的材质包路径进行递归下载
  2. 无法读取指定网站文件夹的目录,导致不知道文件夹里有什么文件
    1. 假如有ftp://可能可以

需要进一步的研究学习

  1. 通过python实现对json文件里指向的材质包路径进行递归下载(感觉只能半自动)
  2. 读取指定网站文件夹的目录

开题缘由、总结、反思、吐槽~~

在找live2d模型的时候找到了 https://github.com/Eikanya/Live2d-model ,然后其中有个HSO的demo网站https://l2d.alg-wiki.com/。

然后一开始我想在自己页面做一个仿站,后来了解后只想把他里面的live2d的材质数据、贴图等爬下来。但是遇到了几个问题。

参考文献

https://www.shuzhiduo.com/A/E35pV9EAzv/

python crawler