Python Cheat Sheet

这篇博客主要记录一下,在实际使用Python处理数据的过程中,会涉及到的一些常用Python代码。

1. 使用json读取/写入文件

主要涉及4个函数

  • json.load()

    • 读取一个json文件,把文件中的内容转化成为python object (load from file)

    • import json
      with open('data.json', 'r') as json_file:
      	python_object = json.load(json_file)
      
  • json.loads()

    • 解析一个字符串,把字符串转化成为python object,可以解析dict, list, int, bool, float, null等

      loads = load string

    • import json
      json_string = '{"name": "John", "age": 30, "city": "New York"}'
      python_dict = json.loads(json_string)
      
  • json.dump()

    • 序列化python object到一个文件

    • data = {"name": "Alice", "age": 30}
      with open('data.json', 'w') as file:
          json.dump(data, file, indent=4)
      
  • json.dumps()

    • 序列化python object到json formatted string

    • import json
      data = {"name": "Alice", "age": 30}
      json_string = json.dumps(data, indent=4)
      print(json_string)
      

使用json.loads()读取一个文件中的多个dict

// data.json
{"name": "Alice", "age": 30}
{"name": "Bob", "age": 31}
{"name": "Cindy", "age": 33}
with open('data.json', 'r') as file:
    for line in file:
        data = json.loads(line)
        print(data)

2. print 保留小数,设置格式

value = 12.34567
width = 10
decimal = 2
print(f"{value:<{width}.{decimal}f}")  # Left-aligns within 10 characters, 2 decimal places
  • 左对齐:{:<}

  • 右对齐:{:>}
  • 居中:{:^}
  • 默认使用空格当占位符, {:0<}表示用0充当占位符

3. 正则表达式

元字符

. ^ $ * + ? { } [ ] \ | ( )

有一些元字符不会使解析引擎在字符串中前进一个字符,他们不占用任何字符,只是表示字符串的位置信息,被称为零宽度断言

元字符 含义 零宽度断言 例子 可匹配字符
. 除\n外的所有字符 - -
[ ] 表示匹配的字符的一个集合 [abc] 匹配 abc 之中的任意一个字符
| “or”运算符,优先级很低,一般和[]一起使用 [Crow | Servo] 匹配字符串Crow/Servo
\ 后面可以跟各种字符来表示各种特殊序列;或者用于转义元字符 -    
  \d 等价于字符类 [0-9] - 匹配任何十进制数字
  \D 等价于字符类 [^0-9] - 匹配任何非数字字符
  \s 等价于字符类 [ \t\n\r\f\v] - 匹配任何空白字符
  \S 等价于字符类 [^ \t\n\r\f\v] - 匹配任何非空白字符
  \w 等价于字符类 [a-zA-Z0-9_] - 匹配任何字母与数字字符
  \W 等价于字符类 [^a-zA-Z0-9_] - 匹配任何非字母与数字字符
  \b 仅在单词的开头或结尾处匹配,当单词包含在另一个单词中时将不会匹配 re.search(r'\bclass\b', 'no class at all') 可以匹配到class
      re.search(r'\bclass\b', 'one subclass is') 不可以匹配到class

表示固定点标记的元字符^ $

元字符 含义 零宽度断言 例子 可匹配字符
^ []一起使用,表示取反,来匹配字符类中未列出的字符 / [^0-9] 不是数字的单个字符
  作为固定点标记,表示行/字符串的开头 ^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 合法账号,长度在5-16个字符之间,只能用字母数字下划线,且第一个位置必须为字母
      re.search('^From', 'From Here to Eternity') 这个正则表达式可以匹配到From
      re.search('^From', 'Reciting From Memory') 该字符串不以From开头,这个正则表达式匹配不到From
$ 作为固定点标记,表示行/字符串的结尾 re.search('}$', '{block} ' 匹配不到}(不在行/字符串的结尾)
      re.search('}$', '{block}\n') 可以匹配到}(在行的结尾)

和重复相关的元字符 * + ? { }

字符 含义 例子 可匹配字符
* 表示匹配0次或多次,等价于{0,} ca*t ct, cat, caat
+ 表示匹配1次或多次,等价于{1,} ca+t cat, caat
? 表示匹配0次或1次,等价于{0,1} home-?brew homebrew, home-brew
  与其他重复字符一起使用,表示lazy matching,匹配尽可能少的字符 re.search('<.*?>','<python>perl>') 匹配到<python>而不是'<python>perl>'
{m,n} 必须至少重复 m 次,至多重复 n a/{1,3}b 'a/b', 'a//b''a///b'
{,n} {m,} 缺失 m 会解释为最少重复 0 次 ,缺失 n 则解释为最多重复无限次。    
{m} 与前一项完全匹配 m a/{2}b a//b

