图像相关

10个Python图像处理工具,非常全了!

图像的RGB 色彩模式

RGB 三个颜色通道的变化和叠加得到各种颜色,其中

  • R 红色,取值范围,0-255
  • G 绿色,取值范围,0-255
  • B 蓝色,取值范围,0-255

一幅图像上的所有像素点的信息就完全可以采用矩阵来表示,通过矩阵的运算实现更加复杂的操作

网络图片读取

1
2
3
4
5
6
7
8
def get_url_img_io(url: str) -> BytesIO:
""" 获取网络图片的io流 """
return BytesIO(requests.get(url).content)


def get_url_img(url: str):
""" 获取网络图片,并转为np.array """
return np.asarray(bytearray(get_url_img_io(url).read()), dtype="uint8")
  • plt.imshow()函数负责对图像进行处理,并显示其格式
  • plt.show()则是将plt.imshow()处理后的函数显示出来

PIL

Python图像库(PIL(Python Image Library))是一个第三方Python包,为Python解释器添加了图像处理功能,允许处理照片并执行许多常见的图像文件操作,官方教程

打开图像:返回一个PIL.JpegImagePlugin.JpegImageFile对象

1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入 Image 模块
import requests
from PIL import Image

# 图像路径
img_path = r'frame.jpg'
img_url = r'https://pic.hycbook.com/i/hexo/post_cover/蕾姆10.webp'

# 打开本地图片 RGB
im = Image.open(img_path) # JpegImageFile image mode=RGB size=1408x1152

# 打开网络图片
img = Image.open(get_url_img_io(img_url)) # [H,W,C]

JpegImageFile对象常用方法

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

# Returns a histogram for the image
histogram = image.histogram()
# 可视化颜色分布直方图
plt.hist(histogram, bins=len(histogram))
plt.xlabel('Histogram')
plt.show()

# 裁剪图像 用于裁剪的开始和结束x/y坐标
# 用于裁剪的坐标将随照片而变化。事实上,可能应该更改此代码,使其接受裁剪坐标作为参数。可以自己反复尝试,找出要使用的裁剪边界框。可以使用像Gimp这样的工具来帮助你,用Gimp绘制一个边界框,并记下它给你的坐标,方便尝试使用Pillow
cropped = image.crop((40, 590, 979, 1500))

# 重新调整图像大小
img = im.resize((240,240))

# 图像旋转
img.rotate(90).show()

使用过滤器:Pillow包含有几个过滤器,可以将其应用于图像。以下是当前支持的筛选器

  1. BLUR
  2. CONTOUR
  3. DETAIL
  4. EDGE_ENHANCE
  5. EDGE_ENHANCE_MORE
  6. EMBOSS
  7. FIND_EDGES
  8. SHARPEN
  9. SMOOTH
  10. SMOOTH_MORE
1
2
3
from PIL import ImageFilter

blurred_image = image.filter(ImageFilter.BLUR)

显示图片

1
2
3
4
# 打开图片
im = Image.open(img_path)
# 显示图片
im.show()

保存图像

1
2
3
4
infile = "in.jpg"
outfile = "output.jpg"
with Image.open(infile) as im:
im.save(outfile)

类型转换

1
2
3
4
5
# JpegImageFile -> np.array  [H,W,C]
np.asanyarray(im)

# np.array -> JpegImageFile
img = Image.fromarray(arr)

cv2

在计算机视觉项目的开发中,OpenCV作为较大众的开源库,拥有了丰富的常用图像处理函数库,采用C/C++语言编写,可以运行在Linux/Windows/Mac等操作系统上,能够快速的实现一些图像处理和识别的任务。此外,OpenCV还提供了Java、python、cuda等的使用接口、机器学习的基础算法调用,从而使得图像处理和图像分析变得更加易于上手,让开发人员更多的精力花在算法的设计上

打开图像:cv2.imread(filepath, flags),返回一个np.array对象

flags:读入图片的标志

  • cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道
  • cv2.IMREAD_GRAYSCALE:读入灰度图片
  • cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道
1
2
3
4
5
6
7
8
9
# 图像路径
img_path = r'frame.jpg'
img_url = r'https://pic.hycbook.com/i/hexo/post_cover/蕾姆10.webp'

# 打开网络图片
img = get_url_img(img_url)

# 打开本地图片
image = cv2.imread(img_path) # [H,W,C] np.array

打开视频

常见操作

resize:

cv2.resize(InputArray src, OutputArray dst, Size, fx, fy, interpolation)

fx, fy 沿x轴,y轴的缩放系数
interpolation 插入方式
INTER_NEAREST 最近邻插值
INTER_LINEAR 双线性插值(默认设置)
INTER_AREA 使用像素区域关系进行重采样。
INTER_CUBIC 4x4像素邻域的双三次插值
INTER_LANCZOS4 8x8像素邻域的Lanczos插值
1
2
img_test = cv2.resize(img, (0, 0), fx=0.25, fy=0.25, interpolation=cv2.INTER_NEAREST)
img_test = cv2.resize(img_test, (0, 0), fx=4, fy=4, interpolation=cv2.INTER_NEAREST)

RGB转灰度:

1
img2 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

灰度转RBG:

1
img3 = cv2.cv2Color(img, cv2.COLOR_GRAY2RGB)

显示图片

1
2
cv2.imshow("local pic", image)
cv2.waitKey(0)

保存图像

1
cv2.imwrite(outfile, image)

