python进阶使用

Table of Contents

自大四上学期使用python炼丹开始计算,笔者使用python已经两年了。目前自以为对python的使用十分纯熟,但在阅读他们代码时,仍然发现很多地方是自己从未用过的,因而总结如下,作为针对笔者自己的进阶指南。希望能够通过这种方式对python本身和其标准库的使用更进一层楼。

1. python标准库进阶

1.1. 命令行函数: argparse

可以通过传递参数的名字来告诉文件我在传递哪个参数,模拟C程序的命令行效果。示例如下:

     ## init argparse for better application.
     # you should finally edit this function for better use
     def setup_train_args():

         parser = argparse.ArgumentParser()
         ## this line is for logging, please do not delete it.
         parser.add_argument('--save_log_path', default='./running_results.log',
                             type=str, required=False)

         ### you can change, comment or delete blew sections.
         parser.add_argument("--max_seq_length", default=64,
                             type=int, required=False, help="max sequence length")
         parser.add_argument('--cuda_num', default='6', type=str, required=False)
         parser.add_argument('--fraction', default='0.5',
                             type=float, required=False)
         parser.add_argument('--no_cuda', action='store_true')

         return parser.parse_args()

args=setup_train_args()

基本数据类型的使用,基本的参数都在示例之中了。

顺便:不要忘记import argparse。

1.2. 日志:logging相关

logging的功能比print稍微复杂一些,当然好处就是看起来更不错了,同时流输出到文件中也更加稳定,如果忍不住用print,可以 print\=logger.info alias一下。。。当然我目前还是很喜欢print来debug。

我自己写了一个万能logging配置,如下:

global logger


## config logging formatter.
class CustomFormatter(logging.Formatter):
    grey = "\x1b[0;37m"
    green = "\x1b[1;32m"
    yellow = "\x1b[1;33m"
    red = "\x1b[1;31m"
    purple = "\x1b[1;35m"
    blue = "\x1b[1;34m"
    light_blue = "\x1b[1;36m"
    reset = "\x1b[0m"
    blink_red = "\x1b[5m\x1b[1;31m"

    format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
    FORMATS = {
        logging.DEBUG: purple + format + reset,
        logging.INFO: green + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: blink_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

## generate logger
def _create_logger(args):


    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)

    # formatter = logging.Formatter(
    #     '%(asctime)s - %(levelname)s - %(message)s')
    formatter=CustomFormatter()

    # construct a handle for file logs.
    file_handler = logging.FileHandler(
        filename=args.save_log_path)
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    logger.addHandler(file_handler)

    # construct a handle for console logs.
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG)
    console.setFormatter(formatter)
    logger.addHandler(console)
    return logger

## main function
def main():
    args=setup_train_args()
    logger=_create_logger(args)
    logger.setLevel(logging.DEBUG)


    logger.info("aaaaaaaaaaaaaaaa")
    logger.debug("bbbbbbbbb")
    logger.error("cccccccccccccc")
    logger.critical("ddddd")

以上配置基本就是debug的使用使用的,它会把logging的结果调成不同的颜色,比如info是绿色,error是红色等等,还会提供诸如触发未知的行号等等额外信息。具体效果如下图所示:

screenshot_20211206_155849.png

顺便:不要忘记import logging。

1.3. 规范参数类型:typing

python身为动态语言,对类型的要求不是很高。但是,如果试图撰写清晰的、标准的代码,如写一个库,那么对参数类型的刻画是十分必要的。可以通过标准库typing进行解决。

比如以下示例:

from typing import Any, Dict, List, Optional, Tuple, Union, overload

def prepare_for_tokenization(text: str,
                             is_split_into_words: bool = False,
                             **kwargs) -> Tuple[str, Dict[str, Any]]:
    blablabla...

我们从中可以清晰地理解其输入参数的类型和返回值是什么。下表总结了一些常用的类型。

类型 介绍
Dict Dict[KeyType,ValueType]
List List[ElementType]
Optional Optional[]
Any any type
Tuple Tuple[int,float,str]
Union Union[A,B],二者之一均可
Optional Optional[X]即Union[X,None]

如果我想了解更多,我会去这里进行阅读。

1.4. collections:常见的特殊数据结构

大家已经对dict,list,set,tuple这些东西习以为常了,还有一些稍微高阶的结构,记录如下。

1.4.1. Counter

Counter就是计数器,用以统计频率。其输入是一个迭代器对象(如一个列表),输出是一个字典,该字典的key就是迭代器里的值,该字典的value就是该值出现的频率。