组 group

含义 例子 可匹配字符
() 将所包含的表达式合为一组,并且可以使用限定符例如 *, +, ?, 或 {m,n} 来重复一个分组的内容 (ab)* 匹配 ab 的零次或多次重复。
(?P<name>...) 命名组,用于给分组命名 p=re.compile('(?P<first>\d)-(\d)-(\d)') p.search('1-2-3').group('first')
输出1
(?:...) 表示匹配该模式,但不捕获该分组 (?:\d+)  
\1 分组的反向引用 r'([ab])\1' 当分组([ab])内的ab匹配成功后,将开始匹配\1\1将匹配前面分组成功的字符。因此该正则表达式将匹配aabb
(?P=Y) Match the named group Y    
# 带分组名称的反向引用
In [27]:
s = '12,56,89,123,56,98, 12'
p = re.compile(r'\b(?P<name>\d+)\b.*\b(?P=name)\b')
m = p.search(s)
m.group(1)

Out[27]:
'12'

使用raw string来处理反斜杠灾难

  • 正则表达式使用反斜杠字符 ('\') 来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义
  • Python 在字符串文字中也使用(\)表示转义

由于这两个规则,如果要在正则中匹配一个\section就需要写很多反斜杠,这会导致大量重复的反斜杠,并使得生成的字符串难以理解。

字符 阶段
\section 被匹配的字符串
\\section re.compile() 转义的反斜杠
"\\\\section" 为字符串字面转义的反斜杠

因此,需要使用 Python 的原始字符串表示法来表示正则表达式;反斜杠不以任何特殊的方式处理前缀为 'r' 的字符串

正则 含义
r"\n" 一个包含 '\''n' 的双字符字符串
"\\n" 一个包含 '\''n' 的双字符字符串
"\n" 一个包含换行符的单字符字符

"\\w+\\s+\\1"r"\w+\s+\1"是等价的,也就是说加了raw string后只需要考虑正则表达式内部的转义就可以了

正则表达式常用函数

方法 / 属性 目的 返回值
match() 确定正则是否从字符串的开头匹配。 None/一个 匹配对象 实例将被返回,包含匹配相关的信息:起始和终结位置、匹配的子串以及其它。
search() 扫描字符串,查找此正则匹配的任何位置,找到第一个匹配 None/一个 匹配对象 实例将被返回,包含匹配相关的信息:起始和终结位置、匹配的子串以及其它。
findall() 找到正则匹配的所有子字符串,并将它们作为列表返回。 findall()只取得所有匹配字符串,返回包含所有匹配字符串的列表,不关心匹配字符串在原字符串中的各项信息。
finditer() 找到正则匹配的所有子字符串,并将它们返回为一个 iterator  
split() 用正则表达式切分字符串,返回一个list  

检查匹配对象实例也有几个方法和属性

方法 / 属性 目的
group() 返回正则匹配的字符串
start() 返回匹配的开始位置
end() 返回匹配的结束位置
span() 返回包含匹配 (start, end) 位置的元组
groups() 用groups()函数取出匹配的所有分组

由于 match() 方法只检查正则是否在字符串的开头匹配,所以 start() 将始终为零。 但是,模式的 search() 方法会扫描字符串,因此在这种情况下匹配可能不会从零开始。

import re
m = re.search(r'\d{3,4}-?\d{8}', '010-66677788,02166697788, 0451-22882828')
print(m.group()) # [Output]: '010-66677788', search总是返回第一个成功匹配
print(m.span())  # [Output]: (0, 12)

ms = re.finditer(r'\d{3,4}-?\d{8}', '010-66677788,02166697788, 0451-22882828')
for m in ms:
    print(m.group()) 
# Output:
# 010-66677788
# 02166697788
# 0451-22882828

p=re.compile('(\d)-(\d)-(\d)')
print(p.search('1-2-3').group())  # [Output]:'1-2-3'
print(p.search('1-2-3').groups()) # [Output]: ('1', '2', '3')

p=re.compile('(?P<first>\d)-(\d)-(\d)')
print(p.search('1-2-3').group('first')) # [Output]: '1'

words = re.split(r'[,-]', '010-66677788,02166697788,0451-22882828')
print(words) # [Output]: ['010', '66677788', '02166697788', '0451', '22882828']

以下两种使用方式只是在性能上有一些区别,功能上没有区别。re.compile会事先编译好正则表达式,在多次循环中访问这个正则时,在性能上会更好

  • p = re.compile(<regular expression>) + p.search('<target_string>')

  • re.search('<regular expression>', '<target_string>')

4. argparse

argparse中有两种类型的参数,都通过add_argument()这个函数添加

  • positional arguments: 在命令行中的相对位置决定了这个参数的用途, path
  • optional arguments: options, flags, or switches,不是必要的,long

最基本的用法:

import argparse
parser = argparse.ArgumentParser()

parser.add_argument("path")
parser.add_argument("-l", "--long", action="store_true"
                    
args = parse.parse_args()

通过添加add_argument_group的方式来让help的输出更加好看,对CLI的输入方式不产生影响,注意这个不是subcommand

parser = argparse.ArgumentParser(
    prog="ls",
    description="List the content of a directory",
    epilog="Thanks for using %(prog)s! :)",
)

general = parser.add_argument_group("general output")
general.add_argument("path")

detailed = parser.add_argument_group("detailed output")
detailed.add_argument("-l", "--long", action="store_true")

args = parser.parse_args()

argparse有辅助缩写的功能,当option的名字很长的时候,可以直接用缩写,默认开启,可以使用argparse.ArgumentParser(allow_abbrev=False)在初始化时关闭

parser.add_argument("--argument-with-a-long-name")
# 以下三条指令是等价的
$ python abbreviate.py --argument-with-a-long-name 42
$ python abbreviate.py --argument 42
$ python abbreviate.py --a 42

add_argument()这个函数中对option设置不同的action 参数

import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
    "--name", action="store"
)  # Equivalent to parser.add_argument("--name")
parser.add_argument("--is-valid", action="store_true")
parser.add_argument("--is-invalid", action="store_false")
parser.add_argument("--item", action="append")
parser.add_argument(
    "--version", action="version", version="%(prog)s 0.1.0"
)
python actions.py `
   --name Python `
   --pi `
   --is-valid `
   --is-invalid `
   --item 1 --item 2 --item 3 
---------------------------------
Namespace(
    name='Python',
    is_valid=True,
    is_invalid=False,
    item=['1', '2', '3']
)

一个参数有多个输入使用nargs,对optional 和positional arguments都适用

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("files", nargs="+")
args = parser.parse_args()
print(args)
$ python files.py hello.txt realpython.md README.md
# files=['hello.txt', 'realpython.md', 'README.md']

设置参数的指定输入

parser.add_argument("--size", choices=["S", "M", "L", "XL"], default="M")

设置互斥的组,-v-s不能在命令行中同时存在

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-s", "--silent", action="store_true")

使用add_subparser()来添加子命令subcommand

global_parser = argparse.ArgumentParser(prog="calc")
subparsers = global_parser.add_subparsers(
    title="subcommands", help="arithmetic operations"
)

arg_template = {
    "dest": "operands",
    "type": float,
    "nargs": 2,
    "metavar": "OPERAND",
    "help": "a numeric value",
}

add_parser = subparsers.add_parser("add", help="add two numbers a and b")
add_parser.add_argument(**arg_template)
add_parser.set_defaults(func=add)

sub_parser = subparsers.add_parser("sub", help="subtract two numbers a and b")
sub_parser.add_argument(**arg_template)
sub_parser.set_defaults(func=sub)

mul_parser = subparsers.add_parser("mul", help="multiply two numbers a and b")
mul_parser.add_argument(**arg_template)
mul_parser.set_defaults(func=mul)

div_parser = subparsers.add_parser("div", help="divide two numbers a and b")
div_parser.add_argument(**arg_template)
div_parser.set_defaults(func=div)

args = global_parser.parse_args()

print(args.func(*args.operands))

5. Unit test in Python

# project/code/my_calculations.py
class Calculations:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def get_sum(self):
        return self.a + self.b

    def get_difference(self):
        return self.a - self.b

# project/test.py
import unittest
from code.my_calculations import Calculations

class TestCalculations(unittest.TestCase):
		# 需要测试的case需要以test开头,让unittest框架识别
    def test_sum(self):
        calculation = Calculations(8, 2)
        self.assertEqual(calculation.get_sum(), 10, 'The sum is wrong.')

if __name__ == '__main__':
    unittest.main()
python -m unittest
python -m unittest -v project.test # 指定单元测试的文件

Reference

  1. python正则表达式快速基础教程
  2. 正则表达式

  3. 正则表达式指南
  4. real python argparse
  5. real python unitest
  6. A Beginner’s Guide to Unit Tests in Python (2023)



Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Solana Core Concepts: Accounts, Programs, and PDAs
  • Python3 Concurrency: asyncio module/async/await
  • MIT6.824 Lab2A Raft Leader Election
  • Linux中的top command
  • ARM交叉编译工具链与编译选项