时间相关库

协调世界时(Coordinated Universal Time, UTC)是一种标准的时间表述方式,它与时区无关

有些计算机,用某时刻与UNIX时间原点之间相差的秒数,来表示那个时刻所对应的时间
对于这些计算机来说,UTC是一种非常好的计时方式

Python提供了两种时间转换方式

  • 旧的方式,是使用内置的time模块,这是一种极易出错的方式

  • 新的方式,则是采用内置的datetime模块,该模块的效果非常好

小结

  • time模块需要依赖操作系统而运作。该模块的实际行为,取决于底层的C函数如何与宿主操作系统相交互

  • 这种工作方式,使得time模块的功能不够稳定。它无法协调地处理各种本地时区,因此,我们应该尽量少用这个模块

如果一定要使用time模块,那就只应该用它在UTC与宿主计算机的当地时区之间进行转换

对于其他类型的转换来说,还是使用datetime模块比较好

常见的时间模块

  • time: Python内置时间库,通过时间戳或元组表示时间,功能简约但实用
  • datetime: 内置日期库,处理日期时间对象和属性
  • dateutil: 基于datetime库的实用拓展,增强了对时间间隔和时间序列的处理

time_datetiime

time库的使用

time— Time access and conversions

time库是Python中处理时间的标准库

  • 计算机时间的表达

  • 提供获取系统时间并格式化输出功能

  • 提供系统级精确计时功能,用于程序性能分析

时间获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 获取当前时间戳,即计算机内部时间值,浮点数
>>>time.time()
1568506809.169575

# 获取当前时间并以易读方式表示,返回字符串, 将 gmtime()或localtime() 返回的 表示时间的 元组或struct_time 转换成一个字符串,格式:'Sun Jun 20 23:21:051993'。
>>>time.ctime() # 等价于 time.asctime(time.localtime(time.time()))
'Sun Sep 15 08:20:16 2019'

# 获取当前时间,表示为计算机可处理的时间格式,返回的时间是两个时区对相同时刻的时间表示
# 返回的是struct_time格式,是tuple的子类
>>>time.gmtime() # 0时区:time.gmtime() 本地时区:time.localtime(seconds=None)
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=15, tm_hour=0, tm_min=21, tm_sec=40, tm_wday=6, tm_yday=258, tm_isdst=0)

# time.mktime(t)是localtime()的反函数
>>>time.mktime(time.localtime())
1568508316.0

struct_time元组的属性如下

序号 属性
0 tm_year 2008
1 tm_mon 1 到 12
2 tm_mday 1 到 31
3 tm_hour 0 到 23
4 tm_min 0 到 59
5 tm_sec 0 到 61 (60或61 是闰秒)
6 tm_wday 0到6 (0是周一)
7 tm_yday 1 到 366(儒略历)
8 tm_isdst -1, 0, 1, -1是决定是否为夏令时的旗帜

时间格式化

  • 对象转字符串: time.strftime(format, obj)
  • 字符串转结构化: time.strptime(time_str, format)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# strftime(tpl, ts)
# tpl是格式化模板字符串,用来定义输出效果
# ts是计算机内部时间类型变量
>>>t = time.gmtime()
>>>time.strftime("%Y-%m-%d %H:%M:%S", t)
'2019-09-15 00:24:23'

# strptime(str, tpl)
# str是字符串形式的时间值
# tpl是格式化模板字符串,用来定义输入效果
>>>timeStr = '2018-01-26 12:55:20'
>>>time.strptime(timeStr, "%Y-%m-%d %H:%M:%S")
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=15, tm_hour=0, tm_min=25, tm_sec=50, tm_wday=6, tm_yday=258, tm_isdst=-1)

# 结构化对象转时间戳
>>>time.mktime(time.localtime())
1668562455.0
# 时间戳转结构化对象
>>>time.localtime(time.time())
time.struct_time(tm_year=2022, tm_mon=11, tm_mday=16, tm_hour=9, tm_min=37, tm_sec=9, tm_wday=2, tm_yday=320, tm_isdst=0)
  • 常用格式(年月日、时分秒)

    | 格式化字符串 | 日期/时间说明 | 值范围和实例 |
    | —————— | ——————— | ——————————————— |
    | %Y | 年份 | 0000~9999,例如:1900 |
    | %y | 去掉世纪的年份 | 00~99 |
    | %m | 月份 | 01~12,例如:10 |
    | %B | 月份名称 | January~December,例如:April |
    | %b | 月份名称缩写 | Jan~Dec,例如:Apr |
    | %H | 小时(24h制) | 00~23,例如:12 |
    | %h | 小时(12h制) | 01~12,例如:7 |
    | %M | 分钟 | 00~59,例如:26 |
    | %S | 秒 | 00~59,例如:26 |

  • 其他格式

    | 格式化字符串 | 日期/时间说明 | 值范围和实例 |
    | —————— | —————————— | ——————————————— |
    | %w | 星期中的第几天 | 0~6,0对应星期天 |
    | %d | 日期,一月中的第几天 | 01~31,例如:25 |
    | %j | 一年中的第几天 | 001~366 |
    | %A | 星期 | Monday~Sunday,例如:Wednesday |
    | %a | 星期缩写 | Mon~Sun,例如:Wed |
    | %p | 上/下午 | AM, PM,例如:PM |
    | %Z | 时区的名字 | |

