2023年有一部分时间在与win32com
、doc/docx
打交道,主要完成文章改写、文章生成方面的项目。其中,对提纲序号的生成的需求一直存在,经过一段时间的积累和完善,形成了一套自用的提纲序号生成工具,用到现在感觉也还可以,本次复盘就再盘一盘它。
序号生成的特点
序号具有多样性,如一、
,1.1
,(一)
、1、
、1.
等,还具有连续性和等级性。不用多说,接触文文章的人都能理解。这些纷杂的特点堆在一起导致刚开始两眼一抹黑,干脆只用使用穷举解决:直接把序号枚举到50
级,因为目前所接触到的文章提纲序号顶天了也才没见过超过30
的,经过实践,最终使用生成器
解决了各种情形下提纲序号生成的问题。
形成的解决方案(生成器)
中文序号
能生成一、二、三、至九十九
,或带括号的,如(一)
CHINESE_MAP = {1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六',
7: '七', 8: '八', 9: '九', 10: '十'}
CHINESE_BTS_MAP = {1: '(一)', 2: '(二)', 3: '(三)', 4: '(四)', 5: '(五)', 6: '(六)',
7: '(七)', 8: '(八)', 9: '(九)', 10: '(十)'}
# 中文标题序号
def chinese_numerals_generator(mark=True):
start=1
while True:
if start <= 10:
base = CHINESE_MAP[start]
yield base if not mark else f'{base}、'
start += 1
else:
# 倍数, 余数
multiple, remainder = divmod(start, 10)
# 10的倍数
if remainder == 0:
base = f'{CHINESE_MAP[multiple]}十'
yield base if not mark else f'{base}、'
# 10的倍数 + 余数
else:
base = f'{CHINESE_MAP[multiple]}十{CHINESE_MAP[remainder]}'
yield base if not mark else f'{base}、'
start += 1
# 中文标题序号带括号
def chinese_numerals_with_bts():
start = 1
while True:
if start <= 10:
yield CHINESE_BTS_MAP[start]
start += 1
else:
# 倍数, 余数
multiple, remainder = divmod(start, 10)
# 10的倍数
if remainder == 0:
yield f'({CHINESE_MAP[multiple]}十)'
# 10的倍数 + 余数
else:
yield f'({CHINESE_MAP[multiple]}十{CHINESE_MAP[remainder]})'
start += 1
数字 + 符号
生成的类型为:1
1.
1、
,使用参数控制符号类型
# 阿拉伯数字序号
def numerals_generator(mark_type=2):
"""
@param mark_type: 1.序号加、 2. 序号加. 3.仅序号
"""
if mark_type > 3:
raise ValueError('mark_type must be 1 to 3')
_map = {
1: '、',
2: '.',
3: None
}
start = 1
type_ = _map.get(mark_type, None)
while True:
yield start if not mark_type else f'{start}{type_}'
start += 1
数字 + 括号
生成的类型为:(1)
(1)
,前者为英文括号,后者为中文括号
阿拉伯数字带括号序号 英文括号
def numerals_with_enbts_generator(start=1):
while True:
yield f"({start})"
start += 1
# 阿拉伯数字带括号序号 中文括号
def numerals_with_cnbts_generator(start=1):
while True:
yield f"({start})"
start += 1
当然,这两者还可以合二为一,不过一直感觉语义明确比节省代码的优先度要高一些。
二级标题类型的数字
生成的类型为:1.1
,参数控制第一位数字
# 多数字序号 二级标题 如 1.1
def multiple_numerals2_generator(start=1):
count=1
while True:
yield f"{start}.{count}"
count += 1
三级标题类型的数字
生成的类型为:1.1.1
,参数控制第一和第二位数字
# 多数字序号 三级标题 如 1.1.1
def multiple_numerals3_generator(start=1, middle=1):
count=1
while True:
yield f"{start}.{middle}.{count}"
count += 1
使用方法
使用非常简单,先调用函数,再调用next
方法即可逐个获取序号
demo = chinese_numerals_generator()
next(demo)
进一步优化
后来对该方法进行了优化,统一了入口,只暴露一个函数outline_seq_maker
:
def outline_seq_maker(
type: Literal=['CHINESE', 'CHINESE_BTS', 'PURE_NUM', 'NUM_CN_BTS', 'NUM_EN_BTS', 'MULTI_NUM2', 'MULTI_NUM3'],
start=1,
middle=1,
mark=True,
mark_type=2.
) -> Generator:
_map = {
'CHINESE': partial(chinese_numerals_generator, mark=mark),
'CHINESE_BTS': chinese_numerals_with_bts,
'PURE_NUM': partial(numerals_generator, mark_type=mark_type),
'NUM_CN_BTS': partial(numerals_with_cnbts_generator, start=start),
'NUM_EN_BTS': partial(numerals_with_enbts_generator, start=start),
'MULTI_NUM2': partial(multiple_numerals2_generator, start=start),
'MULTI_NUM3': partial(multiple_numerals3_generator, start=start, middle=middle),
}
return _map[type]()
不管使用哪种序号生成器,调用outline_seq_maker
即可获得,通过参数type
来映射不同的序号生成器,考虑到它们入参的不同,使用偏函数partial
一下,_map[type]()
就能实现函数的调用,而无需考虑参数不同的情形。使用方法:
g = outline_seq_maker('CHINESE')
next(g)
最终的方案
再后来,又有了新的需求,需要获取某个序号生成器到底i生成到哪里了,所以又增加了类的包装,使用类属性记住序号生成器的状态:
class OutlineSeqMaker:
def __init__(
self,
type: Literal['CHINESE', 'CHINESE_BTS', 'PURE_NUM', 'NUM_CN_BTS', 'NUM_EN_BTS', 'MULTI_NUM2', 'MULTI_NUM3'],
start=1,
middle=1,
mark=True,
mark_type=2,
):
self.type_ = type
self.start = start
self.middle = middle
self.mark = mark
self.mark_type = mark_type
self.generator: Generator = None
self.__current_level = 0
self.init()
def init(self) -> Generator:
_map = {
'CHINESE': partial(chinese_numerals_generator, mark=self.mark),
'CHINESE_BTS': chinese_numerals_with_bts,
'PURE_NUM': partial(numerals_generator, mark_type=self.mark_type),
'NUM_CN_BTS': partial(numerals_with_cnbts_generator, start=self.start),
'NUM_EN_BTS': partial(numerals_with_enbts_generator, start=self.start),
'MULTI_NUM2': partial(multiple_numerals2_generator, start=self.start),
'MULTI_NUM3': partial(multiple_numerals3_generator, start=self.start, middle=self.middle),
}
self.generator = _map[self.type_]()
@property
def current_level(self):
return self.__current_level
def next(self):
self.__current_level += 1
return next(self.generator)
def destroy(self):
del self
在类里,增加了next
方法、current_level
属性方法,使用方法:
# 带括号的中文序号
g = OutlineSeqMaker(type='CHINESE_BTS', mark_type=1)
# 获得一个序号
g.next()
# 获取当前序号的位置
g.current_level
# 不需要时主动销毁该序号生成器
g.destroy()
对于解决方案,个人始终遵循简单易用,好维护,易扩展的宗旨,这套方法也不是很完善,仅能解决目前我所遇到的问题,边积累边优化吧。