from collections import Counter

a=[1,1,1,2,2,2,3,3,1,1,2,4,5,6,7,7,7,7,7,7,7,7,7]
b=Counter(a)
print(f"a: {a}\nb: {b}\n")
return (f"b: {b}\n")

难道是有序列的吗?!

1.4.2. defaultdict

defualtdict与dict的不同在于,defaultDict对于一个未收录的key,会自动根据初始化defaultDict的函数来初始化这个key的value。如:

from collections import defaultdict

a=defaultdict(list)
a["test"].append("test result")

return a

1.4.3. OrderedDict

OrderedDict与dict的不同在于:它的key-value排列顺序是固定的。比如在我读取一个json文件时,可能就需要约束OrderedDict的形式:

import json
from collections import OrderedDict

filename="blablabla"
with open(filename,"r") as f:
    data=json.load(f, object_pairs_hook=OrderedDict)

如果我想了解更多,我会选择阅读这个链接。

1.5. 我真的理解了正则匹配嘛?re

正则表达式匹配是一个大坑,我曾经在读书馆看到一本厚厚的书,就在讲这一件事。所以此处仅仅是简单描绘一下我所遇到的场景。后续可能会补充。

我理解正则表达式:先写出一个pattern,然后用这个pattern去做过滤。

先来几个pattern尝尝咸淡。

p1=r"(" + r"|".join(escaped_special_toks) + r")|" + r"(.+?)"

这个pattern的意思时这样的,实现,对于一个列表escaped_special_toks,我用“|”把里面的元素拼接在一起(如“a|b|c”),之后,添加上括号和后面的部分。因此,这里面涉及到几种re符号,|,(),.+?。

下面一个表格是简单的介绍。

符号 含义
. 匹配任意一个非换行字符
+ 匹配前一个字符的任意多字符
前一个字符匹配0次也可以被接受
\* 匹配前面的0个,1个,无穷多字符
竖线 或运算,无需多提及
{a,b} 对前述内容最少a次最多b次匹配,ab为数字
[] 只要满足中括号中的一个即可
() 括号用以捕获()所匹配到的内容,以进行进一步的处理

依照以上知识可以知道,原来p1可以实现:1. 要么进行左侧的匹配,要么进行右侧的匹配。2. 左侧的匹配是:查看字符串是否不好escaped_special_toks里的任何一个元素,如果包含,那么返回该元素;2. 右侧的匹配是:如果左侧匹配失败,那么我把所有内容完全返回。

是不是这个样子?

下面介绍几个re的api。

compile 编译正则表达式为re对象
search 返回一个候选
findall 返回多个候选
sub(pattern,input, need_replaced) 对于input中符合pattern的用need_repalced替换
   

这篇博客对re模块进行了更为细致的介绍,且文理较为清晰,可供一读。

2. python特殊用法

2.1. f-string: 格式化字符串

之前一直用 format 函数来格式化字符串,后来发现下面的两种形式是等效的。

result=12.5
print("result: {}".format(result))
print(f"result: {result}")

2.2. \*args与\**kwargs

对于函数而言,我可能不知道我能接受的参数有多长,甚至不知道他们都是啥。这时候就可以用上述两个关键字。对于函数而言,

  1. \*args是一个固定长度但长度未知的参数列表。
  2. \**kwargs是一个固定长度但长度未知的字典,字典的key就是参数名,value是参数的值。

    如下面的例子

    def fourPrint(first,*args):
        print(f"first: {first}")
        for i in args:
            print(f"and: {i}")
        return args[0] 
    
    return fourPrint(0,1,2,3)
    
    
def fourPrint(first,**kwargs):
    print(f"first: {first}")
    for key in kwargs:
        print(f"and: {kwargs[key]}")
    return kwargs[key],key 

return fourPrint(first=0,second=1,third=2,fourth=3)

还有一种情况,也可以使用这个trick。如下面的例子:

mytupe=(1,2,3,4)
mydict={"first":1,"second":2,"third":3,"fourth":4}

def fourPrint(first,second,third,fourth):
    print(f"first: {first}")
    return fourth 

return (fourPrint(*mytupe),fourPrint(**mydict))

后续待整理……


Author: Zi Liang (liangzid@stu.xjtu.edu.cn) Create Date: Mon Dec 6 14:55:52 2021 Last modified: 2024-03-09 Sat 20:56 Creator: Emacs 28.1 (Org mode 9.5.2)