Package: pnlp
Table of Contentsgenerated withDocToc功能特性安装使用文本IOIO 处理内置方法文本处理清理和提取内置正则文本切分任意部分切分分句中文字符切分句子分组文本增强Token级别句子级别文本归一化中文数字格式转换BIO转实体任意参数转UUID内置词典停用词文本长度魔术方法并行处理测试更新日志NLP 预/后处理工具。功能特性专为文本 IO 设计的灵活的 Pipeline灵活的文本清理/提取工具文本增强按句切分或按中文字符切分文本文本分桶中文字符归一化文本各种长度计算中英文常用停用词预处理魔术方法并发、批量化、实体 BIO 转实体安装需要 Python3.7+。pip install pnlp使用文本IOIO 处理treetests/piop_data/
├──a.md
├──b.txt
├──c.data
├──first
│├──fa.md
│├──fb.txt
│├──fc.data
│└──second
│├──sa.md
│├──sb.txt
│└──sc.data
├──json.json
├──outfile.file
├──outjson.json
└──yml.ymlimportosfrompnlpimportReaderDATA_PATH="./pnlp/tests/piop_data/"pattern='*.md'# 可以是 '*.txt', 'f*.*' 等，支持正则reader=Reader(pattern,use_regex=True)# 获取所有文件的行，输出行文本、行索引和所在的文件名forlineinreader(DATA_FOLDER_PATH):print(line.lid,line.fname,line.text)"""0 a.md line 1 in a.1 a.md line 2 in a.2 a.md line 3 in a.0 fa.md line 1 in fa.1 fa.md line 2 in fa..."""# 获取某个文件的所有行，输出行文本、行索引和所在文件名，此时由于指定了文件名 pattern 无效forlineinreader(os.path.join(DATA_FOLDER_PATH,"a.md")):print(line.lid,line.fname,line.text)"""0 a.md line 1 in a.1 a.md line 2 in a.2 a.md line 3 in a."""# 获取目录下的所有文件路径forpathinReader.gen_files(DATA_PATH,pattern,use_regex:True):print(path)"""pnlp/tests/piop_data/a.mdpnlp/tests/piop_data/first/fa.mdpnlp/tests/piop_data/first/second/sa.md"""# 获取一个目录下所有文件名和它们的内容paths=Reader.gen_files(DATA_PATH,pattern)articles=Reader.gen_articles(paths)forarticleinarticles:print(article.fname)print(article.f.read())"""a.mdline 1 in a.line 2 in a.line 3 in a...."""# 同前两个例子paths=Reader.gen_files(DATA_PATH,pattern)articles=Reader.gen_articles(paths)forlineinReader.gen_flines(articles,strip="\n"):print(line.lid,line.fname,line.text)内置方法importpnlp# Readfile_string=pnlp.read_file(file_path)file_list=pnlp.read_lines(file_path)file_json=pnlp.read_json(file_path)file_yaml=pnlp.read_yaml(file_path)file_csv=pnlp.read_csv(file_path)file_pickle=pnlp.read_pickle(file_path)list_dict=pnlp.read_file_to_list_dict(file_path)# Writepnlp.write_json(file_path,data,indent=2)pnlp.write_file(file_path,data)pnlp.write_pickle(file_path,data)pnlp.write_list_dict_to_file(file_path,data)# Otherspnlp.check_dir(dirname)# 如果目录不存在会创建文本处理清理和提取importrefrompnlpimportTexttext="这是https://www.yam.gift长度测试，《 》*)FSJfdsjf😁![](http://xx.jpg)。233."pattern=re.compile(r'\d+')# pattern 是 re.Pattern 类型或 str 类型# 默认为空字符串：'', 表示不使用任何 pattern（实际是 re.compile(r'.+')），此时 clean 返回空（全部被清了），extract 返回原始文本。# pattern 支持以下字符串类型（实际为正则）：#	'chi': 中文字符#	'pun': 标点#	'whi': 空白#	'nwh': 非空白#	'wnb': 字母（含中文字符）或数字#	'nwn': 非字母（含中文字符）或数字#	'eng': 英文字符#	'num': 数字#	'pic': 图片#	'lnk': 链接#	'emj': 表情pt=Text(['chi',pattern])# 提取所有符合 pattern 的文本和它们的位置res=pt.extract(text)print(res)"""{'text': '这是长度测试233', 'mats': ['这是', '长度测试', '233'], 'locs': [(0, 2), (22, 26), (60, 63)]}"""# 支持用「点」获取key属性print(res.text,res.mats,res.locs)"""'这是长度测试' ['这是', '长度测试'] [(0, 2), (22, 26)]"""# 返回指定 pattern 清理后的文本print(pt.clean(text))"""https://www.yam.gift，《 》*)FSJfdsjf😁![](http://xx.jpg)。233."""# 可以指定多个 pattern，注意先后顺序可能会影响结果哦pt=Text(['pic','lnk'])# 提取到的res=pt.extract(text)print(res.mats)"""['https://www.yam.gif','![](http://xx.jpg)','https://www.yam.gift','http://xx.jpg']"""# 清理后的print(pt.clean(text))"""这是t长度测试，《 》*)FSJfdsjf😁。233."""内置正则# USE Regexfrompnlpimportregdefclean_text(text:str)->str:text=reg.pwhi.sub("",text)text=reg.pemj.sub("",text)text=reg.ppic.sub("",text)text=reg.plnk.sub("",text)returntext文本切分任意部分切分# Cut by Regexfrompnlpimportcut_part,psenttext="你好！欢迎使用。"sent_list=cut_part(text,psent,with_spliter=True,with_offset=False)print(sent_list)"""['你好！', '欢迎使用。']"""pcustom_sent=re.compile(r'[。！]')sent_list=cut_part(text,pcustom_sent,with_spliter=False,with_offset=False)print(sent_list)"""['你好', '欢迎使用']"""sent_list=cut_part(text,pcustom_sent,with_spliter=False,with_offset=True)print(sent_list)"""[('你好', 0, 3), ('欢迎使用', 3, 8)]"""分句# Cut Sentencefrompnlpimportcut_sentenceaspcstext="你好！欢迎使用。"sent_list=pcs(text)print(sent_list)"""['你好！', '欢迎使用。']"""中文字符切分# 中文字符切分frompnlpimportcut_zhchartext="你好，hello, 520 i love u. = ”我爱你“。"char_list=cut_zhchar(text)print(char_list)"""['你', '好', '，', 'hello', ',', ' ', '520', ' ', 'i', ' ', 'love', ' ', 'u', '.', ' ', '=', ' ', '”', '我', '爱', '你', '“', '。']"""char_list=cut_zhchar(text,remove_blank=True)print(char_list)"""['你', '好', '，', 'hello', ',', '520', 'i', 'love', 'u', '.', '=', '”', '我', '爱', '你', '“', '。']"""句子分组frompnlpimportcombine_bucketparts=["先生，那夜，我因胸中纳闷，无法入睡，","折腾得比那铐了脚镣的叛变水手还更难过；","那时，我就冲动的 ——","好在有那一时之念，","因为有时我们在无意中所做的事能够圆满……"]buckets=combine_bucket(parts.copy(),10,truncate=True,keep_remain=True)print(buckets)"""['先生，那夜，我因胸中','纳闷，无法入睡，','折腾得比那铐了脚镣的','叛变水手还更难过；','那时，我就冲动的 —','—','好在有那一时之念，','因为有时我们在无意中','所做的事能够圆满……']"""文本增强采样器支持删除、交换、插入操作，所有的操作不会跨越标点。Token级别默认 Tokenizer中文：字符级 Tokenizer（见上）英文：空白符切分 TokenizerTokenizer 可以任意指定，但它的输出应该是一个 List 的 Token 或一个 List 的 Tuple，每个 Tuple 包含一个 Token 和一个词性。对字符级增强，默认并不会操作所有字或词。可以自定义要操作的词或词性。默认 Token 是「停用词」默认词性（当 Tokenizer 输出带词性时）是「功能词」：副词、介词、连词、助词、其他虚词（标记为 d p c u xc）# 【】内的为改变的text="人为什么活着？生而为人必须要有梦想！还要有尽可能多的精神体验。"# 字符粒度frompnlpimportTokenLevelSamplertls=TokenLevelSampler()tls.make_samples(text)"""{'delete': '人为什么活着？生而为人必须要【有】梦想！还要有尽可能多的精神体验。','swap': '【为】【人】什么活着？生而为人必须要有梦想！还要有尽可能多的精神体验。','insert': '人为什么活着？生而为人必须要有梦想！【还】还要有尽可能多的精神体验。','together': '人什么着着活？生而必为为须要有梦想！还要有尽可能多的精神体验。'}"""# 支持自定义 tokenizertls.make_samples(text,jieba.lcut)"""{'delete': '人为什么活着？生而为人【必须】要有梦想！还要有尽可能多的精神体验。','swap': '【为什么】【人】活着？生而为人必须要有梦想！还要有尽可能多的精神体验。','insert': '人为什么活着？生而为人必须要有梦想！【还要】还要有尽可能多的精神体验。','together': '人为什么活着？生而为人人要有梦想！还要有多尽可能的精神体验。'}"""# 自定义tls=TokenLevelSampler(rate=替换比例,# 默认 5%types=["delete","swap","insert"],# 默认三个sample_words=["词1","词2"],# 默认停用词sample_pos=["词性1","词性2"],# 默认功能词)句子级别frompnlpimportSentenceLevelSamplersls=SentenceLevelSampler()sls.make_samples(text)"""{'delete': '生而为人必须要有梦想！还要有尽可能多的精神体验。','swap': '人为什么活着？还要有尽可能多的精神体验。生而为人必须要有梦想！','insert': '人为什么活着？还要有尽可能多的精神体验。生而为人必须要有梦想！生而为人必须要有梦想！','together': '生而为人必须要有梦想！人为什么活着？人为什么活着？'}"""# 自定义sls=SentenceLevelSampler(types=["delete","swap","insert"])# 默认三个文本归一化中文数字frompnlpimportnum_normnum_norm.num2zh(1024)=="一千零二十四"num_norm.num2zh(1024).to_money()=="壹仟零贰拾肆"num_norm.zh2num("一千零二十四")==1024格式转换BIO转实体# 实体 BIO Token 转实体frompnlpimportpick_entity_from_bio_labelspairs=[('天','B-LOC'),('安','I-LOC'),('门','I-LOC'),('有','O'),('毛','B-PER'),('主','I-PER'),('席','I-PER')]pick_entity_from_bio_labels(pairs)"""[('天安门', 'LOC'), ('毛主席', 'PER')]"""pick_entity_from_bio_labels(pairs,with_offset=True)"""[('天安门', 'LOC', 0, 3), ('毛主席', 'PER', 4, 7)]"""任意参数转UUIDfrompnlpimportgenerate_uuiduid1=pnlp.generate_uuid("a",1,0.02)uid2=pnlp.generete_uuid("a",1)"""uid1 == 3fbc8b70d05b5abdb5badca1d26e1dbduid2 == f7b0ffc589e453e88d4faf66eb92f669"""内置词典停用词frompnlpimportStopWords,chinese_stopwords,english_stopwordscsw=StopWords("/path/to/custom/stopwords.txt")csw.stopwords# a set of the custom stopwordscsw.zh==chinese_stopwords# Chineses stopwordscsw.en==english_stopwords# English stopwords文本长度frompnlpimportLengthtext="这是https://www.yam.gift长度测试，《 》*)FSJfdsjf😁![](http://xx.jpg)。233."pl=Length(text)# 注意：即使使用了 pattern，长度都是基于原始文本# 长度基于字符计数（不是整词）print("Length of all characters: ",pl.len_all)print("Length of all non-white characters: ",pl.len_nwh)print("Length of all Chinese characters: ",pl.len_chi)print("Length of all words and numbers: ",pl.len_wnb)print("Length of all punctuations: ",pl.len_pun)print("Length of all English characters: ",pl.len_eng)print("Length of all numbers: ",pl.len_num)"""Length of all characters:  64Length of all non-white characters:  63Length of all Chinese characters:  6Length of all words and numbers:  41Length of all punctuations:  14Length of all English characters:  32Length of all numbers:  3"""魔术方法frompnlpimportMagicDict# 嵌套字典pmd=MagicDict()pmd['a']['b']['c']=2print(pmd)"""{'a': {'b': {'c': 2}}}"""# 当字典被翻转时，保留所有的重复 value-keysdx={1:'a',2:'a',3:'a',4:'b'}print(pmag.MagicDict.reverse(dx))"""{'a': [1, 2, 3], 'b': 4}"""并行处理支持四种并行处理方式：线程池：thread_pool进程池：process_pool线程 Executor：thread_executor，默认使用线程：thread注意：惰性处理，返回的是 Generator。importmathdefis_prime(x):ifx<2:returnFalseforiinrange(2,int(math.sqrt(x))+1):ifx%i==0:returnFalsereturnTruefrompnlpimportconcurring# max_workers 默认为 4@concurringdefget_primes(lst):res=[]foriinlst:ifis_prime(i):res.append(i)returnres@concurrint(type="thread_pool",max_workers=10)defget_primes(lst):passconcurring装饰器让你的迭代函数并行。测试Clone 仓库后执行：$python-mpytest更新日志见英文版 README。