将时间以合理的方式展示出来

  • 格式化:类似字符串格式化,需要有展示模板
  • 展示模板由特定的格式化控制符组成
  • strftime()方法

程序计时: 测量起止动作所经历时间的过程

  • 测量时间:perf_counter()

  • 产生时间:sleep()

1
2
3
4
5
6
7
8
9
10
11
# 返回一个CPU级别的精确时间计数值,单位为秒
# 由于这个计数值起点不确定,连续调用差值才有意义
>>>start = time.perf_counter()
318.66599499718114
>>>end = time.perf_counter()
341.3905185375658
>>>end - start
22.724523540384666

# s拟休眠的时间,单位是秒,可以是浮点数
time.sleep(5)

datetime 模块

python 处理日期和时间的标准库,常用 datetime 类,及 date 类 和 time 类,可做计算之类的操作

time模块比较底层,能完成的功能相对有限,这个时候就需要更高级的datetime模块来参与了

可以简单理解为,datetime模块是对time模块进行了更高一层的封装

基本定义和属性方法

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
import datetime
import sys
from typing import Tuple
from dateutil import relativedelta
from dateutil.parser import parse


def fn_basic() -> Tuple:
""" 基本定义和属性方法 """
# 获取当前日期时间
d_now = datetime.datetime.now() # 等价于datetime.datetime.today()
d_utc_now = datetime.datetime.utcnow() # 获取utc对象, 北京时间+8h=d_utc_now
# 至少要给定年月日
d_free = datetime.datetime(year=2022, month=11, day=11)
print("d_now ", d_now)
print(f"d_now的详细信息: year:{d_now.year} month:{d_now.month} day:{d_now.day}")
print(f"d_now的详细信息: hour:{d_now.hour} minute:{d_now.minute} second:{d_now.second} microsecond:{d_now.microsecond}")
print("d_free", d_free)
# 常用属性
d = d_free.date() # 获取日期对象
t = d_free.time() # 获取时间对象
dt = datetime.datetime.combine(date=d, time=t) # 根据date和time, 创建一个datetime对象
ts = d_free.timestamp() # 获取对象的时间戳
print("ts ", ts)
print(sys._getframe().f_code.co_name, "=" * 60, '\n')
return d_now, d_free

# 输出
d_now 2022-11-15 16:59:45.447093
d_now的详细信息: year:2022 month:11 day:15
d_now的详细信息: hour:16 minute:59 second:45 microsecond:447093
d_free 2022-11-11 00:00:00
ts 1668096000.0
fn_basic ============================================================

日期时间差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fn_dif(d_now, d_free):
""" 日期时间差异 """
# type(dif) == timedelta
dif = d_now - d_free
print("d_free+dif==d_now is", d_free + dif == d_now)

# 下个月 前一天 往后9小时 再往前10分钟
d_dif = d_free + relativedelta.relativedelta(months=1, days=-1, hours=9, minutes=-10)
print("d_dif ", d_dif)
# 指定时间,只能是整数
d_dif_abs = d_free + relativedelta.relativedelta(month=1, day=5, hour=9, minute=10)
print("d_dif_abs ", d_dif_abs)
print(sys._getframe().f_code.co_name, "=" * 60, '\n')

# 输出
d_free+dif==d_now is True
d_dif 2022-12-10 08:50:00
d_dif_abs 2022-01-05 09:10:00
fn_dif ============================================================

日期时间的格式化

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
def fn_format_transform():
""" 日期时间的格式化 """
date_format_str = "%Y-%m-%d %H:%M:%S"
date_format_str_2 = "{:%Y-%m-%d %H:%M:%S}"
# 获取当前日期时间
d_now = datetime.datetime.now()

# 对象转字符串
date_string = d_now.strftime(date_format_str)
print("date_string:", date_string)
date_string_2 = date_format_str_2.format(d_now)
print("date_string_2:", date_string_2)

# 字符串转对象
d_from_str = datetime.datetime.strptime(date_string, date_format_str)
print("d_from_str:", d_from_str)
# dateutil.parse 可以解析几乎所有人类能够理解的日期表示形式
d_parse = parse(date_string)
print("parse:", d_parse)
print("parse('Jan 31, 2021 10:45 PM'): ", parse('Jan 31, 2022 10:45 PM'))
print("parse(str(d_now)): ", parse(str(d_now)))

print(sys._getframe().f_code.co_name, "=" * 60, '\n')

# 输出
date_string: 2022-11-15 16:59:45
date_string_2: 2022-11-15 16:59:45
d_from_str: 2022-11-15 16:59:45
parse: 2022-11-15 16:59:45
parse('Jan 31, 2021 10:45 PM'): 2022-01-31 22:45:00
parse(str(d_now)): 2022-11-15 16:59:45.447093
fn_format ============================================================

产生指定范围内的日期时间

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
def gen_date_times():
""" 产生指定范围内的日期时间 """

def date_time(p_start_time, p_end_time, p_format: str = '%Y-%m-%d'):
while p_start_time < p_end_time:
select_time = p_start_time.strftime(p_format)
print(select_time)
p_start_time += datetime.timedelta(days=1)

# 获取当天时间
format_ = '%Y-%m-%d'
end_time = datetime.datetime.now()
start_time = end_time + relativedelta.relativedelta(days=-10)
date_time(p_start_time=start_time, p_end_time=end_time, p_format=format_)

# 输出
2022-11-05
2022-11-06
2022-11-07
2022-11-08
2022-11-09
2022-11-10
2022-11-11
2022-11-12
2022-11-13
2022-11-14
2022-11-15

calendar库的使用

提供与日历相关功能,如:为给定的月份或年份,打印文本日历

1
2
3
4
5
6
7
8
9
10
11
12
13
import calendar
c = calendar.month(2019, 9)
print(c)

September 2019
Mo Tu We Th Fr Sa Su
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

random库的使用

random库是使用随机数的Python标准库

  • 伪随机数: 采用梅森旋转算法生成的(伪)随机序列中元素

img

  • random库主要用于生成随机数

  • 使用random库: import random

主要方法

  • 基本随机数函数: seed(), random()

  • 扩展随机数函数: randint(), getrandbits(), uniform(), randrange(), choice(), shuffle()

基本随机数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 初始化给定的随机数种子,默认为当前系统时间
>>>random.seed(10) #产生种子10对应的序列
# 生成一个[0.0, 1.0)之间的随机小数
>>>random.random()
0.5714025946899135

# 返回捕获当前生成器内部状态的对象.该对象可以用于函数setstate()取保存当前的状态.
>>> state = random.getstate()
>>> random.setstate(state)

# 以相同顺序打乱多个数组
# 等价于 np.random.get_state() np.random.shuffle(a) np.random.set_state(state)
a = np.arange(10)
b=['A','B','C','D','E','F','G','H','I','J']
state=random.getstate()
random.shuffle(a)
print(a)
random.setstate(state)
random.shuffle(b)
print(b)

[9 4 5 0 1 2 6 8 7 3]
['J', 'E', 'F', 'A', 'B', 'C', 'G', 'I', 'H', 'D']

扩展随机数函数

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
# 生成一个[a, b]之间的整数
>>>random.randint(10, 100)
64

# 生成一个[m, n)之间以k为步长的随机整数
>>>random.randrange(10, 100, 10)
80

# 生成一个k比特长的随机整数
>>>random.getrandbits(16)
37885

# 生成一个[a, b]之间的随机小数
>>>random.uniform(10, 100)
13.096321648808136

# 从序列seq中随机选择一个元素
>>>random.choice([1,2,3,4,5,6,7,8,9])
8

# 将序列seq中元素随机排列,返回打乱后的序列
>>> s=[1,2,3,4,5,6,7,8,9];
>>> random.shuffle(s);
>>> print(s)
[3, 5, 8, 9, 6, 1, 2, 7, 4]
函数 描述
randint(a, b) 生成一个[a, b]之间的整数
randrange(m, n[, k]) 生成一个[m, n)之间以k为步长的随机整数
getrandbits(k) 生成一个k比特长的随机整数
uniform(a, b) 生成一个[a, b]之间的随机小数
choice(seq) 从序列seq中随机选择一个元素
shuffle(seq) 将序列seq中元素随机排列,返回打乱后的序列

命令行脚本传参

命令行运行Python脚本时传入参数的三种方式

如果在运行python脚本时需要传入一些参数,例如gpusbatch_size,可以使用如下三种方式。

1
2
3
python script.py 0,1,2 10
python script.py --gpus=0,1,2 --batch-size=10
python script.py --gpus=0,1,2 --batch_size=10

这三种格式对应不同的参数解析方式,分别为sys.argv, argparse, tf.app.run

前两者是python自带的功能,后者是tensorflow提供的便捷方式。

sys.argv

sys模块是很常用的模块, 它封装了与python解释器相关的数据,例如sys.modules里面有已经加载了的所有模块信息,sys.path里面是PYTHONPATH的内容,而sys.argv则封装了传入的参数数据

使用sys.argv接收上面第一个命令中包含的参数方式如下:

1
2
3
4
5
6
import sys
gpus = sys.argv[1]
# gpus = [int(gpus.split(','))]
batch_size = sys.argv[2]
print gpus
print batch_size

argparse

官方文档

使用argparse从命令行接收bool类型的参数

Python argparse库用法总结

速览

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

# 创建一个解析对象
parser = argparse.ArgumentParser(prog='我的解析对象')

# 添加你要关注的命令行参数和选项
parser.add_argument('--doc_path', '-dp')
parser.add_argument('-tip', '--tp')

# 解析参数
opt = parser.parse_args()

# 带--的参数可以直接用名字访问
print(opt.tp, opt.doc_path)

可以用python arg.py -h获取帮助

ArgumentParser

1
class argparse.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=argparse.HelpFormatter, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True, exit_on_error=True)

创建一个新的ArgumentParser对象,所有的参数都应当作为关键字参数传入,每个参数在下面都有它更详细的描述

  • prog - 程序的名称 (默认值: os.path.basename(sys.argv[0]))
  • usage - 描述程序用途的字符串(默认值:从添加到解析器的参数生成)
  • description - 在参数帮助文档之后显示的文本 (默认值:无)
  • epilog - Text to display after the argument help (by default, no text)
  • parents - 一个 ArgumentParser 对象的列表,它们的参数也应包含在内
  • formatter_class - 用于自定义帮助文档输出格式的类
    • argparse.RawDescriptionHelpFormatter:表示 descriptionepilog 已经被正确的格式化
    • argparse.RawTextHelpFormatter:保留所有种类文字的空格,包括参数的描述
    • argparse.ArgumentDefaultsHelpFormatter:自动添加默认的值的信息到每一个帮助信息的参数中:
    • argparse.MetavarTypeHelpFormatter:每一个参数中使用 type 的参数名当作它的显示名
  • prefix_chars - 可选参数的前缀字符集合(默认值: ‘-‘),许多命令行会使用 - 当作前缀,比如 -f/--foo。如果解析器需要支持不同的或者额外的字符,比如像 +f 或者 /foo 的选项,可以在参数解析构建器中使用 prefix_chars= 参数
  • fromfile_prefix_chars - 当需要从文件中读取其他参数时,用于标识文件名的前缀字符集合(默认值: None
  • argument_default - 参数的全局默认值(默认值: None
  • conflict_handler - 解决冲突选项的策略(通常是不必要的)
  • add_help - 为解析器添加一个 -h/--help 选项(默认值: True),有时候可能会需要关闭额外的帮助信息
  • allow_abbrev - 如果缩写是无歧义的,则允许缩写长选项 (默认值:True
  • exit_on_error - 正常情况下,当你向 ArgumentParserparse_args() 方法传入一个无效的参数列表时,它将会退出并发出错误信息。如果用户想要手动捕获错误,可通过将 exit_on_error 设为 False 来启用该特性
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
import argparse


# 实例化
parser = argparse.ArgumentParser(prog="hyc_prog", usage='%(prog)s [options]',
description='[%(prog)s] 参数解析描述,用于演示',
epilog="description 参数后显示额外的对程序的描述",
# 表示 description 和 epilog 已经被正确的格式化了
formatter_class=argparse.RawDescriptionHelpFormatter,
prefix_chars='-+', # 解析器需要支持不同的或者额外的字符
argument_default=argparse.SUPPRESS, # 全局禁止在 parse_args() 中创建属性
)

# fromfile_prefix_chars参数
with open('args.txt', 'w', encoding=sys.getfilesystemencoding()) as fp:
fp.write('-f\nbar')

parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('-f')
parser.parse_args(['-f', 'foo', '@args.txt'])


# conflict_handler参数
parser = argparse.ArgumentParser(prog='PROG', conflict_handler='resolve')
parser.add_argument('-f', '--foo', help='old foo help')
parser.add_argument('--foo', help='new foo help')
parser.print_help()

>>> usage: PROG [-h] [-f FOO] [--foo FOO]
options:
-h, --help show this help message and exit
-f FOO old foo help
--foo FOO new foo help


# exit_on_error参数
parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument('--integers', type=int)

try:
parser.parse_args('--integers a'.split())
except argparse.ArgumentError:
print('Catching an argumentError')

add_argument

1
ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

定义单个的命令行参数应当如何解析,每个形参都在下面有它自己更多的描述,长话短说有:

  • name or flags - 一个命名或者一个选项字符串的列表,例如 foo 或 -f, —foo
  • action - 当参数在命令行中出现时使用的动作基本类型
  • nargs - 命令行参数应当消耗的数目
  • const - 被一些 action 和 nargs 选择所需求的常数
  • default - 当参数未在命令行中出现并且也不存在于命名空间对象时所产生的值
  • type - 命令行参数应当被转换成的类型
  • choices - A sequence of the allowable values for the argument.
  • required - 此命令行选项是否可省略(仅选项可用),只能用于可选参数(optional arguments)
  • help - 一个此选项作用的简单描述
  • metavar - 在使用方法消息中使用的参数值示例
  • dest - 被添加到parse_args()所返回对象上的属性名

name or flags

name就是指命令行参数中没有’-‘的参数名字例如’myname’,而flags就是指前面有’-‘的参数名,例如’-a’、’—age’

其中name对应位置参数,而flags对应可选参数name在命令行必须输入,并按照顺序喂给程序

命令行传入参数时,对于位置参数我们直接给出其值,对于可选参数需要给出其flags。argparse会先将可选参数进行解析,对于剩余未解析的参数,传给位置参数

1
2
3
4
5
6
7
8
9
10
11
import argparse

parser = argparse.ArgumentParser(description='a test')
parser.add_argument('--age','-a')
parser.add_argument('myname')
parser.add_argument('sex')

# 注意前两个参数必须是 myname和sex
args = parser.parse_args('tom boy --age 22'.split())

print(args) # Namespace(age='22', sex='boy', myname='tom')

命令行传入参数时,先写位置参数,再写可选参数

action: 指定了参数是如何被处理的,支持额操作如下

  • store:这个只是简单的存储这个参数值,这也是默认操作
  • store_const: 存储const指定的值,配合const参数
  • store_false和store_true: 分别对应存储False和True值,它们是store_const的特例
  • append:保存为列表格式,将每个参数的值添加到这个列表
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 argparse


# action='store_const' 传参时只要传入参数名,会默认附上const, 如果不传该参数名, 默认为None
parser=argparse.ArgumentParser()
parser.add_argument('--foo',action='store_const',const=42)
parser.parse_args('--foo'.split())
Out[0]: Namespace(foo=42)
parser.parse_args(''.split())
Out[1]: Namespace(foo=None)

# store_false和store_true
# 以store_false为例, 当传入参数名时, 值为False, 不传参数名时值为True
parser.add_argument('--bar', action='store_false')
parser.parse_args(''.split())
Out[2]: Namespace(bar=True, foo=None)
parser.parse_args('--bar'.split())
Out[3]: Namespace(bar=False, foo=None)

# action='append'
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='append')
parser.parse_args('--foo 1 --foo 2'.split())
Out[5]: Namespace(foo=['1', '2'])

nargs: nargs的意思就是输入参数的个数

  • N—某正整数: 指定好后,不能输入多了,也不能输入少了
  • ‘?’: 这时代表parser会读取0个或1个参数,具体遵循下面的原则
    • 如果给出了1个参数,照常读取这个参数保存起来
    • 如果只给出了flags,比如‘—age’后面未给出具体值,则保存const参数的值(如果const未给出则为None)
    • 如果什么都没给,则保存default参数的值(如果default未给出则为None)
  • ‘*‘: 不确定具体个数,那么可以用nargs=*
  • ‘+’: 要求参数的个数必须大于
    • 如果不给flags,parser会用default的值;如果只给flags,不给值,此时会报错
    • 而当nargs=’*’时,就算只给flags不给值,也不会报错,会得到一个空列表参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import argparse

# nargs=?
parser = argparse.ArgumentParser(description='a test')
parser.add_argument('name')
parser.add_argument('--age','-a', nargs='?',const=16,default=18)

args = parser.parse_args('tom -a 15'.split())
print(args) # Namespace(age='15', name='tom')

args = parser.parse_args('tom -a'.split())
print(args) # Namespace(age=16, name='tom')

args = parser.parse_args('tom'.split())
print(args) # Namespace(age=18, name='tom')

const: 多是配合其它参数出演的配角

  • action参数为 ‘store_const’ 时或是 ‘append_const’ 时
  • nargs参数为 ‘?’ 时

default

当命令行完全没有提到某个参数时,default参数就会发挥作用,default的默认值为None

type

这个类型参数可以约束输入参数的类型,当类型转换合法时,会自动帮我们进行类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import argparse

def str2bool(v):
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Unsupported value encountered.')

# type=int
parser = argparse.ArgumentParser(description='a test')
parser.add_argument('name')
parser.add_argument('--age','-a',type=int, default='17')
args = parser.parse_args('tom'.split())
print(args) # Namespace(age=17, name='tom')

# 自定义类型
parser.add_argument('--is_del_aft', type=str2bool, default=False)

choices

这个选项参数可以使用列表约束输入参数的取值范围。如果输入参数不在候选参数列表中,程序会报错

1
2
3
4
5
6
7
8
9
10
11
import argparse

parser = argparse.ArgumentParser(description='a test')
parser.add_argument('name',choices=['tom','Jim','Bob'])
parser.add_argument('--age','-a',type=int, default='17')

args = parser.parse_args('Jim'.split())
print(args) # Namespace(age=17, name='Jim')

args = parser.parse_args('Toy'.split())
print(args) # test.py: error: argument name: invalid choice: 'Toy' (choose from 'tom', 'Jim', 'Bob')

在参数获取阶段,约束好用户输入的参数范围,可以防止意想不到的参数带来的未知后果

metavar: 这个参数的功能也是个性化显示帮助信息

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
import argparse

parser = argparse.ArgumentParser(description='a test')
parser.add_argument('name',metavar='haha')

# 加metavar
parser.add_argument('--age','-a',type=int, default='17',metavar='hahahaha')
parser.parse_args('-h'.split())
>>> usage: pydevconsole.py [-h] [--age hahahaha] haha
a test
positional arguments:
haha
optional arguments:
-h, --help show this help message and exit
--age hahahaha, -a hahahaha


# 不加metavar
parser.add_argument('--age','-a',type=int, default='17')
>>> usage: pydevconsole.py [-h] [--age AGE] haha
a test
positional arguments:
haha
optional arguments:
-h, --help show this help message and exit
--age AGE, -a AGE

dest

每个参数待parser处理完毕后,都会以‘属性名-属性’的形式保存起来

  • 对于位置参数,属性名就是位置参数的name

  • 对于可选参数,属性名是可选参数去掉‘—’后的那部分,如果没有‘—’,则为去掉‘-’的那部分

为了使得属性名合法,parser还会将单词中间的短横杠变为下划线

但如果,你不想用上述方法自动生成的属性名,你想自己指定属性名,就可以设定dest参数来指定。(只能指定可选参数的属性名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import argparse
# 创建 ArgumentParser 对象
parser = argparse.ArgumentParser(description='An example of using argparse')
# 添加位置参数和可选参数
parser.add_argument('--input', dest='input_file', help='Input file path')
parser.add_argument('--output', dest='output_file', help='Output file path')
# 解析命令行参数
args = parser.parse_args('--input 输入 --output 输出'.split())
# 访问解析后的参数值
input_path = args.input_file
output_path = args.output_file
# 执行相应操作
print(f'Input file path: {input_path}')
print(f'Output file path: {output_path}')

>>>
Input file path: 输入
Output file path: 输出

可以看到,如果不指定dest,年龄的属性名是age。若此时指定了dest=’myage’,那么年龄的属性名就人为设定成了myage

实际例子

脚本运行命令python script.py -gpus=0,1,2 --batch-size=10中的--batch-size会被自动解析成batch_size

parser.add_argument 方法的type参数理论上可以是任何合法的类型,但有些参数传入格式比较麻烦

  • 例如list,所以一般使用bool, int, str, float这些基本类型就行了,更复杂的需求可以通过str传入,然后手动解析

  • bool类型的解析比较特殊,传入任何值都会被解析成True,传入空值时才为`False

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import argparse


def str2bool(v):
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Unsupported value encountered.')


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='manual to this script')
parser.add_argument('--zook_host', type=str, default='127.0.0.1:2181')
parser.add_argument('--num_of_task', type=int, default=10)
parser.add_argument('--is_del_aft', type=str2bool, default=False)
args = parser.parse_args()
many_tasks_schedule_performance(zook_host=args.zook_host,
num_of_task=args.num_of_task,
is_del_aft=args.is_del_aft)

tf.app.run

tensorflow也提供了一种方便的解析方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import tensorflow as tf

tf.app.flags.DEFINE_string('gpus', None, 'gpus to use')
tf.app.flags.DEFINE_integer('batch_size', 5, 'batch size')

FLAGS = tf.app.flags.FLAGS


def main(_):
print
FLAGS.gpus
print
FLAGS.batch_size


if __name__ == "__main__":
tf.app.run()


>>> python script.py --gpus=0,1,2 --batch_size=10

有几点需要注意

  1. tensorflow只提供以下四种方法:
    tf.app.flags.DEFINE_string, tf.app.flags.DEFINE_integer,

    tf.app.flags.DEFINE_boolean, tf.app.flags.DEFINE_float

    分别对应str, int,bool,float类型的参数

    这里对bool的解析比较严格,传入1会被解析成True,其余任何值都会被解析成False

  2. 脚本中需要定义一个接收一个参数的main方法:def main(_):,这个传入的参数是脚本名,一般用不到, 所以用下划线接收。

  3. batch_size参数为例,传入这个参数时使用的名称为--batch_size,也就是说,中划线不会像在argparse 中一样被解析成下划线。

  4. tf.app.run()会寻找并执行入口脚本的main方法。也只有在执行了tf.app.run()之后才能从FLAGS中取出参数。
    从它的签名来看,它也是可以自己指定需要执行的方法的,不一定非得叫main

    1
    run(main=None, argv=None)
  5. tf.app.flags只是对argpars的简单封装。

  6. 代码见https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/python/platform/flags.py

位运算

背景知识

🍅二进制

在Python中可以通过以”0b”或者”-0b”开头的字符串来表示二进制

1
2
3
# 转二进制
print(bin(5))
>>> 0b101

🥬原码、反码、补码

  • 原码: 将一个整数转换成二进制形式,就是其原码。例如6的原码就是0110;-18 的原码就是1000 0000 0001 0010

    通俗的理解,原码就是一个整数本来的二进制形式

  • 反码: 对于正数,它的反码就是其原码(原码和反码相同);负数的反码是将原码中除符号位以外的所有位(数值位)取反

  • 补码: 对于正数,它的补码就是其原码(原码、反码、补码都相同);负数的补码是其反码加1

位运算

  • : & 按位与

  • : | 按位或

  • :~ 按位取反

  • 异或: ^ 按位异或

  • 按位左移: << 按位左移,各二进位全部左移n位,高位丢弃,低位补0

    左移1位相当于 乘以2

  • 按位右移: >> 按位右移,所有二进制位向右移动n位,移出的位删掉,进的位补符号位,右移不会改变一个数的符号

    右移1位相当于 除以2

应用场景

  1. 判断奇数还是偶数

    使用&运算,与1进行&,如果为1,那么该数为奇数;如果为0,那么该数是偶数

  2. 交换两个数值

    第一行,a = a ^ b,很容易理解

    第二行, b = b ^ a = b ^ a ^ b,由于 b ^ b = 0,所以 b = a ^ 0,即 b = a

    第三行, a = a ^ b ,由于a在第一步重新赋值,所以,a = a ^ b ^ a = b,完成了数值交换

    1
    2
    3
    a ^= b
    b ^= a
    a ^= b
  3. 寻找数据列表中的独一无二

    有一个数据列表(2N+1个整数),只有一个数出现了1次,其余N个数都出现了2次。如何找到这个独一无二的数据

    1
    2
    3
    4
    5
    from functools import reduce
    lst = [1,5,6,4,2,6,4,2,1]
    reduce(lambda a,b : a^b, lst)

    >>> 输出5
  4. 计算一个数值的二进制数中有多少个1

    1
    2
    3
    4
    5
    6
    def count_ones(x):
    count = 0
    while x:
    count = count + 1
    x = x & (x-1) # 等价于 x = x & (x-1)
    return count
  5. 在一堆数字中找出只出现一次的两个数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 查找构成res的两个数
    def split_res(res, lst):
    tmp = 1
    num_0, num_1 = 0, 0
    while not tmp & res:
    tmp <<= 1
    for i in lst:
    if i & tmp:
    num_0 ^= i
    else:
    num_1 ^= i
    return num_0, num_1

    from functools import reduce
    lst = [1,5,6,4,2,6,4,2,1,7]
    res = reduce(lambda a,b : a^b, lst)
    num_0, num_1 = split_res(res, lst)

路径

Python路径操作模块pathlib,看这篇就够了!

pathlib和os常用操作对比

通过常用路径操作的对比,可以更深刻理解pathlib和os的区别,便于在实际操作中做对照,也便于进行使用替代,详细对比如下:

pathlib操作 os及os.path操作 功能描述
Path.resolve() os.path.abspath() 获得绝对路径
Path.chmod() os.chmod() 修改文件权限和时间戳
Path.mkdir() os.mkdir() 创建目录
Path.rename() os.rename() 文件或文件夹重命名,如果路径不同,会移动并重新命名
Path.replace() os.replace() 文件或文件夹重命名,如果路径不同,会移动并重新命名,如果存在,则破坏现有目标。
Path.rmdir() os.rmdir() 删除目录
Path.unlink() os.remove() 删除一个文件
Path.unlink() os.unlink() 删除一个文件
Path.cwd() os.getcwd() 获得当前工作目录
Path.exists() os.path.exists() 判断是否存在文件或目录name
Path.home() os.path.expanduser() 返回电脑的用户目录
Path.is_dir() os.path.isdir() 检验给出的路径是一个文件
Path.is_file() os.path.isfile() 检验给出的路径是一个目录
Path.is_symlink() os.path.islink() 检验给出的路径是一个符号链接
Path.stat() os.stat() 获得文件属性
PurePath.is_absolute() os.path.isabs() 判断是否为绝对路径
PurePath.joinpath() os.path.join() 连接目录与文件名或目录
PurePath.name os.path.basename() 返回文件名
PurePath.parent os.path.dirname() 返回文件路径
Path.samefile() os.path.samefile() 判断两个路径是否相同
PurePath.suffix os.path.splitext() 分离文件名和扩展名

配置文件解读

python配置文件INI/TOML/YAML/ENV的区别

ini

ini文件可能是我们可以使用的最直接的配置文件。ini文件非常适合较小的项目,主要是因为这些文件仅支持1级深的层次结构,ini文件本质上是平面文件,但变量可以属于组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[APP]
ENVIRONMENT = development
DEBUG = False

[DATABASE]
USERNAME: root
PASSWORD: p@ssw0rd
HOST: 127.0.0.1
PORT: 5432
DB: my_database

[LOGS]
ERRORS: logs/errors.log
INFO: data/info.log

[FILES]
STATIC_FOLDER: static
TEMPLATES_FOLDER: templates

python解析ini文件代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import configparser

config = configparser.ConfigParser()
path = r'Q:\pyCharmWS\object_detection\test.ini'
cfg = config.read(path)

# 多种方式获取值
config['DATABASE']['HOST']
Out[6]: '127.0.0.1'
config.get('DATABASE', 'HOST')
Out[7]: '127.0.0.1'

# 获取指定类型
config.getboolean('APP', 'DEBUG')
Out[8]: False
config.get('APP', 'DEBUG')
Out[9]: 'False'

configparser还有许多其他类型检查方法,例如getint()getfloat()等等

toml

TOML文件似乎与ini文件共享某些语法相似之处,但支持更广泛的数据类型以及值本身之间的关系

如表中所示,TOML支持嵌套表的概念,该[environments]表后面带有多个子表,通过使用点符号,我们能够创建表的关联,这意味着它们是同一元素的不同实例

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
# Keys
title = "My TOML Config"
# Tables
[project]
name = "Faceback"
description = "Powerful AI which renders the back of somebody's head, based on their face."
version = "1.0.0"
updated = 1979-05-27T07:32:00Z
author = "Todd Birchard"
[database]
host = "127.0.0.1"
password = "p@ssw0rd"
port = 5432
name = "my_database"
connection_max = 5000
enabled = true

# Nested `tables`
[environments]
[environments.dev]
ip = "10.0.0.1"
dc = "eqdc10"
[environments.staging]
ip = "10.0.0.2"
dc = "eqdc10"
[environments.production]
ip = "10.0.0.3"
dc = "eqdc10"

# Array of Tables
[[testers]]
id = 1
username = "JohnCena"
password = "YouCantSeeMe69"
[[testers]]
id = 3
username = "TheRock"
password = "CantCook123"

同样有趣的是概念表列,如下表中的[[testers]],双括号中的表会自动添加到数组中,其中数组中的每个项目都是具有相同名称的表,等价于下下面JSON所表达的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"testers": [{
"id": 1,
"username": "JohnCena",
"password": "YouCantSeeMe69"
},
{
"id": 2,
"username": "TheRock",
"password": "CantCook123"
}
]
}

python解析toml的代码如下

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
import toml

path = r'Q:\pyCharmWS\object_detection\test.toml'
cfg = toml.load([path])

# Retrieving a dictionary
cfg['project']
cfg.get('project')

# Retrieving a value
cfg['project']['author']
cfg.get('project').get('author')

print(cfg)
{
'title': 'My TOML Config',
'project': {
'name': 'Faceback',
'description': "Powerful AI which renders the back of somebody's head, based on their face.",
'version': '1.0.0',
'updated': datetime.datetime(1979, 5, 27, 7, 32, tzinfo = < toml.tz.TomlTz object at 0x000001AE244933D0 > ),
'author': 'Todd Birchard'
},
'database': {
'host': '127.0.0.1',
'password': 'p@ssw0rd',
'port': 5432,
'name': 'my_database',
'connection_max': 5000,
'enabled': True
},
'environments': {
'dev': {
'ip': '10.0.0.1',
'dc': 'eqdc10'
},
'staging': {
'ip': '10.0.0.2',
'dc': 'eqdc10'
},
'production': {
'ip': '10.0.0.3',
'dc': 'eqdc10'
}
},
'testers': [{
'id': 1,
'username': 'JohnCena',
'password': 'YouCantSeeMe69'
}, {
'id': 3,
'username': 'TheRock',
'password': 'CantCook123'
}]
}

yaml

系统变量

1
2
3
4
from os import environ

environ.get('ComSpec')
Out[0]: 'C:\\WINDOWS\\system32\\cmd.exe'