基础

简介

​ Python是一门动态、解释型、强类型语言

  1. 动态:在运行期间才做数据检查(不用提前声明变量)- 静态语音(C/Java):编译时检查数据类型(编码时需要声明变量类型)
  2. 解释型:在执行程序时,才一条条解释成机器语言给计算机执行(无需编译,速度较慢)- 编译型语言(C/Java):先要将代码编译成二进制可执行文件,再执行
  3. 强类型:类型安全,变量一旦被指定了数据类型,如果不强制转换,那么永远是这种类型(严谨,避免类型错误,速度较慢)- 弱类型(VBScript/JavaScript): 类型在运行期间会转化,如 javascript中的 1+”2”=”12”, 1会由隐式转换为字符串

python解析器

优点

  1. 简单易学
  2. 开发效率高
  3. 高级语言
  4. 可移植、可扩展、可嵌入
  5. 庞大的三方库

缺点

  1. 速度慢
  2. 代码不能加密
  3. 多线程不能充分利用多核cpu(GIL全局解释性锁,同一时刻只能运行一个线程

应用领域

  1. 自动化测试(UI/接口)
  2. 自动化运维
  3. 爬虫
  4. Web开发(Django/Flask/..)
  5. 图形GUI开发
  6. 游戏脚本
  7. 金融、量化交易
  8. 数据分析,大数据
  9. 人工智能、机器学习、NLP、计算机视觉
  10. 云计算

环境搭建

Windows Python3环境搭建

  1. Python官网,下载Python3安装包
  2. 双击安装,第一个节目选中Add Python3.* to PATH,点击Install Now(默认安装pip),一路下一步
  3. 验证:打开cmd命令行,输入python,应能进入python shell 并显示为Python 3.6.5版本

Mac OS Python3环境搭建

  1. 安装brew:在终端执行以下命令 ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
  2. 使用brew安装python3:brew install python3

CentOS Python3环境搭建

  1. 安装依赖包

    1
    2
    yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make
    yum install libffi-devel -y
  2. 下载Python3源码安装

    1
    2
    3
    4
    5
    wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz
    tar -zxvf Python-3.7.0.tgz
    cd Python-3.7.0
    ./configure --prefix=/usr/local/python37
    make & make install
  3. 建立软连接

    1
    2
    ln -s /usr/local/python37/bin/python3.7 /usr/bin/python3
    ln -s /usr/local/python37/bin/pip3 /usr/bin/pip3
  4. 添加环境变量

    1
    2
    3
    export PATH=$PATH:/usr/local/python37/bin
    # or
    vim ~/.bashrc, 增加

包管理工具-pip

在线安装库文件

  • 在线安装:pip install 包名
  • 从豆瓣源查找安装:pip install 包名 -i https://pypi.doubanio.com/simple/
  • 直接从GitHub安装:git+https://github.com/hanzhichao/logz
  • 一次安装多个:pip install 包1 包2
  • 指定安装版本:pip install 包名==1.5.6
  • 升级到包的最新版本:·pip install 包名 –upgrade·
  • 卸载包:pip uninstall 包名
  • 批量安装requiements.txt文件中所有列出的包:pip install -r requiements.txt
  • 查看已安装包的信息:pip show 包名
  • 查看已安装的所有包:pip list
  • 导出当前环境所有安装的包:pip freeze > requirements.txt

修改pip源

​ Linux/MacOS下,修改 ~/.pip/pip.conf (没有就创建一个),windows下,直接在user目录中创建一个pip目录,如:C:\Users\xx\pip,新建文件pip.ini,内容如下:

1
2
3
4
5
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
#豆瓣:http://pypi.douban.com/simple/
#中科大:https://pypi.mirrors.ustc.edu.cn/simple/
#清华:https://pypi.tuna.tsinghua.edu.cn/simple

pip 离线安装

  • 离线安装
    1. Pypi.org网站查找需要的包,下载.whl文件
    2. 使用pip install <下载的whl包> 进行本地whl文件安装
  • 源码安装
    1. Pypi.org下载源码包,解压,进入解压目录
    2. 打开命令行,执行 python setup.py install
    3. 验证:pip list 进行查找

语法

缩进

​ python 语法没有 { } 代码块,严格按照缩进进行代码块的规范

1
2
3
4
5
6
7
8
9
if x > 0:
print("正数")
elif x = 0:
print("0")
else:
print("负数")

def add(x,y):
return x+y

一行多条语句

除了可以利用换行进行语句结束划分,也可以使用 ;

1
x=1; y=2; print(x+y)

断行

当一行语句太长时,会显得累赘;可以使用 ‘\‘ 将两行语句归为一行

注释

1
2
3
4
5
6
7
8
9
10
# 单行注释
'''
多行注释
'''
"""
多行注释,也可以代表函数说明
使用三个单引号(''' ''')或三个双引号(""" """)来创建多行注释
用法:三个单引号和三个双引号都可以用来创建多行注释。两者的作用是一样的,都可以用于对代码进行注释和说明。
使用双引号时,你不需要转义单引号,而使用单引号时则需要转义双引号。
"""

类型注释

在函数中,可以对参数及返回值类型进行注释

1
2
def add(x: int, y: int) -> int:
return x+y

变量

  1. 变量类型(局部变量、全局变量、系统变量)

  2. 变量赋值

    • 多重赋值x=y=z=1

    • 多元赋值x,y = y,x

  3. 变量自增 x+=1x-=1(不支持x++, x--)

  4. Python 语言本身没有提供内置的常量机制,但是通常使用全大写命名的变量来表示常量,在 Python 3.8 中引入了 typing.Final 来标识一个变量为最终值;Python 中,使用约定而不是强制,不像Java 有明确的常量关键字 final 来定义常量

    1
    2
    from typing import Final
    PI: Final = 3.14159

运算符

  • 算术运算符:一般用于数字类型的计算

    • +:加,如 1+2,结果为3,在字符串中为连接,如'1' + '2',结果为’12’,不同类型不能直接相加。
      • :减,如 3-1,结果2
    • :乘,如23,结果为6
    • /:除:如 3/2,结果为1.5
    • //:地板除(只向下保留整数),如3//2,结果为1,3//-2,结果为-2
    • **:乘方,如4**2,结果为16
    • %: 取余,如果5%2,结果为1
  • 比较运算符:可用于两个相同类型对象的比较

    • ==:相等比较,如1+1==2,结果为True,{'a': 1, 'b': 2} = {'b': 1 , 'a': 1},结果为True
    • !=:比较是否不等,如1+1 != 3,结果为True,[1,2,3] != [2,1,3],结果为True
    • <:比较小于
    • <=:比较小于等于
  • 身份运算符:用于判断是否同一对象

    • is:判断是同一对象,’==’只判断值是否相等,如True==1,is则判断是否同一对象(使用id()得出的内存地址相同),2>1 is True,结果为True,a=None;a is None`,结果为True
    • is not:判断非同一对象,如True is not 1结果为True。
  • 赋值运算符:用于将值赋给变量

    • =:赋值,如a = 1,将1赋给变量a,支持多重赋值,如a,b = 1,2a,b=[1,2](赋值时序列会自动解包变为1,2两个变量),结果为将1赋给a,将2赋给b
    • +=:变量自增,如a+=1相当于a=a+1
    • -=: 自减
    • *=:自乘
    • /=:自除
    • //=:自地板除
    • **=:自乘方
    • %=:自取余
  • 逻辑运算符:用于多个表达式的逻辑判断,有短路效应

    • and:并且,如1>2 and 1>0,都为真时返回最后一个结果的值。当第一个条件为假时不执行后面的判断,直接返回False,如果第一个条件为真时结果为第二个判断的结果(本例实际1>0不会执行)
    • or:或,如1>2 or 1>0,返回第一个为真的值或都无假时返回最后一个结果的值。当第一个条件为真是不执行后面的判断,直接返回True,否则返回第二个结果的值(本例1>0会执行)
    • not:非,如not 1>2,结果为True
  • 成员运算符:用于判断包含,常用判断字符串、列表、元祖、集合、字典中是否包含某元素(某键值)

    • in:是否在其中,如a in 'abcd',结果为True,或1 in [1,2,3],结果为True,或'name' in {'name': 'kevin', 'age': 12},结果为True。
    • not in:判断不包含,如a not in 'bcde'结果为True,从效率上由于not in会对比所有的元素,因此效率比使用in低
  • 位运算符:用于二进制或集合运算

    • &:按位与
    • |:按位或
    • ~:按位取反
    • ^:按位异或
    • <<:左移

运算符优先级 ( 从高到低)

运算符 描述
** 指数(最高优先级)
~、+、- 按位翻转,一元加号和减号(最后两个的方法名为+@和-@)
*、/、%、// 乘、除、取模和取整除
+、- 加法、减法
>>、<< 右移、左移运算符
& 位与
^、| 位运算符
<=、<、>、>= 比较运算符
<> == != 等于运算符
= %= /= //= -= += *= **= 赋值运算符
is is not 身份运算符
in in not 成员运算符
not or and 逻辑运算符

[''][[],[]]由于不是空列表,在逻辑判断时被认为是True。

表达式与语句

​ Python代码由表达式和语句组成;表达式(Expression)是运算符(operator)和操作数(operand)所构成的序列,语句是让计算机执行特定操作的指示

  • 表达式

    表达式一般结果为一个Python对象,如1+2, int('123')range(10)
    表达式一般可以包含算数运算符、比较运算符、逻辑运算符、成员运算符、位运算符,但不能包含赋值运算符

    由于表达式计算后返回一个Python对象,因此表达式可以当做待计算的变量一样使用

  • 语句

    结果不是对象的代码则成为‘语句’。它们表示的是一个动作而不是生成或者返回一个值。

    常见的Python语句有:

    • 赋值语句
    • 调用
    • print:打印对象
    • if/elif/else
    • for/else、while/else
    • pass
    • break、continue
    • def
    • return、yield
    • global
    • raise
    • import、from … import

基本数据类型

数据类型

Python基本的数据类型包含10种,分别是:

  • 整数(int):表示整数值,例如:5、-10、100。
  • 浮点数(float):表示带有小数点的数值,例如:3.14、-2.5、1.0。
  • 布尔类型(bool):表示真(True)或假(False)的值。
  • 字符串类型(str):表示文本数据,使用单引号(’)或双引号(”)括起来,例如:”Hello”、’World’。
  • 列表类型(list):表示有序、可变的集合,可以包含不同类型的元素,使用方括号[] 括起来,例如:[1, 2, ‘three’, True]。
  • 元组类型(tuple):表示有序、不可变的集合,可以包含不同类型的元素,使用圆括号()括起来,例如:(1, 2, ‘three’, True)。
  • 集合类型(set):表示无序、唯一的元素集合,不包含重复的元素,使用大括号{}或 set() 函数创建,例如:{1, 2, 3}、set([1, 2, 3])。
  • 字典类型(dict):表示键值对的集合,每个键都是唯一的,使用大括号{}括起来,键和值之间使用冒号(:)分隔,例如:{‘name’: ‘John’, ‘age’: 25}。
  • NoneType 类型:None 是一个特殊的常量,表示一个空对象或空值。它是一个内置的对象
  • 复数类型(complex)

这些又可以分为可变类型和不可变类型

在 Python 中,对于一些简单的不可变对象,如整数和短字符串等,解释器会对其进行缓存和重用,以提高性能和节省内存。这就是常见的整数对象池(integer pool)机制。

  • 不可变类型- 整数、浮点数、布尔类型、字符串、元组

不可变类型指的是一旦创建就不能被修改的数据类型,例如整数、浮点数、布尔类型、字符串、元组等。对于不可变类型的数据,如果需要修改,通常需要创建一个新的对象来代替原来的对象。

对于不可变类型的数据,虽然不能直接修改其内容,但是可以通过重新赋值的方式来改变其值。

  • 可变类型 - 列表、集合、字典等

可变类型指的是可以被修改的数据类型,例如列表、集合、字典等。对于可变类型的数据,可以直接修改其内容,而不需要创建新的对象。

可以通过选择合适的数据类型、避免频繁的对象复制、使用不可变类型作为函数参数、使用生成器和迭代器等方式来提高程序的性能。

总结:

不可变类型:数字/字符串/元祖/frozen set
可变类型:列表、集合、字典

有序类型:序列/字符串/列表/元祖
无序类型:集合、字典

操作符

  • +:加
  • -:减
  • *:乘
  • /:除,结果为浮点数,如1/2=0.5,又称真实除
  • //:整除,舍去所有小数,又称地板除
  • %:取模,如3 % 2 = 1
  • **:乘方,如3 ** 2 = 9

类型转换

​ !!! 字符串形式的浮点数,如’1.23’,只能使用float转为浮点数,用int转为整数则会报错。

  • str(): 其他类型转为字符串, 如str(12)
  • int():字符串整数或浮点数转为整型,如int("12")
  • float():字符串数字或整形,转换为浮点数,如float("1.23")

进制转换

Python中的数字除了10进制数之外,还支持2进制(表示为0b开头)、8进制(表示为0o开头)、16进制数(表示为0x开头)。相互转换方法如下:

  • bin():转为2进制,如bin(10),结果为0b1010
  • oct():转为8进制,如oct(10),结果为0o12
  • hex():转为16进制,如hex(10),结果为0xa
  • int():转为10进制,如int(0b1010)结果为10

内置函数

  1. abs(x):
    返回x的绝对值。如果x是一个复数,返回其模。
  2. all(iterable):
    返回True如果可迭代对象中的所有元素均为真(非零),否则返回False。
  3. any(iterable):
    返回True如果可迭代对象中的任一元素为真(非零),否则返回False。
  4. ascii(obj):
    返回对象的可打印表示形式,使用Python的转义语法。
  5. bin(x):
    返回x的二进制表示形式。
  6. bool(x):
    将x转换为布尔值,返回True或False。
  7. breakpoint(*args, **kws):
    断点调试函数,用于交互式调试。
  8. bytearray(source, encoding, errors):
    创建一个可修改的字节数组对象。
  9. bytes([source[, encoding[, errors]]]):
    返回一个bytes对象,适合存储二进制数据。
  10. callable(obj):
    检查对象是否可调用(函数、方法、类、实例等)。
  11. chr(i):
    返回Unicode码位i对应的字符。
  12. classmethod(func):
    将函数装饰为类方法。
  13. compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1):
    将源代码编译为代码对象。
  14. complex(real, imag):
    创建一个复数。
  15. delattr(obj, name):
    删除对象的属性。
  16. dict([object]):
    创建一个字典对象。
  17. dir([object]):
    返回一个对象的所有属性和方法的列表。
  18. divmod(a, b):
    返回a除以b的商和余数。
  19. enumerate(iterable, start=0):
    返回一个包含索引和值的枚举对象。
  20. eval(expression, globals=None, locals=None):
    执行字符串表达式,并返回结果。
  21. exec(object, globals, locals):
    执行Python代码。
  22. filter(function, iterable):
    使用指定函数过滤可迭代对象,返回一个迭代器。
  23. float(x):
    将字符串或数字转换为浮点数。
  24. format(value, format_spec):
    格式化字符串输出。
  25. frozenset([iterable]):
    创建一个不可变的集合对象。
  26. getattr(object, name, default=None):
    返回对象的属性值。
  27. globals():
    返回全局变量的字典。
  28. hasattr(object, name):
    检查对象是否具有指定属性。
  29. hash(object):
    返回对象的哈希值。
  30. help([object]):
    调用内置帮助系统。
  31. hex(x):
    返回整数x的十六进制表示形式。
  32. id(object):
    返回对象的唯一标识符。
  33. input(prompt):
    从用户获取输入。
  34. int(x, base=10):
    将字符串或数字转换为整数。
  35. isinstance(object, classinfo):
    检查一个对象是否是指定类的实例。
  36. issubclass(class, classinfo):
    检查一个类是否是另一个类的子类。
  37. iter(object, sentinel):
    返回一个迭代器对象。
  38. len(s):
    返回对象的长度。
  39. list([iterable]):
    创建一个列表对象。
  40. locals():
    返回当前局部变量的字典。
  41. map(function, iterable, …):
    将指定函数应用于可迭代对象的每个元素,返回一个迭代器。
  42. max(iterable, * [, key, default]):
    返回可迭代对象中的最大值。
  43. memoryview(obj):
    创建一个内存视图对象。
  44. min(iterable, * [, key, default]):
    返回可迭代对象中的最小值。
  45. next(iterator, default):
    返回迭代器的下一个元素。
  46. object():
    创建一个空对象。
  47. oct(x):
    返回整数x的八进制表示形式。
  48. open(file, mode=’r’, buffering=-1):
    打开文件并返回文件对象。
  49. ord(c):
    返回Unicode字符c的整数表示。
  50. pow(x, y, z=None):
    返回x的y次幂,再模z。
  51. print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False):
    输出对象到标准输出。
  52. property(fget=None, fset=None, fdel=None, doc=None):
    返回一个属性对象。
  53. range(start, stop, step):
    创建一个包含指定范围数字的可迭代对象。
  54. repr(object):
    返回对象的可打印表示形式。
  55. reversed(seq):
    返回逆序的迭代器对象。
  56. round(number[, ndigits]):
    四舍五入函数。
  57. set([iterable]):
    创建一个集合对象。
  58. setattr(object, name, value):
    设置对象的属性值。
  59. slice(stop):
    创建一个切片对象。
  60. sorted(iterable, *, key=None, reverse=False):
    对可迭代对象进行排序。

字符串

系统方法

方法 说明 示例
len() 计算字符串长度 len(“abcdefg”),结果为7
count() 查询字符串中某个元素的数量 aabcabc”.count(“a”),结果为3
find() / index() 查找字符串中某个字符第一次出现的索引,find()找不到返回-1 , index()找不到报错 “abcdefg”.find(“b”),结果为1 , ”abcdefg”.index(“b”),结果也为1
replace() 替换字符串中的某部分 “hello,java”.replace(“java”, “python”),结果为hello,python
split() 将字符串按分隔符分割成列表 “a,b,c,d”.split(“,”),结果为[“a”, “b”, “c”, “d”]
join() 将字符串作为分隔符连接列表元素得到一个字符串 “-“.join([“a”, “b”, “c”, “d”]),结果为a-b-c-d
lower() / upper() 将字符串转换为全小写/大写 “AbcdeF”.lower(),结果为abcdef , “abcedF”.upper(),结果也为ABCDEF
isdigit() / isalpha() / isalnum() 字符串是否纯数字/纯字母/纯数字字母组合 “123”.isdigit(),结果为True
strip() / lstrip() / rstrip() 去掉字符串左右/左边/右边的无意字符(包括空格、换行等非显示字符) “ this has blanks \n”.strip(),结果为this has balnks

字符串遍历

  1. 使用 for 循环:

    1
    2
    3
    s = "hello"  
    for char in s:
    print(char)
  2. 使用 enumerate() 函数(获取索引和字符):

    1
    2
    3
    s = "hello"  
    for index, char in enumerate(s):
    print(index, char)
  3. 使用 range() 函数和索引:

    1
    2
    3
    s = "hello"  
    for i in range(len(s)):
    print(s[i])

格式化输出

字符串格式化是指,将字符串的某部分按一定格式输出,同时也可以将某些变量的实际值,插入到字符串中。

  • %: 如"Name: %s, Age: %d" % ("Lily", 12)"Name: %(name)s, Age: %(age)d" % {"name": "Lily", "age": 12}
  • format: 如"Name: {}, Age: {}".format("Lily", 12)"Name: {name}, Age: {age}".format(name="Lily",age=12)
  • fstring:如 f'Name: {name}, Age: {age}'
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
tpl='''<html>
<head><title>{title}</title></head>
<body>
<h1>{title}</h1>
<table border=1px>
<tr>
<th>序号</th>
<th>用例</th>
<th>结果</th>
</tr>
{trs}
</table>
</body>
</html>
'''

tr='''<tr><td>{sn}</td>
<td>{case_name}</td>
<td>{result}</td>
'''

title="自动化测试报告"
case_results = [("1", "test_add_normal", "PASS"),("2", "test_add_negative", "PASS"), ("3", "test_add_float", "FAIL")]

trs=''
for case_result in case_results:
tr_format = tr.format(sn=case_result[0], case_name=case_result[1], result=case_result[2])
trs += tr_format

html = tpl.format(title=title, trs=trs)

f = open("report.html", "w")
f.write(html)
f.close()

列表list

​ 列表元素支持各种对象的混合,支持嵌套各种对象,如["a", 1, {"b": 3}, [1,2,3]]

列表操作

  • 赋值: l = [1, "hello", ("a", "b")]
  • 获取: a = l[0] # 通过索引获取
  • 增: l.append("c");l.extend(["d","e"]);l+["f"]
  • 删: l.pop() # 按索引删除,无参数默认删除最后一个;l.remove("c") # 按元素删除
  • 改:l[1]="HELLO" # 通过索引修改
  • 查: 遍历 for i in l: print(i)

列表系统方法

方法 说明 示例
append() 添加 [1,2].append(3) = [1, 2, 3]
insert() 插入 [1,3].insert(1,2) = [1, 2, 3]
extend() 扩展(连接) [1,2].extend([3,4]) = [1, 2, 3, 4]
index() 获取元素索引 [1,2].index(2) = 1
count() 统计元素个数 [1,2,1,1].count(1) = 3
pop() 按索引删除 [1,2].pop(0) = [2] 返回值 1
remove() 按元素删除 [1,2].remove(1) = [2]
sort() 排序 [1,3,2].sort() = [1, 2, 3]
reverse() 反转 [1,3,2].reverse() = [2, 3, 1]

列表遍历

  1. 使用 for 循环

最直接的方式是使用 for 循环来遍历列表中的每个元素。

1
2
3
lst = [1, 2, 3, 4, 5]  
for item in lst:
print(item)
  1. 使用 enumerate() 函数

enumerate() 可以同时获取列表的索引和值。

1
2
3
lst = [10, 20, 30, 40, 50]  
for index, value in enumerate(lst):
print(f"Index: {index}, Value: {value}")
  1. 使用 range() 函数和索引

可以通过 range() 函数生成索引,然后用这些索引来访问列表的元素。

1
2
3
lst = ['a', 'b', 'c', 'd']  
for i in range(len(lst)):
print(f"Index: {i}, Value: {lst[i]}")
  1. 使用列表推导式

列表推导式是一种简洁的方式来处理列表元素。虽然它不直接用于遍历,但可以在创建新列表时处理每个元素。

1
2
3
lst = [1, 2, 3, 4, 5]  
squared = [x**2 for x in lst]
print(squared)
  1. 使用 map() 函数

map() 函数可以将一个函数应用于列表中的每个元素。

1
2
3
4
5
6
lst = [1, 2, 3, 4, 5]  
def square(x):
return x ** 2

squared = list(map(square, lst))
print(squared)
  1. 使用 while 循环

虽然不如 for 循环常用,但 while 循环也可以用于遍历列表。

1
2
3
4
5
lst = [1, 2, 3, 4, 5]  
i = 0
while i < len(lst):
print(lst[i])
i += 1
  1. 使用 itertools 模块

如果你需要更高级的遍历功能,比如在迭代过程中使用多个列表,可以考虑使用 itertools 模块中的工具。

1
2
3
4
5
6
import itertools  

lst1 = [1, 2, 3]
lst2 = ['a', 'b', 'c']
for item in itertools.zip_longest(lst1, lst2, fillvalue='None'):
print(item)

元祖tuple

  1. 不可改变,常用作函数参数(安全性好)
  2. 同样支持混合元素以及嵌套
  3. 只有一个元素时,必须加”,”号,如a=("hello",) - 因为Python中()还有分组的含义,不加”,”会识别为字符串

为什么需要元祖?有时候我们需要多个变量来表达一个确定的值,如坐标(x,y)。在哈希算法中,不可变是非常重要的,这样每次生成的哈希值才能相同。作为不可变对象,元祖可以作为字典的KEY,即{(1,2): 3}是合法的。

元祖对象操作方法

​ 由于元素是不可变对象,自带操作对象较少

方法 说明 示例
index() 获取元素索引 t=(1,2,3); index = (t.index(2))
count() 获取元素个数 t=(1,2,3,2,1,2,3); count = (t.count(2))

元组遍历方法

  1. 使用 for 循环

最简单的方法是使用 for 循环遍历元组中的每个元素。

1
2
3
tup = (1, 2, 3, 4, 5)  
for item in tup:
print(item)
  1. 使用 enumerate() 函数

enumerate() 可以同时获取元组的索引和值。

1
2
3
tup = ('a', 'b', 'c', 'd')  
for index, value in enumerate(tup):
print(f"Index: {index}, Value: {value}")
  1. 使用 range() 函数和索引

通过 range() 函数生成索引,并使用这些索引来访问元组的元素。

1
2
3
tup = (10, 20, 30, 40)  
for i in range(len(tup)):
print(f"Index: {i}, Value: {tup[i]}")
  1. 使用 while 循环

while 循环也可以用于遍历元组,但通常使用 for 循环会更简单。

1
2
3
4
5
tup = (100, 200, 300, 400)  
i = 0
while i < len(tup):
print(tup[i])
i += 1
  1. 使用列表推导式

虽然列表推导式通常用于创建新列表,但你可以在创建新列表时处理元组的每个元素。

1
2
3
tup = (1, 2, 3, 4, 5)  
squared = [x**2 for x in tup]
print(squared)
  1. 使用 map() 函数

map() 函数可以将一个函数应用于元组中的每个元素。

1
2
3
4
5
6
tup = (1, 2, 3, 4, 5)  
def square(x):
return x ** 2

squared = tuple(map(square, tup))
print(squared)
  1. 使用 itertools 模块

itertools 模块提供了很多有用的函数来处理迭代器和元组。

1
2
3
4
5
6
import itertools  

tup1 = (1, 2, 3)
tup2 = ('a', 'b', 'c')
for item in itertools.chain(tup1, tup2):
print(item)

序列类型相关操作方法

​ 字符串、列表、元祖等按顺序存储的变量类型,我们统称为序列类型。

序列类型 - 索引

  • 正反索引: l[3];l[-1]
  • 索引溢出(IndexError): 当索引大于序列的最大索引时会报错,如[1,2,3,4]最大索引是3,引用l[4]会报IndexError

序列类型 - 切片

  • l[1:3] # 从列表索引1到索引3(不包含索引3)进行截取, 如 l = [1, 2, 3, 4, 5], l[1:3]为[2, 3]
  • l[:5:2] # 第一个表示开始索引(留空0), 第二个表示结束索引(留空为最后一个,即-1), 第三个是步长, 即从开头到第5个(不包含第5个),跳一个取一个
  • 案例: 字符串反转 s="abcdefg";r=s[::-1]

序列类型-遍历

  • 按元素遍历: for item in l: print(item)
  • 按索引遍历: for index in range(len(l)): print(l[index])
  • 按枚举遍历: for i,v in enumerate(l): print((i,v))

当遍历序列类型(如列表、元组、字符串等)和非序列类型(如集合、字典等)时,存在一些区别和注意点。

  • 对于集合,遍历顺序可能会不一致。
  • 对于字典,Python 3.7+ 保持插入顺序,但在 Python 3.6 及更早版本中,字典的遍历顺序是不确定的。
1
2
3
4
5
6
7
8
9
10
11
12
# python3.7+
# 遍历键
for key in {'a': 1, 'b': 2}:
print(key)

# 遍历值
for value in {'a': 1, 'b': 2}.values():
print(value)

# 遍历键值对
for key, value in {'a': 1, 'b': 2}.items():
print(key, value)

遍历序列类型

  1. 使用 for 循环:

    1
    2
    3
    my_list = [1, 2, 3, 4]
    for item in my_list:
    print(item)
  2. 使用 while 循环和索引:

    1
    2
    3
    4
    5
    my_tuple = (10, 20, 30)
    index = 0
    while index < len(my_tuple):
    print(my_tuple[index])
    index += 1
  3. 使用内置函数 enumerate() 获取索引和值:

    1
    2
    3
    my_string = "Hello"
    for index, value in enumerate(my_string):
    print(f"Index: {index}, Value: {value}")
  4. 列表解析(List comprehension):

    1
    2
    3
    my_list = [1, 2, 3]
    squared_values = [x**2 for x in my_list]
    print(squared_values)
  • 序列类型支持索引操作,因此可以使用循环或者内置函数来获取每个元素。
  • 序列类型的元素有顺序,因此我们可以按照它们在序列中的位置来进行遍历。
  • 序列类型通常具有固定长度,因此我们可以使用内置函数 len() 来获取其长度

遍历非序列类型

集合 (set) 示例:

1
2
3
my_set = {10, 'a', True}
for item in my_set:
print(item)

字典 (dict) 示例:

1
2
3
4
5
6
7
8
9
my_dict = {'name': 'Alice', 'age': 25}
# 遍历键值对
for key, value in my_dict.items():
print(key, value)
# 只遍历键或值
for key in my_dict.keys():
print(key)
for value in my_dict.values():
print(value)

某些非序列对象可能具有特定方法用于迭代。例如,在 pandas 的 DataFrame 中,我们可以使用 iteritems() 方法来遍历每一行数据。

区别与注意点

  • 对于集合(set),由于其是无需且不重复的容器,所以在遍历时不能保证元素的顺序。
  • 字典(dict)是键值对形式存储数据的对象,在循环中需要考虑是否要同时处理键和值。
  • 其他非序列对象可能具有特定方法用于迭代。需要查看相应文档以了解如何进行迭代操作。

扩展/连接(添加多个元素

extend()/+ "abc"+"123";[1,2,3]+[4,5];[1,2,3].extend([4,5,6,7])

类型互转: str()/list()/tuple()

list转str一般用join(), str转list一般用split()

系统函数

  • len(): 计算长度
  • sort()/revers(): 排序/反转列表序列

集合 set

集合是Python中一种映射类型,集合中的元素要是不可变类型(数字、字符串、元祖),元素不重复(自动去重)。

由于集合是基于映射类型(基于hash算法计算得到的元素地址,而不是顺序排列),相比于列表和元祖,集合的查询效率非常高,无论集合中有多少个元素,查询某个元素只需要一次操作。

Python中有可变结合set和不可变集合frozenset两种

创建集合

1
2
3
s = {'a', 'b', 'c'}
s = set() # 创建空集合
s = set(['a', 'b', 'c']) # 将列表转为集合

集合操作

同数学概念中的集合,Python中的集合也支持交集、并集、差集等操作,集合于集合常见操作符如下:

  • 联合(并集): 例如:{'a', 'b' } | {'b', 'c'},结果为{'a', 'b', 'c'}
  • 交集: &: 例如:{'a', 'b' } & {'b', 'c'} ,结果为{'b'}
  • 差集: 例如:{'a', 'b' } - {'b', 'c'},结果为 {'a'},相反{'b', 'c' } - {'a', 'b'},结果为{'c'}
  • 对称差分(去除相同的项做并集): 例如{'a', 'b' } ^ {'b', 'c'}结果为{'a', 'c'}

通过集合操作快速对比出一些数据的不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data1 = [{'name': '王六', 'score': 75},
{'name': '张三', 'score': 98},
{'name': '赵六', 'score': 89},
{'name': '李四', 'score': 87},
{'name': '李五', 'score': 87}]
data2 = [{'name': '赵六', 'score': 89},
{'name': '李四', 'score': 98},
{'name': '张三', 'score': 98},
{'name': '王五', 'score': 75},
{'name': '王六', 'score': 85}]

# 将列表中的每一项转为value值组成的不可变元祖, 然后将列表转为集合
set1 = set([tuple(i.values()) for i in data1])
set2 = set([tuple(i.values()) for i in data2])

print('data2中于data1中不同的有', set1-set2)
print('data1中于data2中不同的有', set2-set1)

集合对象自带方法

方法 说明 示例
add() 添加元素 s = set(); s.add(‘a’)
update() 更新集合(批量添加多个) s = {‘a’, ‘b’}; s1=set([‘b’, ‘c’]); s.update(s1)
remove(element) 移除指定元素(如果元素不存在报错) s = {‘a’, ‘b’, ‘c’}; s.remove(‘a’) ;print(s)
discard(element) 丢弃指定元素(如果元素不存在不报错) s = {‘a’, ‘b’, ‘c’}; s.discard(‘d’) ;print(s)
pop() 随机移除一个元素 s={‘a’, ‘b’, ‘c’}; s.pop(); print(s)
copy() 复制集合 s = {‘a’, ‘b’, ‘c’}; s1 = s.copy(); print(s1)
clear() 清空集合 s = {‘a’, ‘b’, ‘c’}; s.clear() ; print(s)
difference(s1) 与另一个集合的差别(相当于差集) s = {‘a’, ‘b’};s1 = {‘b’, ‘c’};print(s.difference(s1))
issubset(s1) 是否子集 s = {‘a’, ‘b’}; s1 = {‘a’, ‘b’, ‘c’}; print(s.issubset(s1))
issuperset(s) 是否父集 s = {‘a’, ‘b’};s1 = {‘a’, ‘b’, ‘c’}; print(s1.issuperset(s))
isdisjoint(s) 是否互斥(彼此都不包含对方元素) s = {‘a’, ‘b’};s1 = {‘c’, ‘d’}; print(s1.isdisjoint(s))

集合遍历

  1. 使用 for 循环

最直接的方法是使用 for 循环来遍历集合中的每个元素。

1
2
3
my_set = {1, 2, 3, 4, 5}  
for item in my_set:
print(item)
  1. 使用 enumerate() 函数

虽然 enumerate() 通常与列表一起使用,但你也可以将其与集合一起使用,以获取元素的索引和对应的值。然而,由于集合是无序的,索引的值将是基于集合的遍历顺序,而不是原始的顺序。

1
2
3
my_set = {'a', 'b', 'c', 'd'}  
for index, value in enumerate(my_set):
print(f"Index: {index}, Value: {value}")
  1. 使用 while 循环

while 循环可以用来遍历集合,但需要小心,因为集合没有顺序。

1
2
3
4
5
6
7
8
my_set = {10, 20, 30, 40}  
iterator = iter(my_set)
while True:
try:
item = next(iterator)
print(item)
except StopIteration:
break
  1. 使用集合推导式

集合推导式可以用于处理集合的每个元素并创建一个新的集合。虽然它主要用于生成新的集合,但它可以在生成过程中处理每个元素。

1
2
3
my_set = {1, 2, 3, 4, 5}  
squared = {x**2 for x in my_set}
print(squared)
  1. 使用 map() 函数

map() 函数可以应用一个函数到集合中的每个元素。因为集合是无序的,返回的结果集合也将是无序的。

1
2
3
4
5
6
my_set = {1, 2, 3, 4, 5}  
def square(x):
return x ** 2

squared = set(map(square, my_set))
print(squared)
  1. 使用 itertools 模块

itertools 模块提供了很多有用的函数来处理迭代器和集合。例如,itertools.chain() 可以用来连接多个集合。

1
2
3
4
5
6
import itertools  

set1 = {1, 2, 3}
set2 = {4, 5, 6}
for item in itertools.chain(set1, set2):
print(item)
  1. 集合的有序遍历(使用排序)

虽然集合本身是无序的,但你可以将集合转换为列表,并对列表进行排序,从而以特定顺序遍历元素。

1
2
3
4
my_set = {3, 1, 4, 2, 5}  
sorted_list = sorted(my_set)
for item in sorted_list:
print(item)

案例1: 列表去重

l=[1,2,3,1,4,3,2,5,6,2];l=list(set(l)) (由于集合无序,无法保持原有顺序)

案例2: 100w条数据,用列表和集合哪个性能更好?

集合性能要远远优于列表, 集合是基于哈希的, 无论有多少元素,查找元素永远只需要一步操作, 而列表长度多次就可能需要操作多少次(比如元素在列表最后一个位置)

字典 dict

​ 字典是由若干key-value对组成, Python3.6后字典是有序的, 字典的key不能重复,而且必须是可哈希的,通常是字符串

创建字典

  • dict(key1=value1, key2=value2, …): 创建字典

    1
    2
    d = dict(a=1, b=2, c=3)
    d = {'a': 1, 'b': 2, 'c': 3}
  • dict.fromkeys(key1, key2, key3, … , default): 以默认值创建包含多个键的字典

    1
    2
    d = dict.fromkeys(['a', 'b', 'c'], 1)
    d={'a': 1, 'b': 1, 'c': 1}

字典操作

  • 赋值: d = {"a":1, "b":2}
  • 获取: a = d['a']a = d.get("a") # d中不存在"a"元素时不会报错
  • 增: d["c"] = 3; d.update({"d":5, "e": 6}
  • 删: d.pop("d"); d.clear() # 清空
  • 查: d.get("c")
  • 遍历:
    • 遍历key: for key in d:for key in d.keys():
    • 遍历value: for value in d.values():
    • 遍历key-value对: for item in d.items():

字典常用方法

方法 说明 示例
get(key, default=None) 获取指定键的值,如果不存在该键,则返回default默认值, d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; print(d.get(e, 5))
setdefault(key, default) 设置没有字典中该项时的默认值 d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; d.set_default(‘e’, 5); print(d[‘e’])
keys() 所有键的集合(类似列表) d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; print(d.keys())
values() 所有值的集合(类似列表) d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; print(d.values())
items() 所有key, value对的集合(类似列表) d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; print(d.items())
copy() 复制字典(浅拷贝) d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; d2 = d.copy(); print(d2)
update(…) 更新字典 d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; d.update({‘c’: 4, ‘d’: 5}; print(d)
clear() 清空字典 d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; d.clear(); print(d)
pop(key) 取出(移除)指定key并获取对应的值 d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; b_value = d.pop(‘b’); print(d)
popitem() 取出(移除)末尾的key-value对 d = {‘a’: 1, ‘b’: 2, ‘c’: 3}; print(d.popitem())

字典遍历

  1. 遍历字典的键(Keys)

使用 for 循环遍历字典的键,可以通过字典的 keys() 方法获取所有的键。

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}  
for key in my_dict.keys():
print(key)
  1. 遍历字典的值(Values)

使用 for 循环遍历字典的值,可以通过字典的 values() 方法获取所有的值。

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}  
for value in my_dict.values():
print(value)
  1. 遍历字典的键值对(Items)

使用 for 循环遍历字典的键值对,可以通过字典的 items() 方法获取所有的键值对。

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}  
for key, value in my_dict.items():
print(f"Key: {key}, Value: {value}")
  1. 使用字典推导式

字典推导式是一种创建新字典的简洁方法,可以在创建过程中处理字典的每个元素。

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}  
squared_values = {k: v**2 for k, v in my_dict.items()}
print(squared_values)
  1. 使用 enumerate() 函数

enumerate() 可以用于获取字典项的索引和对应的键值对,尽管字典本身没有固定顺序。

1
2
3
my_dict = {'a': 1, 'b': 2, 'c': 3}  
for index, (key, value) in enumerate(my_dict.items()):
print(f"Index: {index}, Key: {key}, Value: {value}")
  1. 使用 while 循环和迭代器

通过创建字典项的迭代器并使用 while 循环遍历字典。

1
2
3
4
5
6
7
8
my_dict = {'a': 1, 'b': 2, 'c': 3}  
iterator = iter(my_dict.items())
while True:
try:
key, value = next(iterator)
print(f"Key: {key}, Value: {value}")
except StopIteration:
break
  1. 使用 itertools 模块

itertools 模块提供了一些用于迭代的函数,可以与字典结合使用。

1
2
3
4
5
import itertools  

my_dict = {'a': 1, 'b': 2, 'c': 3}
for key, value in itertools.chain(my_dict.items()):
print(f"Key: {key}, Value: {value}")
  1. 使用 defaultdictcollections 模块

collections.defaultdict 是一个字典的子类,允许在访问不存在的键时提供默认值。可以用它来处理遍历中的特殊情况。

1
2
3
4
5
from collections import defaultdict  

my_dict = defaultdict(int, {'a': 1, 'b': 2})
for key in my_dict:
print(f"Key: {key}, Value: {my_dict[key]}")

更新字典数据

  • 通过key值修改

    1
    2
    api = {"url": "/api/user/login": data: {"username": "张三", "password": "123456"}}
    api['data']['username'] = "李四"
  • 通过update() 方法修改

    1
    2
    api = {"url": "/api/user/login": data: {"username": "张三", "password": "123456"}}
    api['data'].update({"username": "李四"})

哈希与可哈希元素

  1. 哈希是通过计算得到元素的存储地址(映射), 这就要求不同长度的元素都能计算出地址,相同元素每次计算出的地址都一样, 不同元素计算的地址必须唯一, 基于哈希的查找永远只需要一步操作, 计算一下得到元素相应的地址, 不需要向序列那样遍历, 所以性能较好
  2. 可哈希元素: 为了保证每次计算出的地址相同, 要求元素长度是固定的, 如数字/字符串/只包含数字,字符串的元组, 这些都是可哈希元素

python3 字典视图对象

在Python 3中,字典(Dictionary)、集合(Set)和映射(Mapping)对象都具有视图对象(View Objects)。这些视图对象提供了对原始数据结构的动态“视图”,可以在不复制原始数据的情况下查看和操作数据。Python 3中常见的视图对象有三种:

  1. dict_keys:包含字典的所有键的视图对象。它代表了字典的键集合,支持集合操作(比如并集、交集、差集等)和迭代操作。
1
2
3
d = {'name': 'Alice', 'age': 30, 'city': 'New York'}  
keys_view = d.keys()
print(keys_view) # dict_keys(['name', 'age', 'city'])
  1. dict_values:包含字典的所有值的视图对象。它代表了字典的值集合,支持集合操作和迭代操作。
1
2
values_view = d.values()  
print(values_view) # dict_values(['Alice', 30, 'New York'])
  1. dict_items:包含字典的键值对(项)的视图对象。它代表了字典的键值对集合,支持迭代操作。
1
2
items_view = d.items()  
print(items_view) # dict_items([('name', 'Alice'), ('age', 30), ('city', 'New York')])

使用这些视图对象进行检索、遍历、过滤和集合操作,而不必复制字典中的数据。视图对象会实时反映原始数据结构的更改,因此在对视图对象进行操作时会直接影响原始数据。这种动态“视图”能够提高代码的效率和减少内存消耗。

文件路径读取

  1. 获取当前工作目录

    1
    2
    import os  
    current_dir = os.getcwd()
  2. 获取脚本文件所在目录

    1
    2
    import os  
    script_dir = os.path.dirname(os.path.abspath(__file__))
  3. 拼接文件路径

    1
    2
    import os  
    file_path = os.path.join(script_dir, 'file.txt')
  4. 获取用户主目录

    1
    2
    from pathlib import Path  
    user_home = Path.home()
  5. 获取环境变量中的路径

    1
    2
    import os  
    path_from_env = os.getenv('MY_PATH', '/default/path')
  6. 获取模块的文件路径

    1
    2
    import mymodule  
    module_path = os.path.abspath(mymodule.__file__)
  7. 通过 pathlib 获取文件路径

    1
    2
    3
    from pathlib import Path  
    p = Path('file.txt')
    absolute_path = p.resolve()

isinstance和type的区别

isinstancetype是Python中用于检查对象类型的两个函数,它们有以下区别:

  1. type(object)函数返回对象的类型,通常是对象所属类的类型。例如,type(5)将返回<class 'int'>,表示整数类型。type函数还可以接受一个参数,即要检查的对象,返回该对象的类型。
  2. isinstance(object, classinfo)函数用于检查对象是否是指定类(或其子类)的实例。isinstance函数接受两个参数,第一个参数是要检查的对象,第二个参数是要检查是否属于的类或类型。如果对象是指定类的实例或子类的实例,isinstance将返回True,否则返回False。例如,isinstance(5, int)将返回True,表示整数5是整数类的一个实例。

关键区别:

  • type函数返回对象的类型,而isinstance函数用于检查对象是否是指定类的实例。
  • type函数返回的是对象的确切类型,而isinstance函数可以检查对象是否是指定类的实例或其子类的实例。

分支及循环

分支

if条件判断

if …

如果条件满足,才执行其中语句,例如

1
2
3
4
x = 1
if x>0:
print("正数")
print('结束')
if … else …

即,如果条件满足,执行某些语句,否则执行另一些语句,例如

1
2
3
4
5
6
x = 1
if x>0:
print("正数")
else:
print("不是正数")
print('结束')
f… elif … else …

即,如果满足某条件,执行某些语句,否则如果满足另一条件,执行该条件语句,如果都不满足所列条件则执行其他语句。

1
2
3
4
5
6
7
8
x = 1
if x>0:
print("正数")
elif x=0:
print("0")
else:
print("负数")
print('结束')

真假值

​ 在Python中False、None,数字0,字符串’0’,以及空字符串’’,空列表[]、空字典{},空元祖(,),空集合set(),都被失望假值;否则被视为真。比如可以直接使用if list判断列表为空:

1
2
3
list = []
if list: # 也可以使用if len(list)>0:
print('列表不为空')

三元表达式

​ 三元表达式即 当条件满足时 变量 为一个值,条件不满足时,变量为另一个值,例如:

1
2
3
4
5
6
7
max = a if a > b else b
'''
当 a>b 为true时
max = a
若 a>b 为false
max = b
'''

逻辑符代替if判断

在Python语句中and和or也可以用于逻辑判断。

  • c = a and b:如果a为假,则c的值为a,否则c的值为b
  • c = a or b:想反,如果a为假,c的值为b,否则c的值为a

特别是or语句,常用来确保变量的值非空并赋于默认值,例如:

1
2
3
d = {'a': 1}
c = d.get('c') or '默认值'
# 如果d.get('c')的结果为None,0,空字符串等假值,c被赋予默认值

循环

Python中的循环有for循环和while循环两种

for循环

for循环(也称遍历),是从一个含有多个变量的数据集合中,依次取出每一项,进行操作。

for循环可以对字符串、列表、元祖、集合、字典及生成器、迭代器、文件指针等可以迭代的对象进行遍历输出。

  • 遍历获取一组顺序的数字

    1
    2
    for i in range(10):  # range(10)即生成0-9的10个数字,不包括10
    print(i)
  • 遍历字符串

    1
    2
    3
    4
    str_var = 'hello,world'

    for i in str_var: # i是代表所遍历对象的每一项的一个变量,可以是任意变量名
    print(i)
  • 遍历列表

    1
    2
    3
    4
    list_var = ['a', 'b', 'c', 'd']

    for i in list_var: # i是代表所遍历对象的每一项的一个变量,可以是任意变量名
    print(i)

    在遍历时如果想连同该项的索引一起输出,可以使用enumerate实现

    1
    2
    3
    4
    list_var = ['a', 'b', 'c', 'd']

    for index, item in enumerate(list_var): # index代表索引,item代表每一项的值
    print(index, item)
  • 遍历字典

    1
    2
    3
    4
    dict_var ={'a': 1, 'b': 2, 'c': 3}
    # 遍历字典时,需要使用字典的items()方法,得到每一项key,value的组合
    for key, value in dict_var.items(): # i是代表所遍历对象的每一项的一个变量,可以是任意变量名
    print(key, value)

while 循环

​ while循环用于当指定条件满足时,循环执行某些语句,直到条件不满足。while循环中为了避免死循环,一般循环中要有使循环趋于结束的语句,

1
2
3
4
i = 0
while i < 10: # 当i<10时,循环执行下面语句
print(i)
i += 1 # i每次自己增加1,等i>=10时,循环自动结束

break和continue

break用于结束当前循环,continue用于结束本次循环,直接开始下次循环

1
2
3
4
5
6
7
8
9
10
11
# break    当有多层循环嵌套时,break每次只能跳出一层循环
for in range(10):
if i > 5: # 如果大于5,结束循环
break
print(i)

# continue
for in range(10):
if i % 2 == 0: # 如果是偶数,跳过本次循环
continue
print(i)

循环中的else

​ 循环结束有break结束和全部循环完结束两种,为了判断是哪种结束方式,可以使用else。当非break结束时执行else。

由于实际不会break,因此运行结果会打印’循环完毕’,while…else的使用和for…else类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for in range(10):
if i > 5: # 如果大于5,结束循环
break
print(i)
else: # 非break结束时执行
print('非break跳出,循环全部执行完毕')


while i < 10:
if i > 5:
break
print(i)
i += 1
else:
print('非break跳出,循环全部执行完毕')

函数

Python中的函数可以理解为一种预先设定的处理过程。一般过程都会包含输入、处理、和输出三个部分。

  • 输入,即函数参数,可以有多个参数;
  • 处理:函数内部的处理过程,可以调用其他函数及模块;
  • 输出:即返回值,也有可以返回多个。

数学中的函数指一种映射的变换关系,如f(x)=2x+1,转换为Python函数为:

1
2
def f(x):
return x*2 + 1

函数定义和调用

函数分为函数定义和函数调用两部分。

  • 函数定义即设计函数,是对参数、处理过程和返回值的描述。
  • 函数调用及使用函数,是使用实际的数据,运行函数并得到实际的返回值。

定义函数

定义一个函数使用def关键字,格式如下。

1
2
def 函数名(参数1,参数2, ...):
处理过程

调用函数

定义的函数需要调用才能执行,调用是按定义的格式传入和参数对应的实际数据,调用方式如下

1
函数名(数据1,数据2,...)

如果需要获取函数的返回结果,可以使用将函数调用复制给变量

1
变量 = 函数名(数据1,数据2,...) 

函数参数

参数是指使用函数时需要提供的信息,如一个登录函数add(a, b),需要提供加数和被加数,才能进行运算。

1
2
3
def add(x, y): # 定义函数
s = x+y # 处理过程
return s # 返回结果

形参和实参

函数分为定义和调用,在定义函数时的参数称为形式参数,如def add(x, y): ...,这里的xy便是形式参数,形式参数是函数内部使用的。
在调用函数时需要传入实际的数据,如add(3,5),这里的35便是实际参数。实际参数也可以是预先定义好的变量

1
2
3
4
5
6
7
def add(x, y): 
# 这里的 x,y 便是形参,函数外部不能调用
s = x+y
return s

a, b = 3, 5 # 实参
add(a, b)

参数类型注解

Python3.5版本以后提供了类型注解

1
2
3
4
5
6
7
def add(x: int, y: int) -> int:   # 说明x,y应为整型 -> 返回值也会整数
s = x+y
return s
# 类型注释也可以指定多种类型,同时也可以注释函数返回值类型。
def add(x: (int, float), y: (int, float)) -> (int, float): # ->指返回值类型
s = x+y
return s

参数默认值-必选和可选参数

1
2
3
4
5
6
def add(x, y=1): # x 为必传参数, y 为可选传参数,不传默认值为1; 提供默认值的参数必须写到后面
pass

# 如果加上类型注解,写法为
def add(x: (int,float), y: (int, float) = 1):
pass

位置参数和关键字参数

参数在定义时只有必选和可选之分,可选的参数需要写到后面。
但是在函数调用时就有如下两种传参方式

1
2
3
4
5
add(3,5)
add(y=3,x=5) # 加了y= 就会将3传值给y形数

def add(x, y):
pass

不定参数

​ 当一个函数使用方式不确定,需要设计其支持任意多个、任意方式(位置/关键字形式)传入时。可以使用*args**kwargs
args即参数(复数):arguments的缩写,kwargs即关键词参数(复数):keyword arguments的缩写

1
2
3
4
5
6
7
8
9
10
11
12
def add(*args, **kwargs):
s = 0
for num in args: # args得到一个元祖类型,没有位置参数时为空元祖
s += num
for num in kwargs.values(): # kwargs得到一个字典类型,无关键词参数时为空字典
s += num
return s

add() # args 为 空元祖 () kwargs 为空字典 {} 结果为 0
add(3, 5) # args 为 元祖 (3,5) kwargs 为空字典 {} 结果为 8
add(x=3, y=5) # args 为 空元祖 () kwargs 为空字典 {'x': 3, 'y': 5} 结果为 8
add(3, 5, z=6) # 此时 args为(3,5) kwargs为 {'z': 6}, 得到 14

限定关键字参数

函数中可以使用*参数,其后的参数在使用时只能按key=value形式使用

1
2
3
4
5
def add(*, a, b):
return a + b

print(add(1, 2)) # 报错
print(add(a=1,b=2))

限定位置参数

函数中可以使用/参数,其前的参数在使用时只能按位置参数形式使用

1
2
3
4
5
def add(a, b, /):
return a + b

print(add(a=1,b=2)) # 报错
print(add(1, 2))

函数中也可以使用不定参 def add(*args): ... 来仅允许使用位置参数,使用起来不如上述形式方便,例如

1
2
3
4
5
6
def add(*args):
a, b = args
print(a + b)

add(a=1, b=2) # 报错
add(1,2)

函数返回值

函数中使用关键return返回结果,return操作后,函数结束(后面的语句不会再执行)

如果想函数每次调用都返回一个值后并不终止,而是暂停等待下次调用,可以使用yield代替return,这样便得到一个生成器函数。

变量的作用域

函数内部的变量被称为局部变量,局部变量是私有变量,一般情况下,一个函数无法访问其他函数的局部变量。

如果需要在一个函数中声明一个变量,让所有函数都可以使用,可以使用global关键字声明其为全局变量。

1
2
3
4
5
6
7
8
def add(x, y):
global z
z = 3
return x + y + z

def sub(x, y):
return x - y -z # 可以使用全局变量z
# 由于函数都可以访问和改变全局变量,这会导致全局变量的值不可预测,因此需要谨慎使用全局变量。
1
2
3
4
5
6
7
8
9
10
a = 3   # 模块中的全局变量

def add():
a = a + 3 # 将全局变量 进行加3 赋值给局部变量
print(a)

def addGlobal():
global a
a = a + 3
print(a)

匿名函数

1
2
lambda 参数: 返回值
add = lambda x,y: x+y

高阶函数

函数用于显示一个函数的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def add(x,y):
"""加法函数"""
return x + y

# 定义一个函数描述的方法
def info(func):
# 使用魔术方法获取func方法的信息并打印
print('函数名称:', func.__name__) # __name__: 可以获取函数的方法名
print('函数描述:', func.__doc__)# __doc__: 可以获取到函数的解析注释
# 通常情况下,我们不会直接使用 func.__name__ 来获取函数或者方法的名称,通常会使用内置函数 getattr() 来动态地获取对象属性。
function_name = getattr(my_function, '__name__')
method_name = getattr(obj.my_method, '__doc__')
print('函数名称:', function_name)
print('函数描述:', method_name)



info(add)
'''
函数名称: add
函数描述: 加法函数
'''

装饰器

装饰器也是一种典型的以函数为参数的函数,装饰器旨在通过包装,在不改变原函数的情况下,为函数来增加功能。

1
2
3
4
5
# 直接使用@info为add函数加上装饰器
@info # @info 装饰器 - 在调用函数add时会自动打印相应的函数信息。
def add(x,y):
"""加法函数"""
return x + y
装饰器的执行流程
  1. 定义装饰器函数:首先需要定义一个装饰器函数。这个函数将接收一个被装饰的目标函数作为参数,并返回一个新的内嵌(包裹)了目标函数功能的封闭(wrapper)函数。
  2. 应用装饰器:使用 @ 符号将定义好的装饰器应用到目标函数上。这相当于将目标函数作为参数传递给了装饰器,并重新赋值给了原来的名称。
  3. 装饰过程:当调用被修饰后的原始目标函时数时,实际上调用并执行了封闭(wrapper) 函数。这个封闭函(wrapper)数接收到相同参数并执行了额外操作或者修改后再调用内部保存着目标函数引用 的实际逻辑代码。
1
2
3
4
5
6
7
8
9
10
11
12
def uppercase_decorator(func):
def wrapper():
result = func() # 获取原函数的返回值
return result.upper() # 将原函数的返回值进行大写

return wrapper

@uppercase_decorator
def greet():
return "hello world"

print(greet()) # 输出: HELLO WORLD

常用高阶函数

map, filter和reduce是Python中常用的3个高阶函数

  • map()
    map用于使用一个函数,对一个序列进行批量操作,示例如下

    1
    2
    3
    4
    add1 = lambda x: x + 1   # 处理函数,由于一次处理一个,所有只能有一个参数
    data = [1, 3, 5, 7, 9]
    new_data = list(map(add1, data) # map(add1, data) 实际上是一个生成器,不会自动执行,必须遍历或者转成列表才会执行
    print(new_data) # 得到列表 [2, 4, 8 , 10]
  • filter()
    filter使用一个函数来过滤数据
    当某个数据传入函数时返回非”假”值(Python中False,None,0, ‘0’, ‘’,[], {}, (,)都被视为假),则保留。否则抛弃。示例如下

    1
    2
    3
    4
    is_even = lambda x: x % 2 == 0   #  过滤函数,x是偶数是 x对2取模==0,返回True,奇数时不等于0,返回False。
    data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    new_data = list(filter(is_even, data)) # filter同样是一个生成器,需要转列表才会执行。
    print(new_data) # 得到列表 [2, 4, 6, 8]
  • reduce()
    reduce使用一个函数,对序列进行累积操作,不同的是,这个函数接受两个参数,操作完将结果作为下一轮的第一个参数,再读入下一个参数。
    如累加[1, 2, 3, 4, 5,6 ,7, 8, 9, 10],先传入add(1,2)得到和3,第二轮结果3作为第一个参数,再传入下一个参数3得到add(3,3)结果6,下一轮则为add(6,4)…

    1
    2
    3
    4
    5
    6
    from fuctools import reduce  # 不同于map/filter,reduce需要导入方可使用

    add = lambda x,y: x + y
    data = [1, 2, 3, 4, 5,6 ,7, 8, 9, 10]
    result = reduce(add, data) # reduce调用即执行,返回序列操作完的最后结果
    print(result) # 得到 55
  • sorted()

    Python内置的sorted()函数就可以对list进行排序

    1
    2
    >>> sorted([36, 5, -12, 9, -21])
    [-21, -12, 5, 9, 36]

    它还可以接收一个key函数来实现自定义的排序
    绝对值大小排序 - key = abs

    1
    2
    >>> sorted([36, 5, -12, 9, -21], key=abs)
    [5, 9, -12, -21, 36]

    忽略大小写 - key = str.lower

    1
    2
    >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
    ['about', 'bob', 'Credit', 'Zoo']

    进行反向排序 - reverse=True

    1
    2
    >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
    ['Zoo', 'Credit', 'bob', 'about']

函数嵌套 和闭包

函数嵌套

​ 函数嵌套是指在一个函数内部定义了另一个函数。这种嵌套的函数被称为内部函数,而包含它的外部函数被称为外部函数

嵌套的内部函数可以访问外部函数的参数,但是外部函数无法访问内部函数的参数。
如果想在内部函数内声明一个具有外部函数范围的参数可以使用nolocal关键字声明其为自由变量

优点:内部函数可以使用外部函数中的参数 (当内部函数使用了外部函数的参数时,就形成了闭包。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def outer_function():
def inner_function():
print("This is inner function")

print("This is outer function")
inner_function()
outer_function()

# 当嵌套函数传入的参数是函数,则会变成装饰器; 这样便得到一个参数类型,检查装饰器@check
def check(add): # 外部函数,接受一个add函数

def new_add(x, y): # 内部函数
if not isinstance(x, int) or not isinstance(y, int): # 参数类型校验
raise TypeError('x,y两个参数必须是整数类型')
result = add(x,y) # 可以使用外部函数参数add并得到结果
# return result

return new_add # 返回替换后的new_add函数,具有函数add的功能,还加了参数检查功能
闭包

​ 闭包是指在一个内层函数中引用了其外层(封闭)作用域中变量的情况。这样的情况下,如果返回了内层函数,则该内层函数将继续保持对其封闭作用域中变量的引用,即使这些变量已经超出了其正常生命周期或者超出了原本作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def counter():
count = 0

def increment():
nonlocal count
count += 1
return count

return increment

# 创建两个计数器实例
counter1 = counter()
counter2 = counter()

print(counter1()) # 输出: 1
print(counter1()) # 输出: 2

print(counter2()) # 输出: 1
print(counter2()) # 输出: 2

# 注意:每个计数器实例都有自己独立的状态和计数值。
总结:
  • 函数嵌套是指在一个函数里定义另一个子级或者局部级别别名。
  • 当子级或者局势级别师徒能够访问父级别名称空间时就形成了闭包关系。
  • 这种情况下,如果返回了子级或者局势级别师徒则他将会继续保存父亲名称空间数据区块信息

递归函数和尾递归

递归函数
  1. 必须有出口条件,如最后一层1,有明确的结果1。
  2. 每层只负责乘以本层数字,调用自己推给下一层
  3. 整个推导过程要逐步趋于出口

递归函数是指在函数的定义中调用函数本身的过程。递归函数通常用于解决可以被分解为相同问题的子问题的情况,每次递归调用都会将问题规模缩小,直到达到基本情况(递归终止条件)。

1
2
3
4
5
6
7
8
9
10
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1) # 自己调用自己
'''
factorial() 函数通过递归调用自身来计算阶乘。当 n 的值为 0 时,递归终止,返回 1。否则,函数会将问题规模缩小,将 n 乘以 factorial(n-1) 的结果。
'''

print(factorial(5)) # 输出: 120
尾递归

尾递归是一种特殊的递归形式,指的是递归函数的最后一个操作是递归调用本身。在尾递归中,递归调用是函数的最后一步操作,不会再有其他操作。

尾递归函数可以通过优化来避免栈溢出的问题,因为它们可以被转换为循环的形式,不会在每次递归调用时创建新的栈帧。

1
2
3
4
5
6
7
8
9
10
11
# 计算斐波那契数列的尾递归函数的示例
def fibonacci(n, a=0, b=1):
if n == 0:
return a
else:
return fibonacci(n-1, b, a+b)

'''
fibonacci() 函数使用尾递归的方式计算斐波那契数列。每次递归调用时,更新参数 a 和 b 的值,并将 n 的值减少,直到达到递归终止条件。
'''
print(fibonacci(6)) # 输出: 8
总结:
  • 递归函数是指在函数的定义中调用函数本身的过程,用于解决可以被分解为相同问题的子问题的情况。
  • 尾递归是一种特殊的递归形式,指的是递归函数的最后一个操作是递归调用本身。
  • 尾递归函数可以通过优化来避免栈溢出的问题,因为它们可以被转换为循环的形式,不会在每次递归调用时创建新的栈帧。

类与对象

类(Class)和对象(Object),也称作实例(Instance)是面向对象编程(OOP)中的重要概念。类的主要作用如下:

  • 在同一模块中,对多个函数进行分组,并共享其中的变量;
  • 按动作主体归类函数动作,使得逻辑更清晰。

面向过程及面向对象

面向过和面向对象是两种编程风格。

  • 面向过程:主要考虑功能的实现步骤和过程,即怎么去实现,多使用函数相互组合调用实现。
  • 面向对象:主要考虑动作的主体和相互关系,即谁去实现,怎么配合,使用类的继承或组合实现。

面向过程的实现逻辑如下:

  • 拆分过程
  • 定义函数实现每个过程(过程可以包含子过程及相互调用)
  • 在主函数中组合调用各个过程函数,完成整个流程。

面向对象实现逻辑如下:

  • 根据动作主体进行建模,即需要几种对象(角色),每个对象需要哪些属性和方法
  • 设计各个对象需要的类
  • 在主流程中将每个类生成对象,组合对象完成整个流程操作。

面相对象与面相过程

类与对象(实例)的关系

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;

对象初始化方法

Python类中拥有很多魔术方法,起不同的作用,其中__init__(self)方法称作对象初始化方法,在调用类创建对象时自动调用,通常作用如下:

  • 将创建类传人的参数,绑定到对象属性
  • 做一些对象初始化操作
1
2
3
4
5
6
7
8
9
class Student:
def __init__(self, name, age):
print('我是一个学生')
self.name = name
self.age = age

# 创建对象时将自动将参数传递给对象,并打印初始化信息
lilei = Student('李磊', 18) # 创建对象
print(lilei.name, lilei.age)

类属性及实例属性

类中的属性称为类属性 ( 类共享),绑定self的属性称为实例属性 (实例独有),同时实例自动继承类属性

  • 类属性:可以使用类名或对象访问
  • 对象属性:一般使用对象进行访问
1
2
3
4
5
6
7
8
9
10
11
12
13
class Student:
role = '学生' # 类属性

def __init__(self, name, age):
self.name = name # 对象属性
self.age = age # 对象属性


lilei = Student('李磊', 18) # 生成对象
print(lilei.role) # 使用对象引用类属性
print(lilei.name, lilei.age) # 使用对象引用对象属性

print(Student.role) # 使用类名引用类属性

访问限制 - 私有变量

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score

def print_score(self):
print('%s: %s' % (self.__name, self.__score))

def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name


def get_score(self):
return self.__score
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')

类方法、实例方法及静态方法

类和实例是两种不同的范畴,因此在类中可以实例方法,也可以有类方法,如果方法根类和实例都没有关系,则可以设置成静态方法

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
from datetime import datetime

class Student:
role = '学生' # 类属性

def __init__(self, name, age):
self.name = name # 对象属性
self.age = age # 对象属性

def get_name(self): # 实例方法,self代表实例本身
return self.name

@classmethod
def get_class_role(cls): # 类方法,cls代表当前类名
return cls.role

@staticmethod
def get_datetime(): # 静态方法,与类和对象都无关 (无需访问类/对象属性或调用其方法)
return datetime.now().strftime('%Y%m%d %H:%M:%D')

# 使用对象调用
lilei = Student('李磊', 18) # 生成对象
print(lilei.get_name()) # 对象调用实例方法
print(lilei.get_class_role()) # 对象调用类方法
print(lilei.get_datetime()) # 对象调用静态方法
'''
静态方法和静态类属性是所有类和实例共享的。它们属于类本身,而不是类的实例。因此,无论是通过类还是实例访问和修改静态方法或静态类属性,都会影响到所有的类和实例。
'''
# 使用类名调用,注意类名后不加括号(加括号是调用类并生成对象,即实际为对象)
print(Student.get_class_role()) # 类名调用类方法
print(Student.get_datetime()) # 类名调用静态方法

# 类也可以调用对象实例方法,但是需要传入一个对象本身,将self指向该对象
print(Student.get_name(lilei)) # 类名调用对象方法,需要一个对象

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal(object):
def run(self):
print('Animal is running...')

# 继承可以让子类获得了父类的全部功能
class Dog(Animal): # 继承Animal
pass

class Cat(Animal): # 继承Animal
'''
重写是针对继承关系中的父类和子类,子类通过重写方法来改变继承自父类的方法的行为。
重载是在同一个类中定义多个具有相同名称但参数列表不同的方法,通过参数的不同来区分方法的调用。
'''
def run(self): # 重写run方法
print('Animal-Cat is running...')

获取对象属性

  • dir()

    获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list

    1
    2
    >>> dir('ABC')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
  • __len__() or len()

    len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> len('ABC')
    3
    >>> 'ABC'.__len__()
    3

    # 给自己的类写一个__len__()方法
    >>> class MyDog(object):
    ... def __len__(self):
    ... return 100
    ...
    >>> dog = MyDog()
    >>> len(dog)
    100
  • hasattr()

    判断某个对象中是否有该属性

    1
    2
    >>> hasattr(obj1, 'x') # 有属性'x'吗?
    True
  • getattr()

    获取某个对象中的属性;当前属性不存在时会报错,建议结合hasattr() 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if hasattr(obj1, 'x'):
    getattr(obj1, 'x')
    # or
    # 通过第三个参数给默认值,当属性不存在时,返回默认值
    getattr(obj1, 'x', '属性不存在')

    # 通过getattr的返回值,把地址赋给fn变量
    fn = getattr(obj1, 'x', '属性不存在')
    # 此时fn() 等同于 obj1.x()
  • setattr()

    给某个对象的属性设置新的值

  • MethodType()

    给对象实例绑定方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from types import MethodType

    def set_age(self, age): # 定义一个函数作为实例方法
    self.age = age
    s = Student() # 创建实例
    # MethodType函数的第一个参数是方法,第二个参数是要绑定该方法的实例。
    s.set_age = MethodType(set_age, s) # 给实例绑定一个方法

    # 扩展:给class绑定方法,所有实例均可调用
    Student.set_age = set_age

模块与包

模块

一个.py脚本就是一个模块

当程序较为复杂时,我们可以将不同类型的功能拆分成不同的模块,每个模块建立一个.py脚本。这样做的好处是:

  • 不同模块负责不同部分的内容,逻辑更清晰;
  • 公共模块部分可以复用;
  • 模块拥有独立的命名空间(不同模块中可以拥有同名的变量/函数/类型)。

模块导入

一个模块可以导入其他模块,;但要避免循环导入,导入的名不能与当前模块重名

  • import moudle

    import time,此时模块名被导入到当前命名空间,使用模块名引用其中的函数,如time.sleep(1)

  • from moudle import …

    如果要最小导入到模块,不能直接使用import 导入模块中的变量/函数/类,如import time.sleep;要使用from .. import ...语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 分开导入
    from time import localtime as lt # 如果一个模块的函数名太长,可以使用as别名,在该模块下可以使用别名来使用该函数 localtime() 等同于 lt()
    from time import sleep

    # 一起导入
    from time import localtime, sleep

    # 一起导入
    from time import (localtime, sleep)
  • from moudle import *

    如,from time import *,将导入time模块所有,非下划线_开头的变量/函数/类。

    1
    2
    3
    4
    5
    6
    7
    # aaa.py
    __all__ = ['b', 'c'] # 设置from b import * 时只导入b和c变量

    b = 1
    _b = 2 # 下划线开头到变量视为私有变量,from b import * 时不会被导入
    c = 3
    d = 4 # __all__ 设置了不会导入d变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # bbb.py
    from b import *

    c = 4 # 将覆盖b中导入的c
    print(b)
    print(c)
    # print(d) # 由于没有导入d,因此打印d会报错
    # print(_b) # 由于没有导入d,因此打印d会报错
    '''
    如果想使用b模块的私有变量_b,可以使用import b; print(b._b),或 from b import _b; print(_b)等方式

    这种方式是不被推荐的,原因如下:
    不知道都导入了哪些变量/模块/类;
    把其他模块的变量全部导入到当前命名空间,可能导致名称覆盖(重复),及来源不清楚。
    '''

模块导入常见问题

主模块和导入模块

模块分为主模块和导入模块,当前执行模块被称为主模块,其他被导入的模块称为导入模块。可以使用Python魔术变量__name__(注意不加引号),来查看当前模块是否主模块。
如果当前模块为主模块时,模块名__name__为字符串'__main__';因此在模块中常用if __name__ == '__main__'来判断当前模块是不是主模块(即是不是以当前脚本为开始运行的)

1
2
3
4
print('b模块,当前模块名', __name__)

if __name__ == '__main__':
print('模块私有代码,只有b模块自己运行才会输出,别的模块导入不会输出')
间接导入问题

间接导入是指a模块导入b模块,b模块导入c模块,运行a模块时c模块被间接导入

根本原因是:相对路径问题

例如:

1
2
3
4
5
6
7
8
9
假如目录结构为:
project/
package1/
__init__.py
ccc.py
bbb.py
package2/
__init__.py
aaa.py

aaa.py 导入了bbb.py ,但是 bbb.py 里面已经导入了ccc.py
此时,运行a.py,间接导入c模块时便会出现异常

1
2
3
    ...
import ccc
ModuleNotFoundError: No module named 'ccc'

原因为,运行a.py时,间接导入import c时,只会在a.py所在目录(当前运行目录是package2, 在bbb.py 中导入的路径是package1,导致找不到模块ccc.py)及PYTHONPATH、三方包site-packages中查找。而不会切换目录到b.py所在目录进行查找。

需要为当前python模块添加package1添加环境变量

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

# 获取当前脚本文件的路径
current_file_path = os.path.abspath(__file__)

# 获取当前文件所在目录的父目录(即项目根目录)
project_root = os.path.dirname(os.path.dirname(current_file_path))
# 添加到环境变量中
sys.path.append(project_root)

循环导入问题

​ 循环导入指a导入了b模块,b模块又直接或间接导入了a模块

  • 使用import 模块 导入时,允许循环导入,模块只在第一次导入时及作为主模块时运行
  • 使用from ... import ...时不允许循环导入

处理方法有以下几种:

  • 使用import 模块方式导入
  • from ... import ...改到函数内部(延迟执行)
  • 使用包__init__.py统一规划导入方式
导入立即执行及调用(延迟)执行

在导入模块时,有些是立即执行,有些时调用时才执行的

第一次导入模块时立即执行的有:

  • 直接写模块中的语句
  • 装饰器
  • 类变量

调用时才执行的有:

  • 定义的函数
  • 定义的类及方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def deco(func):   # 装饰器函数
print('调用 %s' % func.__name__)
return func

@deco # 装饰器调用,第一次导入模块时立即执行
def add(x, y): # 函数定义,调用函数时执行
print('%s+%s' % (x, y))
return x + y

s1 = add(1, 2) # 模块语句,第一次导入模块时立即执行

class Calc: # 类定义,调用类(生成对象时) 执行__init__方法
s2 = add(3, 4) # 类属性,第一次导入模块时立即执行
def __init__(self):
print('对象初始化')

def sub(self, x, y): # 对象方法,调用时执行
print('%s-%s' % (x, y))
return x - y

包 package

包Package

当拥有多个模块时,我们可以使用包来组织同一类别或层次的模块。在Python
在Python中,一个目录被视为包,包中可以包含其他子包(子目录),包中不强制必须有__init__.py文件。
包中可以包含__init__.py文件作为包的初始化配置,__init__.py可以为空。
在Python3中,目录中是否包含__init__.py机会没有区别,我们可以使用Python魔术变量__pacakge__查看当前模块所在包路径。

ython中常用的魔术变量如下:

  • __file__:表示当前脚本路径
  • __name__: 表示当前模块导入路径,作为主模块时(当前运行脚本),其值为'__main__',否则为其模块导入路径
  • __package__: 当前包导入路径,作为主模块时(当前运行脚本),其值为None,否则为其模块所在包导入路径
  • __buildins__: 当前所有可用内置变量/函数组成的字典

导入包及模块

同模块导入一样,包的导入也支持import ...from ... import ...两种导入方式

导入包或子包

在main.py中,可以使用如下语句导入包或子包

  • import package
  • import package.sub_package

此时实际导入的是package/__init__.py,及package/sub_package/__init__.py

包标识 - __init__.py 的使用

__init__.py是Python包中的特殊文件,用于指示该目录是一个Python包,并在包导入时执行一些初始化操作。该文件通常为空,但也可以包含一些初始化代码。

__init__.py文件的作用是定义包的初始化行为,管理包的导入机制,并提供一些包级别的设置和操作。要注意,在Python 3.3及更高版本中,__init__.py文件是可选的,不过为了保持向后兼容性和明确包的边界,建议在包目录下都添加__init__.py文件。

以下是__init__.py的一些用法:

  1. 声明包中的模块:在__init__.py中可以通过导入其他模块或子包来声明该包中包含的模块,使得外部用户可以直接通过包名访问这些模块。例如:

    1
    2
    3
    4
    # package/__init__.py 
    # 将该包下的模块的方法,导入__init__.py
    from .module1 import *
    from .module2 import *
    1
    2
    3
    4
    import mypackage  
    # 这样我们就可以直接导入包,不需要详细到某个模块内的方法
    mypackage.module1.my_function()
    obj = mypackage.module2.MyClass()
  2. 执行初始化代码:在__init__.py中可以执行一些初始化操作,例如设置全局变量、加载配置、注册插件等。这些操作会在包被导入时执行一次。例如:

    1
    2
    3
    # __init__.py  
    print("Initializing mypackage...")
    CONFIG = {'debug': True, 'name': 'mypackage'}
  3. 控制包的导入行为:通过在__init__.py中定义__all__变量,可以控制包被导入时哪些模块会被导入。例如:

    1
    2
    3
    4
    5
    # __init__.py  
    '''
    __all__变量:在__init__.py中,可以使用__all__来控制使用from package import *时导入的模块和变量。只有__all__中声明的模块和变量才会被导入。
    '''
    __all__ = ['module1', 'module2']
  4. 实现”lazy loading”:在__init__.py中可以根据需要延迟加载模块,这样可以节省一些资源并加快导入速度。例如:

    1
    2
    3
    4
    # __init__.py  
    def lazy_load_module():
    from .module import my_function
    return my_function

文件操作

纯文本文件读取

  • 读取全部内容

    1
    2
    3
    4
    f = open('demo.txt', 'r', encoding='utf-8')
    data = f.read() # 读取文件全部内容
    print(data)
    f.close()
  • 按行读取

    1
    2
    3
    4
    5
    6
    f = open('demo.txt', 'r', encoding='utf-8')
    line = f.readline() # 读取一行(包括结尾的换行符)
    print(line)
    line = f.readline() # 读取下一行(包括结尾的换行符)
    print(line)
    f.close()
  • 结合for循环按行读取文件全部内容

    1
    2
    3
    4
    f = open('demo.txt', 'r', encoding='utf-8')
    for line in f.readlines(): # 也可以直接 for line in f:
    print(line) # 每一行内容包括结尾的换行符
    f.close()

纯文本文件写入

  • 写入整段文本

    1
    2
    3
    4
    5
    6
    data = '''hello
    world
    '''
    f = open('demo2.txt', 'w', encoding='utf-8')
    f.write(data)
    f.close()
  • 写入多行数据
    有一行一行文本组成的列表型数据,可以使用f.writelines()写入

    1
    2
    3
    4
    data = ['hello\n', 'world\n']
    f = open('demo2.txt', 'w', encoding='utf-8')
    f.writelines(data)
    f.close()

使用上下文格式自动关闭文件

1
2
3
4
# 退出with语句(如print时),自动关闭文件。
with open('demo.txt', 'r', encoding='utf-8') as f:
data = f.read()
print(data)

非纯文件读写

二进制格式读写图片等非二进制文件,有一张图片hua.jpg,我们可以读写来复制文件

1
2
3
4
5
6
# 使用二进制读/写模式打开时无需指定编码
with open('hua.jpg', 'rb') as f:
data = f.read()

with open('hua_copy.jpg', 'wb') as f:
f.write(data)

读写模式

标示 文本模式 (默认)。
x 写模式,新建一个文件,如果该文件已存在则会报错。
b 二进制模式。
+ 打开一个文件进行更新(可读可写)。
U 通用换行模式(Python 3 不支持)。
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

其他类型文件读取

数据及配置文件之争

数据及文件通常有三种类型:

  1. 配置文件型:如ini,conf,properties文件,适合存储简单变量和配置项,最多支持两层,不适合存储多层嵌套数据
  2. 表格矩阵型:如csv,excel等,适合于存储大量同类数据,不适合存储层级结构的数据
  3. 多层嵌套型:如XML,HTMl,JSON、YAML,TOML等,适合存储单条或少数多层嵌套数据,不适合存储大量数据

CSV文件

​ CSV(Comma-Separated Values)即逗号分隔值,一种以逗号分隔按行存储的文本文件,所有的值都表现为字符串类型(注意:数字为字符串类型)。
​ 如果CSV中有中文,应以utf-8编码读写,如果要支持Excel查看,应是要用utf-8 with bom格式及utf-8-sig

Python3操作CSV文件使用自带的csv包
  • reader=csv.reader(f, delimiter=’,’):用来读取数据,reader为生成器,每次读取一行,每行数据为列表格式,可以通过delimiter参数指定分隔符
  • writer=csv.writer(f):用来写入数据,按行写入,writer支持writerow(列表)单行写入,和writerows(嵌套列表)批量写入多行,无须手动保存。
  • 当文件中有标题行时,可以使用header=next(reader)先获取到第一行的数据,再进行遍历所有的数据行。
  • 写入时,可以先使用writer.writerow(标题行列表),写入标题行,再使用writer.writerows(多行数据嵌套列表),写入多行数据(也可以逐行写入)。

示例:

1
2
3
4
5
6
7
// 数据文件data.csv
name,password,status
abc,123456,
张五,123#456,
张#abc123,123456,
666,123456,
a b,123456,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# getCsvFile.py  读取
import csv

with open('data.csv', encoding='utf-8') as f:
# 读取数据,reader为生成器,每次读取一行,每行数据为列表格式
reader = csv.reader(f)
header = next(reader)
print(header)
for row in reader:
print(row)
'''
reader必须在文件打开的上下文中使用,否则文件被关闭后reader无法使用
所有的数字被作为字符串,如果要使用数字格式,应使用int()/float()做相应转换
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# setCsvFile.py  写入
import csv

header = ['name', 'password', 'status']

data = [
['abc', '123456', 'PASS'],
['张五', '123#456', 'PASS'],
['张#abc123', '123456', 'PASS'],
['666', '123456', 'PASS'],
['a b', '123456', 'PASS']
]


with open('result.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(data)

'''
打开文件时应指定格式为w, 文本写入,不支持wb,二进制写入,当然,也可以使用a/w+/r+
打开文件时,指定不自动添加新行newline='',否则每写入一行就或多一个空行。
如果想写入的文件Excel打开没有乱码,utf-8可以改为utf-8-sig。
'''
使用字典格式的数据:DictReader, DictWriter

​ !!! 使用此方法时,必须有标题行才能使用

  • reader=csv.DictReader(f):直接将标题和每一列数据组装成有序字典(OrderedDict)格式,无须再单独读取标题行
  • writer=csv.DictWriter(f, 标题行列表):写入时可使用writer.writeheader()写入标题,然后使用writer.writerow(字典格式数据行)或write.writerows(多行数据)
1
2
3
4
5
6
7
# getCsvFile.py
import csv

with open('data.csv', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['password'],row['status'])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# setCsvFile.py
import csv

header = ['name', 'password', 'status']

data = [
{'name':'abc', 'password':'123456', 'status':'PASS'},
{'name':'张五', 'password':'123#456', 'status':'PASS'},
{'name':'张#abc123', 'password':'123456', 'status':'PASS'},
{'name':'666', 'password':'123456', 'status':'PASS'},
{'name':'a b', 'password':'123456', 'status':'PASS'}
]


with open('result2.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, header)
writer.writeheader()
writer.writerows(data)

ini文件 - configparser

​ ini文件即Initialization File初始化文件,在应用程序及框架中常作为配置文件使用,是一种静态纯文本文件,使用记事本即可编辑。
​ 配置文件的主要功能就是存储一批变量和变量值,在ini文件中使用[章(Section)]对变量进行了分组,基本格式如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# filename: config.ini
[user]
name=admin
password=123456
is_admin=true

[mysql]
host=10.10.10.10
port=3306
db=apitest
user=root
password=123456

[log]
file=run.log
level=info

以上文件中,有3个Section段,分别user、mysql和log
ini文件中使用#或者;添加注释,最好独占一行,不能写在变量后面

读取

读取ini配置文件需要使用Python3自带的configparser库,使用示例如下

1
2
3
4
5
6
from configparser import ConfigParser   # Python2中是from ConfigParser import ConfigParser

conf = ConfigParser() # 需要实例化一个ConfigParser对象
conf.read('config.ini') # 需要添加上config.ini的路径,不需要open打开,直接给文件路径就读取,也可以指定encoding='utf-8'
# conf对象每个section段的数据类似于一个字典,可以使用['变量名']或者.get('变量名')获取对应的值,获取到的是字符串格式。
print(conf['user']['name']) # 读取user段的name变量的值,字符串格式

其他常用的读取方法如下:

  • conf.sections(): 获取所有的section名,结果[‘user’, ‘mysql’, ‘log’]
  • conf[‘mysql’][‘port’]: 获取section端port变量的值,字符串格式
  • conf[‘mysql’].get(‘port’): 同上,字符串格式
  • conf.get(‘mysql’, ‘port’): 同上,字符串格式
  • conf[‘mysql’].getint(‘port’): 获取对应变量的整型值
  • conf[‘mysql’].getfloat(‘port’): 获取对应变量的浮点型值
  • conf[‘user’].getboolean(‘is_admin’): 获取对应变量的布尔值,支持配置为yes/no, on/‘off, true/false 和 1/0,都可以转化为Python中的True/False
  • conf.has_section(section):检查是否有该section
  • conf.options(section):输出section中所有的变量名
  • conf.has_option(section, option):检查指定section下是否有该变量值

如果想遍历一个section所有的变量和值,可以像遍历字典意义操作,示例如下

1
2
3
for key, value in conf['mysql'].items():
print(key, value)
# ini文件中的变量名是大小写不敏感的,而Section名是大小写敏感的。
公共变量

假如我们每个Section变量组都有一批相同的重复变量,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[dev]
# 开发环境
user=admin
password=123456
base_url=http://localhost:7777

[test]
# 测试环境
user=admin
password=123456
base_url=http://test.abc.com

[prod]
# 生产环境
user=admin
password=123456
base_url=http://www.abc.com

对应这种,我们可以设置[DEFAULT]段公用变量,公用变量会自动添加到每一个段中,修改后如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[DEFAULT]
user=admin
password=123456

[dev]
# 开发环境
base_url=http://localhost:7777

[test]
# 测试环境
base_url=http://test.abc.com

[prod]
# 生产环境
base_url=http://www.abc.com
参数化

在ini文件中我们还可以使用%(变量名)s的占位符进行参数化,这种特性被称为Interpolation(插值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[DEFAULT]
url = %(base_url)s/get?a=%(a)s&b=%(b)s
base_url=https://httpbin.org

[dev]
# 开发环境
base_url=http://localhost:5555
a=1
b=2

[prod-case1]
# 生成环境-场景1
a=1
b=2

[prod-case2]
# 生成环境-场景2
a=kevin
b=male

上例中,我们在[DEFAULT]段设置了一个参数化的公用变量url,其中埋设了三个占位符,%(base_url)s、%(a)s、和%(b)s。
并且我们设置了base_url变量的默认值为https://httpbin.org。
当下面的section中没有覆盖该变量时,如prod-case1和prod-case2中,是用base_url的默认值。

!!! 每个section段中,加上默认变量base_url,必须提供所有参数化变量的值,比如此例中每个段最少必须设置a和b的值,否则会报错。

在Python脚本中打印conf['prod-case2']['url'],可以得到组装后的url。

1
base_url = conf['prod-case2']['url'] # https://httpbin.org/get?a=kevin&b=male
修改保存
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
from configparser import ConfigParser
from configparser import ConfigParser

conf = ConfigParser()

conf.read('httpbin.ini', encoding='utf-8') # 如果新建的话就不需要read,如果修改则需要使用read打开

# 使用set() 方法修改
conf.set('DEFAULT', 'url', '%(base_url)s/get?a=%(a)s&b=%(b)s') # 可以设置DEFAULT段的值
conf.set('DEFAULT', 'base_url', 'https://httpbin.org') # 可以设置DEFAULT段的值
conf.add_section('dev')
conf.set('dev', 'base_url', 'http://localhost:5555')
conf.set('dev', 'a', '1') # 值必须是字符串
conf.set('dev', 'b', '2')

# 使用下标的方式
conf['prod-case1'] = {'a': 1, 'b': 2} # 直接使用字典添加多个变量
conf['prod-case2'] = {'a': 'kevin', 'b': 'male'}

print(conf.get('dev', 'url'))
print(conf.get('prod-case1', 'url'))
print(conf.get('prod-case2', 'url'))

# 保存csv
with open('httpbin.ini', 'w', encoding='utf-8') as f:
conf.write(f)

JSON文件 - json

JSON(JavaScript Object Notation)即JavaScript对象表示法,一种轻量级,通用的文本数据格式。
JSON语法支持对象(Object),数组(Array),字符串,数字(int/float)以及true/false和null。
JSON拥有严格的格式,主要格式如下:

  • 只能用双引号,不能用单引号
  • 元素之间用逗号隔开,最后一个元素不能有逗号
  • 不支持注释
  • 中文等特殊字符传输时应确保转为ASCII码(\uXXX格式)
  • 支持多层嵌套Object或Array
1
2
3
4
5
6
7
{
"name": "Cactus",
"age": 18,
"skills": ["Python", "Java", "Go", "NodeJS"],
"has_blog": true,
"gf": null
}
JSON与Python数据类型的对应关系
JSON Python
Object 字典
Array 列表
字符串 字符串
数字 数字(int/float)
true/false True/False
null Null
JSON字符串与Python字典的相互转换

!!! 为什么要相互转换,JSON是字符串,方便存储传输,不方便提取值;字典是内存中的数据结构,取值方便,不方便传输和存储

使用Python自带的json包可以完成字典与JSON字符串的相互转换

  • json.dumps(字典):将字典转为JSON字符串
  • json.loads(JSON字符串):将JSON字符串转为字典,如果字符串不是合法的JSON格式,会报JSONDecodeError

字典转JSON字符串

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

dict_var = {
'name': 'Cactus',
'age': 18,
'skills': ['Python', 'Java', 'Go', 'NodeJS'],
'has_blog': True,
'gf': None
}

print(json.dumps(dict_var))
print(json.dumps(dict_var, indent=2,sort_keys=True, ensure_ascii=False))

JSON字符串转字典

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

json_str = '''{
"name": "Cactus",
"age": 18,
"skills": ["Python", "Java", "Go", "NodeJS"],
"has_blog": true,
"gf": null
}'''

print(json.loads(json_str))
JSON文件与字典的相互转换

将字典保存为JSON文件或从JSON文件转为字典

  • json.dump(字典, f):将字典转为JSON文件(句柄)
  • json.loads(f):将打开的JSON文件句柄转为字典

字典转成JSON文件

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

dict_var = {
'name': 'Cactus',
'age': 18,
'skills': ['Python', 'Java', 'Go', 'NodeJS'],
'has_blog': True,
'gf': None
}

with open("demo2.json", "w", encoding='utf-8') as f:
# json.dump(dict_var, f) # 写为一行
json.dump(dict_var, f,indent=2,sort_keys=True, ensure_ascii=False) # 写为多行

JSON文件转成字典

1
2
3
4
5
6
import json

with open("demo2.json", encoding="utf-8") as f:
data = json.load(f)

pritn(data)

!!! 字典转为JSON时,只支持嵌套字典、列表、字符串、数字、True/False/None等,不支持日期对象以及Python的其他对象
!!! 解析复杂嵌套JSON格式,请使用JSONPath

YAML 文件 - yaml

​ YAML兼容JSON格式,简洁,强大,灵活,可以很方便的构造层级数据并快速转为Python中的字典。

yaml简介

​ YAML(YAML Ain’t Markup Language)即一种反标记(XML)语言。强调数据为中心,而非标记。YAML大小写敏感,使用缩进代表层级关系。
​ YAML中支持对象Object(对应Python中的字典), 数组Array(对应Python中的列表)以及常量(字符串、数字(int/float),true/false/null)。
​ 相比于JSON格式,YAML免除了双引号,逗号,大括号,中括号等,(当然也支持原始的JSON格式),并且支持注释,类型转换,跨行,锚点,引用及插入等等。

基本格式
  • 对象:使用key: value表示,冒号后面有一个空格,也可以是使用{key: value}(flow流格式)或{"key": "value"}表示
  • 数组:使用- value表示,**-后面有一个空格**,每项一行,也可以使用[value1,value2,value3,...] (flow流格式)或["value1", "value2", "value3", ...]
  • 字符串:abc"abc"
  • 数字:123123.45
  • true/false:true/false,TRUE/FALSE,True/Falseon/off, ON/OFF, On/Off
  • null: null,NULL, Null~
1
2
3
4
5
6
7
8
9
10
11
12
13
# 注释:示例yaml文件
name: Cactus
age: 18
skills:
-
- Python
- 3
-
- Java
- 5
has_blog: true
gf: ~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 转译成json文件
{
"name": "Cactus",
"age": 18,
"skills": [
[
"Python",
3
],
[
"Java",
5
]
],
"has_blog": true,
"gf": null
}
类型转换

使用!!str, !!float等可以将默认类型转为指定类型

1
2
3
- !!float 3
- !!str 4
- !!str true

对应JSON格式

1
2
3
4
5
[
3.0,
"4",
"true"
]
多行文本及拼接
  • | 保留多行文本(保留换行符)
  • > 将多行拼接为一行
1
2
3
4
5
6
7
8
a: |

喜欢你

b: >

不喜欢你
才怪

对应JSON格式

1
2
3
4
{
"a": "我\n喜欢你\n",
"b": "我 不喜欢你 才怪"
}
锚点,引用及插入

-:后 加上&锚点名为当前字段建立锚点,下面可使用*锚点名引用锚点,或使用<<: *锚点名直接将锚点数据插入到当前的数据中,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
users:
- &zs
name: 张三
password: !!str 123456
- &ls
name: 李四
password: abcdefg

case1:
login: *zs

case2:
user:
<<: *ls
age: 20

对应JSON格式:

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
{
"users": [
{
"name": "张三",
"password": "123456"
},
{
"name": "李四",
"password": "abcdefg"
}
],
"case1": {
"login": {
"name": "张三",
"password": "123456"
}
},
"case2": {
"user": {
"name": "李四",
"password": "abcdefg",
"age": 20
}
}
}
Python操作YAML文件及字符串

​ 需要安装pyyaml, pip install pyyaml

和JSON文件类似,yaml也提供load和dump两种方法。

  • yaml.load()yaml.safe_load(YAML字符串或文件句柄):yaml -> 字典,如yaml中有中文,需要使用 字符串.encode('utf-8')或打开文件时指定encoding='utf-8'
  • yaml.dump(字典):默认为flow流格式,即字典{b': {'c': 3, 'd': 4}},会被转为b: {c: 3, d: 4}形式,可以使用default_flow_style=False关闭流模式

由于yaml.load()支持原生Python对象,不安全,建议使用yaml.safe_load()

yaml 字符串 转成 字典
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import yaml
# yaml字符串
yaml_str = '''
name: Cactus
age: 18
skills:
-
- Python
- 3
-
- Java
- 5
has_blog: true
gf: ~
'''
print(yaml.safe_load(yaml_str))
# 如果有中文,可以使用
yaml.load(yaml_str.encoding('utf-8'))
yaml 文件转成字典
1
2
3
import yaml
with open('demo.yaml', encoding='utf-8') as f: # demo.yaml内容同上例yaml字符串
print(yaml.safe_load(f))
字典 转成 yaml字符串或文件
1
2
3
4
5
import yaml
dict_var = {'name': 'Cactus', 'age': 18, 'skills': [['Python', 3], ['Java', 5]], 'has_blog': True, 'gf': None}
print(yaml.dump(dict_var,)) # 转为字符串,使用默认flow流格式
with open('demo5.yaml', 'w', encoding='utf-8') as f:
yaml.dump(dict_var, f, default_flow_style=False) # 写入文件,不是用flow流格式
1
2
3
4
5
6
7
8
9
10
# demo5.yaml
age: 18
gf: null
has_blog: true
name: Cactus
skills:
- - Python
- 3
- - Java
- 5
自定义tag

​ YAML常用于配置文件,当配置文件中需要配置一些用户名密码时,直接写在YAML文件并上传到代码仓库中则很容易造成密码泄露。

解决的方法有两种:

  1. 配置文件仅本地使用,不传到代码仓库中
  2. 将密码配置到执行机器的环境变量中,在YAML中使用特殊标记表示读取一个环境变量 (推荐)

在PyYAML中一种tag标识一种类型,常见的tag有:

!!null None
!!bool bool
!!int int
!!float float
!!binary bytes
!!timestamp datetime.datetime
!!omap, !!pairs list of pairs
!!set set
!!str str
!!seq list
!!map dict

我们自定义一个新的tag, !env, 并编写一个对应的处理函数(PyYAML中称为constructor构造器),代码如下:
demo.yaml文件

1
2
# demo.yml
user: !env ${USER} # 表示环境变量USER,即当前用户名
1
2
3
4
5
6
7
8
9
10
11
12
13
# tag在python中使用
import os
import yaml # 需要pip install pyyaml

def env_var_constructor(loader, node):
value = loader.construct_scalar(node) # PyYAML loader的固定方法,用于根据当前节点构造一个变量值
var_name = value.strip('${} ') # 去除变量值(例如${USER})前后的特殊字符及空格
return os.getenv(var_name, value) # 尝试在环境变量中获取变量名(如USER)对应的值,获取不到使用默认值value(即原来的${USER})

yaml.SafeLoader.add_constructor('!env', env_var_constructor) # 为SafeLoader添加新的tag和构造器

with open('demo.yml') as f:
print(yaml.safe_load(f)) # 打开文件并使用SafeLoader加载文件内容

为tag分配匹配模式

此时YAML文件中环境变量只能使用强制类型声明!env ${变量名}来使用,如果想直接使用${变量名}来使用则需要为该tag指定一种正则匹配模式,即识别到类似${变量名}格式时自动使用!env这个tag。

1
2
3
# demo.yml
user: !env ${USER} # 表示环境变量USER,即当前用户名
path: ${PATH} # 期望可以直接使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# tag在python中使用
import os
import re
import yaml

pattern = re.compile('\${\w+}') # 匹配 ${一个或多个字母或数字}

def env_var_constructor(loader, node):
value = loader.construct_scalar(node)
var_name = value.strip('${} ')
return os.getenv(var_name, value)

yaml.SafeLoader.add_constructor('!env', env_var_constructor) # 添加新tag即对应的构造器
yaml.SafeLoader.add_implicit_resolver('!env', pattern, None) # 为tag指定一种正则匹配

with open('demo.yml') as f:
print(yaml.safe_load(f)) # 打开文件并使用SafeLoader加载文件内容

一个节点使用多个变量

如果我们想要在一个节点中使用多个变量,如

1
2
3
4
# demo.yml
user: !env ${USER}
path: ${PATH}
msg: 当前用户名 ${USER} 系统路径 ${PATH}

则需要对节点值value(字符串格式)进行逐个替换。
首先我们需要修改我们的匹配模式,允许${变量}前后可以拥有多个任意字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os
import re
import yaml

pattern = re.compile('.*?(\${\w+}).*?') # 前后可以拥有多个任意字符,使用小括号分组只取当前变量${变量名}内容,`?`表示非贪婪匹配。

def env_var_constructor(loader, node):
value = loader.construct_scalar(node)
for item in pattern.findall(value): # 遍历所有匹配到到${变量名}的变量, 如${USER}
var_name = item.strip('${} ') # 如,USER
value = value.replace(item, os.getenv(var_name, item)) # 用环境变量中取到的对应值替换当前变量
return value # 如superin替换${USER},取不到则使用原值${USER}

yaml.SafeLoader.add_constructor('!env', env_var_constructor)
yaml.SafeLoader.add_implicit_resolver('!env', pattern, None)

with open('demo.yml') as f:
print(yaml.safe_load(f))

Excel文件 - openpyxl / xlrd / xlwt

Python中常用的操作Excel的三方包有xlrd,xlwt和openpyxl等

  • xlrd支持读取.xls和.xlsx格式的Excel文件,只支持读取,不支持写入。
  • xlwt只支持写入.xls格式的文件,不支持读取。
  • openpyxl不支持.xls格式,但是支持.xlsx格式的读取写入,并且支持写入公式等。
xlsx文件格式
读取所有数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import openpyxl

# 打开 Excel 文件
wb = openpyxl.load_workbook('example.xlsx')

# 选择工作表
sheet = wb['Sheet1']

# 读取单元格数据
cell_value = sheet['A1'].value
print(cell_value)

# 遍历行并获取数据,假设数据从第二行开始
for row in sheet.iter_rows(min_row=2, values_only=True):
cell1 = row[0]
cell2 = row[1]
print(cell1, cell2)

# 关闭 Excel 文件
wb.close()
按行读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import openpyxl  

# 打开 Excel 文件
wb = openpyxl.load_workbook('example.xlsx')

# 选择要操作的工作表
sheet = wb['Sheet1']

# 遍历每一行并获取数据
for row in sheet.iter_rows(values_only=True):
# values_only=True 可以确保直接获取单元格的值而非包含其他信息的对象

# 遍历每个单元格的值
for cell in row:
print(cell, end=' ') # end=' ' 是为了在同一行打印所有单元格的值
print() # 换行表示下一行

# 关闭 Excel 文件
wb.close()
读取单元格数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import openpyxl  

# 打开 Excel 文件
wb = openpyxl.load_workbook('example.xlsx')

# 选择要操作的工作表
sheet = wb['Sheet1']

# 读取特定单元格的数据 - 使用 cell() 方法
cell_value = sheet.cell(row=1, column=1).value
print(cell_value)

# 读取特定单元格的数据 - 通过索引访问
cell_value = sheet['A1'].value
print(cell_value)

# 关闭 Excel 文件
wb.close()
写入文件
1
2
3
4
5
6
7
8
9
10
11
import openpyxl  

wb = openpyxl.Workbook()
sheet = wb.active

# 写入数据到指定单元格
sheet['A1'] = 'Hello'
sheet['B1'] = 'World'

# 保存 Excel 文件
wb.save('output.xlsx')
xls文件格式
读取 所有数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import xlrd  

# 打开 Excel 文件
workbook = xlrd.open_workbook('example.xls')

# 选择要操作的工作表
sheet = workbook.sheet_by_index(0) # 假设选择第一个工作表

# 一次性读取所有数据
data = [sheet.row_values(row) for row in range(sheet.nrows)]

# 打印数据
for row in data:
print(row)
按行读取
1
2
3
4
5
6
7
8
9
10
11
12
import xlrd  

# 打开 Excel 文件
workbook = xlrd.open_workbook('example.xls')

# 选择要操作的工作表
sheet = workbook.sheet_by_index(0) # 假设选择第一个工作表

# 逐行读取数据
for row_idx in range(sheet.nrows):
row_data = sheet.row_values(row_idx)
print(row_data)
读取单元格数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import xlrd  

# 打开 Excel 文件
workbook = xlrd.open_workbook('example.xls')

# 选择要操作的工作表
sheet = workbook.sheet_by_index(0) # 假设选择第一个工作表

# 指定要读取的行和列
row_idx = 0 # 第一行
col_idx = 0 # 第一列

# 读取特定单元格的数据
cell_value = sheet.cell_value(row_idx, col_idx)
print(cell_value)
写入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import xlwt  

# 创建一个新的 Excel 文件
workbook = xlwt.Workbook()

# 创建一个工作表
sheet = workbook.add_sheet('Sheet1')

# 写入数据到单元格
sheet.write(0, 0, 'Hello')
sheet.write(0, 1, 'World')

# 保存 Excel 文件
workbook.save('output.xls')

html文件

lxml支持HTML及XML,解析速度快,兼容性强。使用方式和ElementTree比较像。

安装方法
1
pip install lxml
lxml节点对象常用方法:
  • xpath(): 使用XPath获取下级节点,结果为列表
  • text: 节点文本
  • itertext(): 迭代输出当前节点及下级所有节点文本,例如''.join(node.itertext()) 可以拿到节点中所有文本
  • attrib: 节点属性字典,如a节点 node.attrib['href']可以拿到其url
操作步骤
  • 第一步:使用etree.HTML()实例化得到根节点,实例化时会自动补全HTML代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from lxml import etree
    html = '''
    <div id="content">
    <ul>
    <li id="top_001" class="item">肖申克的救赎<li>
    <li id="top_001" class="item">霸王别姬</li>
    <li id="top_002" class="item">阿甘正传</li>
    </ul>
    </div>
    '''
    root = etree.HTML(html)
  • 第二步:使用root.xpath()查找节点

    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
    '''
    不同于xml.etree.ElementTree中只支持部分的XPath语法,root.xpath()中支持使用完整的XPath语法,路径不需要使用“.”开始,root.xpath()方法返回查找到的所有节点列表
    '''
    root.xpath('/html/body/div'):绝对路径查找
    root.xpath('//ul/li[2]') :相对路径,结合索引
    root.xpath('//div[@id="content"]'):结合属性查找
    root.xpath('//li[@id="top_001" and @class="item"]'):多条件查找
    root.xpath('//li[text()="阿甘正传"]'):使用text()函数根据元素文本查找
    root.xpath('//li[contains(text(), "阿甘")]'):使用contains函数查找文本包含
    root.xpath('//li[1]/following-sibling::li'):使用XPath的轴方法获取后面所有的同级元素

    '''
    如果想要获取节点是属性或文本等,可以从返回的节点列表中取出节点,并使用.tag、.text或.attrib获取节点的标签、文本或属性字典,也可以直接在XPath语句中使用/@attribute或/text来获取属性
    '''
    div = root.xpath('//div')[0]
    print(div.attrib)
    print(root.xpath('//div/@id'))
    print(root.xpath('//ul/li[last()]/text()'))

    '''
    由于root.xpath()方法返回节点列表,这里去第一个元素,并打印其属性字典。第三行直接使用XPath表达式取相应节点的id属性,返回属性列表。第四行使用XPath表达式text()函数取节点的文本,返回文本列表
    '''
    {'id': 'content'}
    ['content']
    ['阿甘正传']

StringIO和BytesIO

StringIO

数据读写不一定是文件,也可以在内存中读写;
StringIO顾名思义就是在内存中读写str

1
2
3
4
5
6
7
from io import StringIO
f = StringIO() # 创建 StringIO
f.write('hello') # 返回写入的长度: 5
f.write(' ')
f.write('world!')
print(f.getvalue()) # 通过 getvalue() 方法读取f的值
hello world!

BytesIO

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO

1
2
3
4
5
6
7
from io import BytesIO
f = BytesIO()
# 写入的不是str,而是经过UTF-8编码的bytes
f.write('中文'.encode('utf-8')) # 返回写入的长度 6

print(f.getvalue())
# b'\xe4\xb8\xad\xe6\x96\x87'

os 文件及目录操作

os.name

通过name的值可以判断是什么系统
如果是posix,说明系统是LinuxUnixMac OS X,如果是nt,就是Windows系统。
详细信息可以使用 os.uname()

环境变量 - os.environ

在操作系统中定义的环境变量,全部保存在os.environ这个变量中, 要获取某个环境变量的值,可以调用os.environ.get('key')

操作文件和目录

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import os
# 查看当前目录的绝对路径:
os.path.abspath('.')

# 获取当前模块的文件名
print(__file__)

# 获取命令行参数
import sys
print(sys.argv)

# 合并路径
os.path.join('/Users/michael', 'testdir')

# 创建一个目录
os.mkdir('/Users/michael/testdir')

# 删掉一个目录:
os.rmdir('/Users/michael/testdir')

# 拆分路径
os.path.split('/Users/michael/testdir/file.txt')
# ('/Users/michael/testdir', 'file.txt')

# 得到文件扩展名
os.path.splitext('/path/to/file.txt')
# ('/path/to/file', '.txt')

# 文件重命名
os.rename('test.txt', 'test.py')

# 删掉文件
os.remove('test.py')

# 文件复制
'''
`shutil`模块提供了`copyfile()`的复制函数,你还可以在`shutil`模块中找到很多实用函数,它们可以看做是`os`模块的补充。
'''
import shutil
# 源文件路径
source_file = 'path/to/source/file.txt'
# 目标文件路径
destination_file = 'path/to/destination/file.txt'
# 复制文件
shutil.copy(source_file, destination_file)


# 获取文件路径下的所有文件
# 指定要遍历的文件夹路径
folder_path = 'path/to/your/folder'

# 获取指定目录下所有的文件
files_in_folder = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]

# 打印输出所有文件名
for file_name in files_in_folder:
print(file_name)


# 获取目录和子目录
# 指定要遍历的根路径
root_folder = 'path/to/your/folder'

# 使用os.walk()遍历根路径以及其子路径,并打印每个找到项
for folder_path, folders, files in os.walk(root_folder):
print(f'当前所在位置: {folder_path}')
print('包含以下子目录:')
for folder in folders:
print(os.path.join(folder_path, folder))


# 获取整个树结构
def list_files(startpath):
for root, dirs, files in os.walk(startpath):
level = root.replace(startpath, '').count(os.sep)
indent = ' ' * 4 * (level)
print('{}{}/'.format(indent, os.path.basename(root)))
subindent = ' ' * 4 * (level + 1)
for f in files:
print('{}{}'.format(subindent, f))


root_folder = 'path/to/your/folder'
list_files(root_folder)

序列化

在程序运行的过程中,所有的变量都是在内存中
把变量从内存中变成可存储或传输的过程称之为序列化
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化

Python提供了pickle模块来实现序列化。

序列化

1
2
3
4
import pickle  
d = dict(name='Bob', age=20, score=88)
with open('data.pickle', 'wb') as f:
pickle.dump(d, f)

反序列化

1
2
3
4
5
import pickle  
with open('data.pickle', 'rb') as f:
d = pickle.load(f)

print(d) # Output: {'name': 'Bob', 'age': 20, 'score': 88}

JSON处理

如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,例如XML,JSON

JSON类型 Python类型
{} dict
[] list
“string” str
1234.56 int或float
true/false True/False
null None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json  

class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age

# 创建类的实例
my_object = MyClass("John", 25)

# 将类实例转换为JSON字符串
json_string = json.dumps(my_object.__dict__)

# 将JSON字符串序列化到文件中
with open("data.json", "w") as file:
json.dump(json_string, file)
1
2
3
4
5
6
7
8
9
10
11
12
import json  

# 从JSON文件中读取数据
with open("data.json", "r") as file:
json_string = json.load(file)

# 将JSON字符串转换为Python对象
my_object = json.loads(json_string)

# 打印Python对象的属性
print(my_object["name"])
print(my_object["age"])

异常处理

image-20250226001150186

try… except

一般情况下,异常会导致程序中断退出,为避免程序中断,我们需要对异常进行处理,在Python中我们使用try ... except ...语句处理异常

1
2
3
4
5
6
7
8
9
def div(a, b):
try:
return a / b
# 使用Exception代表任何异常, as ex是对异常添加别名,ex就是具体的异常对象
except Exception as ex:
print('出现异常', type(ex), ex)

div(1, 0)
div(1, 'a')

一般来说,建议对不同类型的异常进行单独处理

1
2
3
4
5
6
7
8
9
10
def div(a, b):
try:
return a / b
except ZeroDivisionError:
print('被除数b不能为0')
except TypeError:
print('类型错误a和b应为数字')

div(1, 0)
div(1, 'a')

except也可以一次捕获多个异常,对任意一种异常做同一处理,例如try: ... except: (ZeroDivisionError, TypeError): ...

无异常及无论是否有异常都执行的操作

异常处理支持使用try: ... except: ...后使用else: ...在无异常时执行某些操作,及使用finally: ...,无论是否有异常都执行某些语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def div(a, b):
try:
c = a / b
except ZeroDivisionError:
print('被除数b不能为0')
except TypeError:
print('类型错误a和b应为数字')
else: # 异常没有匹配后,最终匹配结果
print('结果为', c)
return c
finally:
print('执行结束...')

div(4, 2)
div(1, 0)
div(1, 'a')
'''
结果为 2.0
执行结束...
被除数b不能为0
执行结束...
类型错误a和b应为数字
执行结束...
'''

Python中常见异常

异常类型 说明 示例
SyntaxError Python语法异常(缩紧或语法错误) int a = 1
NameError 变量名异常(不存在该变量名) print(a)
TypeError 变量类型异常 print(‘a’/ ‘b’)
ValueError 值异常(变量值异常) int(‘a’)
ZeroDivisionError 0除异常(被除数不能为0) 10/0
IndexError 索引异常(列表等中不存在该索引对应的值) s = [‘a’, ‘b’, ‘c’] ; print(s[5])
KeyError 键异常(字典中不存在这个Key对应的值) s = {‘a’:1, ‘b’:2, ‘c’:3} ; print(s[‘d’])
AttributeError 获取属性异常(对象没有该属性) a = ‘hello’; print(a.name)
AssertionError 断言异常(断言未通过) assert 1>2
FileNotFoundError 文件不存在 open(‘abc.txt’)
OSError 操作系统错误 os.mkdirs(‘/ddd/xxx’)

主动抛出异常

在编写程序中不一定要捕获并处理(抑制)所有异常,有时候快速抛出异常并清楚的说明原因也是一种比较好的使用方式

1
2
3
4
5
6
7
8
9
10
11
def div(a, b):
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
# 使用raise排除异常,终止程序
raise ValueError('a和b必须为int或float类型')
if b == 0:
raise ValueError('被除数b不能为0')
return a / b

div(1, 0)
div(1, 'a')

自定义异常

也可以自定义异常类型进行抛出,以使的错误类型更清晰

1
2
3
4
5
6
7
8
9
10
11
"""
定义了一个名为MyCustomException的自定义异常类,它继承自Exception类,并重写了__init__方法以接受一个消息作为参数。然后我们使用raise语句抛出自定义异常,并在except块中捕获并打印异常消息。
"""
class MyCustomException(Exception):
def __init__(self, message):
self.message = message

try:
raise MyCustomException("This is a custom exception")
except MyCustomException as e:
print("Caught custom exception: ", e.message)

装饰器

装饰器是Python中的一个重要概念,多用于在不修改原函数的基础上,为函数增加额外的功能

基础装饰器

案例: 你要喝水

1
2
3
def DrinkWater():
print(" 喝水(动作) ")
DrinkWater()

但你杯子里没水了,你还需要往杯子里装水

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def drinkWater():
print(" 喝水(动作) ")

# 装水
def fillWater(fn):
print(" 往杯子倒水 ")
return drinkWater

drinkWater = fillWater(drinkWater)

drinkWater()
'''
print 打印:
往杯子倒水
喝水(动作)
'''
'''
fillWater 便是一个装饰器,它的参数是一个函数对象,同数字、字符串、列表、字典等数据类型一样,函数和类也可以作为函数的参数使用
'''

装饰器本质上就是以函数作为参数,对函数做一些处理,并替换原函数的一种高阶函数。

1
2
3
4
5
6
7
8
9
10
# 装水
def fillWater(fn):
print(" 往杯子倒水 ")
return drinkWater

@fillWater # 挂载装饰器,会自动替换原函数, 我们只需要执行原函数就行
def drinkWater():
print(" 喝水(动作) ")

drinkWater() # 这里的drinkWater() 实际就是装饰后的drinkWater() == fillWater(drinkWater)

装饰器处理函数参数

如果需要给喝水加入一个参数,控制喝几口水

1
2
3
4
5
6
7
8
9
10
11
12
def fillWater(fn):
def new_gift(item): # 准备一个新的函数,跟drinkWater()一样传递一个数字
print(" 往杯子倒水 ")
return drinkWater(item) # 调用原函数,保证原函数的输出,并return结果出去
return new_gift # 返回新的喝水函数,包装了倒水动作的新喝水函数

@fillWater
def drinkWater(item):
print(f'喝{item}口水')

drinkWater(2) # 通过传递参数喝几口水

带参装饰器

如果给杯子倒水也加上倒多少毫升,通过装饰器传递多少毫升

1
2
3
4
5
6
7
8
9
10
11
12
def fillWater(ms):
def new_gift(item): # 准备一个新的函数,跟drinkWater()一样传递一个数字
print(f" 往杯子倒{ms}毫升水 ")
return drinkWater(item) # 调用原函数,保证原函数的输出,并return结果出去
return new_gift # 返回新的喝水函数,包装了倒水动作的新喝水函数

@fillWater(500)
def drinkWater(item):
print(f'喝{item}口水')

drinkWater(2) # 通过传递参数喝几口水

!!! 装饰器在导入模块时立即计算的,即没调用drinkWater()之前就已经执行生成定制后的new_gift

生成器和迭代器

可迭代对象

​ 实现了__iter__方法, __iter__方法返回一个迭代器

迭代器

​ 在Python中,迭代器(iterator)是一个对象,它实现了迭代协议,即具有__iter__()__next__()方法的对象。迭代器允许我们遍历容器对象(例如列表、元组、集合、字典等)中的元素,而不需要了解容器的内部结构。

以下是关于迭代器的一些重要信息:

  1. 迭代器协议

    • __iter__()方法返回迭代器对象自身。
    • __next__()方法返回容器中的下一个元素,如果没有更多元素,则抛出StopIteration异常。
  2. 使用迭代器

    • 使用iter()函数可以将可迭代对象(如列表、元组)转换为迭代器。例如:my_iter = iter([1, 2, 3])
    • 使用next()函数从迭代器中获取下一个元素。例如:next(my_iter)
  3. 迭代器示例

    1
    2
    3
    4
    5
    6
    numbers = [1, 2, 3]  
    iter_numbers = iter(numbers)

    print(next(iter_numbers)) # 输出: 1
    print(next(iter_numbers)) # 输出: 2
    print(next(iter_numbers)) # 输出: 3
  4. 迭代器的优点

    • 节省内存:迭代器一次只加载一个元素,不需要一次性加载整个容器。
    • 惰性计算:在需要时计算元素值,延迟执行。
  5. 迭代器与可迭代对象的区别

    • 可迭代对象(iterable)定义了__iter__()方法,返回一个新的迭代器。
    • 迭代器会保存当前状态,每次调用next()方法获取下一个值,直到遍历完成。

生成器

​ 生成器(generator)是一种特殊的迭代器,可以通过生成器函数或生成器表达式来创建。生成器能够动态生成值,而不需要一次性将所有值存储在内存中,这使得生成器在处理大量数据或需要惰性计算时非常有用。

以下是有关生成器的一些重要信息:

  1. 生成器函数

    • 使用yield关键字来生成值,而不是return
    • 生成器函数在被调用时返回一个迭代器对象。
  2. 生成器表达式

    • 类似于列表推导式,但使用圆括号而不是方括号。
    • 通过惰性计算的方式生成值。
  3. 使用生成器

    • 使用next()函数从生成器中获取下一个值。
    • 生成器会在生成每个值时暂停,直到需要下一个值。
  4. 生成器示例

    • 生成器函数示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def my_generator():  
    yield 1
    yield 2
    yield 3

    gen = my_generator()
    print(next(gen)) # 输出: 1
    print(next(gen)) # 输出: 2
    print(next(gen)) # 输出: 3
    • 生成器表达式示例:
    1
    2
    3
    gen = (x**2 for x in range(5))  
    for num in gen:
    print(num) # 输出: 0, 1, 4, 9, 16
  5. 生成器的优点

    • 节省内存:不需要一次性存储所有值。
    • 惰性计算:按需生成值,延迟执行。

列表推导式

!!! 推倒式:当我们对一批可迭代的数据(如列表或字典)进行提取或处理,最后要得到一个新的列表或字典时,推导式是一种非常简洁的表达方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data = [
{'name': '张三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '赵六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孙八', 'gender': 'male', 'age': 13},
]

names = [] # 定义一个空列表

# 用for循环
for item in data: # 遍历数据
name = item['name'] # 提取每行中的name
names.append(name) # 追加到列表中

# 用推导式
names = [item['name'] for item in data] # 遍历data,提取每项中的name生成一个新列表
# 在提取数据时,我们还可以对每一项数据进行
names = ['姓名: '+item['name'] for item in data]

多重循环

推导式还支持多重循环

1
2
3
4
5
6
7
8
for x in range(1,5)
if x > 2
for y in range(1,4)
if y < 3
x*y

# 用推导式
[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]

批量执行操作

推导式就是一种循环操作,我们也可以使用推导式来批量执行一些相似操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def step1(driver):
print('步骤1)

def step2(driver):
print('步骤2)

def step3(driver):
print('步骤3)

# 用推导式实现
# 我们可以将函数名放到一个列表里,然后使用推导式循环执行
steps = [step1, step2, step3] # 函数名列表

[step(driver) for step in steps] # 不需要变量接收,我们只需要它循环执行

字典推导式

当我们需要遍历一批数据最后得到一个字典时,同样可以使用字典推导式

1
2
3
4
5
6
7
8
9
10
11
data = [
{'name': '张三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '赵六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孙八', 'gender': 'male', 'age': 13},
]

# 使用字典推导式
persons = {item['name']: item['age'] for item in data}

生成器

生成器实际上是一种包含初始数据和推导法则的对象

对应大量的数据或者CSV/Excel文件中的数据,生成器可以大量的节省内存,比如csv.Reader(f)就是一个生成器,只存了当前位置和读取下一行数据的方法;当你需要遍历时,它再每次给你读取一行数据给你

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data = [
{'name': '张三', 'gender': 'male', 'age': 12},
{'name': '李四', 'gender': 'female', 'age': 10},
{'name': '王五', 'gender': 'male', 'age': 20},
{'name': '赵六', 'gender': 'male', 'age': 11},
{'name': '周七', 'gender': 'female', 'age': 16},
{'name': '孙八', 'gender': 'male', 'age': 13},
]
# 列表推导式
names = [item['name'] for item in data]

# 我们把列表的中括号改为小括号就得到一个生成器
names2 = (item['name'] for item in data)
'''
!!! 生成器和推导式不同,其中的循环不是立即执行的,只用你遍历这个生成器时才会执行
'''

魔术方法

魔术方法 描述
__new__ 创建类并返回这个类的实例
__init__ 可理解为“构造函数”,在对象初始化的时候调用,使用传入的参数初始化该实例
__del__ 可理解为“析构函数”,当一个对象进行垃圾回收时调用
__metaclass__ 定义当前类的元类
__class__ 查看对象所属的类
__base__ 获取当前类的父类
__bases__ 获取当前类的所有父类
__str__ 定义当前类的实例的文本显示内容
__getattribute__ 定义属性被访问时的行为
__getattr__ 定义试图访问一个不存在的属性时的行为
__setattr__ 定义对属性进行赋值和修改操作时的行为
__delattr__ 定义删除属性时的行为
__copy__ 定义对类的实例调用 copy.copy() 获得对象的一个浅拷贝时所产生的行为
__deepcopy__ 定义对类的实例调用 copy.deepcopy() 获得对象的一个深拷贝时所产生的行为
__eq__ 定义相等符号“==”的行为
__ne__ 定义不等符号“!=”的行为
__lt__ 定义小于符号“<”的行为
__gt__ 定义大于符号“>”的行为
__le__ 定义小于等于符号“<=”的行为
__ge__ 定义大于等于符号“>=”的行为
__add__ 实现操作符“+”表示的加法
__sub__ 实现操作符“-”表示的减法
__mul__ 实现操作符“*”表示的乘法
__div__ 实现操作符“/”表示的除法
__mod__ 实现操作符“%”表示的取模(求余数)
__pow__ 实现操作符“**”表示的指数操作
__and__ 实现按位与操作
__or__ 实现按位或操作
__xor__ 实现按位异或操作
__len__ 用于自定义容器类型,表示容器的长度
__getitem__ 用于自定义容器类型,定义当某一项被访问时,使用 self[key] 所产生的行为
__setitem__ 用于自定义容器类型,定义执行 self[key]=value 时产生的行为
__delitem__ 用于自定义容器类型,定义一个项目被删除时的行为
__iter__ 用于自定义容器类型,一个容器迭代器
__reversed__ 用于自定义容器类型,定义当 reversed( ) 被调用时的行为
__contains__ 用于自定义容器类型,定义调用 in 和 not in 来测试成员是否存在的时候所产生的行为
__missing__ 用于自定义容器类型,定义在容器中找不到 key 时触发的行为

面向对象 - __slots__

如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称


# 当你给限定之外的属性绑定值,正常情况下会创建新属性,并赋值。但加了限制后会直接报错
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

!!! 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

1
2
3
4
5
6
7
8
9
10
11
12
13
class ParentClass:
__slots__ = ['attribute1', 'attribute2']


class ChildClass(ParentClass):
pass


# 现在 ChildClass 没有定义自己的 __slots__,不受限制

c1 = ChildClass()
c1.attribute3 = 1 # success
print(c1.attribute3)

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ParentClass:
__slots__ = ['attribute1', 'attribute2']


class ChildClass(ParentClass):
__slots__ = []
pass


# 现在 ChildClass 定义自己的 __slots__,它将会继承父类的 __slots__加上自己的 __slots__

c1 = ChildClass()
c1.attribute3 = 1 # 报错
print(c1.attribute3)
'''
c1.attribute3 = 1
AttributeError: 'ChildClass' object has no attribute 'attribute3'
'''

面相对象 - MixIn

通过多重继承,一个子类就可以同时获得多个父类的所有功能

mixln - 可以让一个类除了继承其他类外,再同时集成其他类。这种设计通常称之为MixIn。

1
2
3
# 这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass

面相对象 - 订制类

__str__

打印一个类实例会打印这种信息

1
2
3
4
5
6
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

我们可以通过 __str__ 进行输出格式化操作

1
2
3
4
5
6
7
8
9
10
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self): # print输出时调用
return 'Student object (name: %s)' % self.name
__repr__ = __str__
s = Student('Michael')
print(s)
s
# Student object (name: Michael)

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b

def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己

def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值


for n in Fib():
print(n)

__getitem__

__iter__() 可以让类进行遍历,但不能拿到某一个单一的值,因为它不是了列表。不能用下标获取

但可以通过实现__getitem__ 可以实现用下标获取某一个值

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
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L


f = Fib()
print(f[0]) # 1

print(f[0:5]) # [1, 1, 2, 3, 5]

__getattr__ 和 __setattr__

__getattr__()方法在访问一个不存在的属性时会被调用,可以在该方法中自定义返回值。
__setattr__()方法在给对象的属性赋值时会被调用,可以在该方法中自定义赋值操作。

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
# __getattr__
class Person:
def __init__(self, name):
self.name = name

def __getattr__(self, attr):
return f"{attr}属性不存在"

p = Person("Tom")
print(p.name) # 输出 "Tom"
print(p.age) # 输出 "age属性不存在"

# __setattr__()
class Person:
def __init__(self, name):
self.name = name

def __setattr__(self, attr, value):
if attr == "name":
self.__dict__[attr] = value.upper() # 赋值时,对形参进行特殊处理
else:
self.__dict__[attr] = value

p = Person("Tom")
print(p.name) # 输出 "TOM"
p.age = 18
print(p.age) # 输出 18

__call__

只需要定义一个__call__()方法,就可以直接对实例进行调用

1
2
3
4
5
6
7
8
9
class Student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('hello word %s' % self.name)

s = Student('Michael')
s() # 直接调用实例,就会调用__call__方法

面向对象 - 枚举类

  1. 枚举类中的成员是从1开始计数的int常量。
  2. 可以通过名称或值来获取对应的枚举成员,使用 EnumName.member_nameEnumName('member_value')
  3. 枚举成员的值是自动赋给成员的int常量。
  4. 枚举类型会自动实现比较和排序功能。

列表类型

当需要定义的类型,只含有值,可以这样定义

1
2
3
4
5
6
7
8
9
10
11
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) # value属性则是自动赋给成员的int常量,默认从1开始计数。
# 访问枚举成员

print(Month.Jan) # 输出: Month.Jan(通过名称访问)
print(Month['Jan']) # 输出: Month.Jan(通过名称访问)
print(Month(1)) # 输出: Month.Jan(通过值访问)
print(Month.Jan.value) # 输出: 1(通过名称访问)
print(Month['Jan'].value) # 输出: 1(通过名称访问)
print(Month(1).value) # 输出: 1(通过值访问)

键值对类型

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
from enum import Enum, unique

@unique # @unique装饰器可以帮助我们检查保证没有重复值。
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

# 获取方式
print(Weekday.Mon) # 输出: Weekday.Mon(通过名称访问)
print(Weekday['Mon']) # 输出: Weekday.Mon(通过名称访问)
print(Weekday(1)) # 输出: Weekday.Mon(通过值访问)
print(Weekday.Mon.value) # 输出: 1(通过名称访问)
print(Weekday['Mon'].value) # 输出: 1(通过名称访问)
print(Weekday(1).value) # 输出: 1(通过值访问)
'''
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
'''

面向对象 - @property

当类中使用私有变量来限制访问,通过get和set方法来获取和设置,属性过多,就需要定义多的get和set方法;@property可以通过装饰器的方式解决这个问题

  • @property

    该装饰器,可以直接把一个get方法书写方式的返回值

  • @fn.setter

  • 设置只读属性 - 只定义getter方法,不定义setter方法

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
class Student(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

@property
def age(self):
return self._age


s = Student()
s.score = 60 # 这里调用的其实是 def score(self, value)
print(s.score) # 这里调用的其实是 def score(self)
'''
比正常的属性. 调用,这种写法可以不写属性,且可以在调用时进行逻辑判断
'''

面相对象 - 元类 [补充]

元类是什么

​ 在面向对象(OOP)编程中,我们可以用不同的类来描述不同的实体及操作,可以通过父类来设计一些“默认”操作,也可以用MixIn类来组合扩展一些额外操作,也可以用抽象类抽象方法来描述要实现的接口,面向接口编程。

​ 元类是一种type(type的子类),是一种自定义类型,可以定制类的调用、对象创建、初始化、销毁等各种操作。

元类的使用场景

多数情况下元类用来对普通类来加以限制和规范。使用元类(自定义类型)可以在类的创建(__new__)、初始化()比如限制类必须包含特定属性和实现特定方法、限制类只能有一个实例对象。

典型使用场景如下:

  • 不允许类实例化
  • 单例模式:每个类只允许创建一个对象
  • 根据属性缓存对象:当某一属性相同时返回同一对象
  • 属性限制:类中必须包含某些属性或实现某些方法
  • ORM框架:用类及类属性来描述数据库表并转为数据库操作

元类使用示例

不允许类实例化

在某些情况下,假设我需要限制一些类不允许创建对象(只允许使用类名操作),可以使用元类加以限制

1
2
3
4
5
6
7
8
9
10
class NoInstances(type):  # 定义元类-继承type
def __call__(self, *args, **kwargs): # 控制类调用(实例化)过程
"""类调用"""
raise TypeError("不允许实例化")


class User(metaclass=NoInstances): # 声明使用元类(该类型)
pass

user = User() # ()即调用类的__call__操作,这里会抛出异常,因此无法直接实例化创建对象

单例模式

某些情况下仅允许类创建一个实例对象,也可以使用元类进行限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton(type):   # 单例类型-定制的元类
def __init__(self, *args, **kwargs):
self.__instance = None # 添加一个私有属性,用于保存唯一的实例对象
super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs): # 控制类调用
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs) # 不存在则创建
return self.__instance
else:
return self.__instance # 否则返回已创建的


class User(metaclass=Singleton):
def __init__(self):
print('创建用户')

user1 = User() # 创建对象
user2 = User() # 创建对象
print(user1 is user2) # 返回True,两者是同一对象

根据属性缓存对象

这是单例模式的扩展,针对特定属性,完全相同的属性组合创建同一对象

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 weakref

class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary() # 添加一个缓存字典

def __call__(self, *args):
if args in self.__cache: # 通过 参数组合查询缓存字典中有没有对应的对象
return self.__cache[args]
else:
obj = super().__call__(*args) # 创建对象
self.__cache[args] = obj # 根据参数组合(元祖类型)到缓存字典
return obj


class User(metaclass=Cached):
def __init__(self, name):
print('创建用户({!r})'.format(name))
self.name = name

a = User('张三')
b = User('李四')
c = User('张三')
print(a is b) # False 名字不同,不是同一对象
print(a is c) # True 名字相同,是同一对象

限制类必须包含特定属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TestCaseType(type):
def __new__(cls, name, bases, attrs):
print('name', name)
print('bases', bases)
print('attrs', attrs)
if {'priority', 'timeout', 'owner', 'status', 'run_test'} - set(attrs.keys()):
raise TypeError('测试用例类必须包含priority、status、owner、timeout属性并实现run_test方法')
return super().__new__(cls, name, bases, attrs)


class TestA(metaclass=TestCaseType):
# priority = 'P1'
timeout = 10
owner = 'kevin'
status = 'ready'

def run_test(self):
pass


a = TestA() # 这里注释了用例类的priority属性,实例化会报错

ORM框架

​ ORM(Object-Relational Mapping)是一种将对象和关系数据库之间的映射的技术,它可以让我们使用面向对象的方式来操作数据库。

​ Django中的ORM模型、以及SQLAlchemy都是基于元类实现的,将数据库操作映射为 类声明和对象操作,下面是一个基于元类的,简单的ORM框架的实现

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ModelMeta(type):  # 元类
def __new__(cls, name, bases, attrs):
if name == 'Model':
return super().__new__(cls, name, bases, attrs)

table_name = attrs.get('__table__', name.lower()) # 如果类中包含table_name属性,则以该属性作为表明
mappings = {}
fields = []
primary_key = None

for k, v in attrs.items():
if isinstance(v, Field):
mappings[k] = v
if v.primary_key:
if primary_key:
raise RuntimeError('Duplicate primary key for field: {}'.format(k)) # 只允许一个Field声明为主键
primary_key = k
else:
fields.append(k)

if not primary_key:
raise RuntimeError('Primary key not found for table: {}'.format(table_name)) # 不允许没有主键

for k in mappings.keys():
attrs.pop(k)

attrs['__table__'] = table_name
attrs['__mappings__'] = mappings
attrs['__fields__'] = fields
attrs['__primary_key__'] = primary_key

return super().__new__(cls, name, bases, attrs)

class Model(metaclass=ModelMeta): # 数据模型-对应一张数据库表
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)

def save(self): # 对象保存方法-对应数据库表插入数据
fields = []
params = []
args = []

for k, v in self.__mappings__.items():
if v.primary_key:
continue
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))

sql = 'INSERT INTO {} ({}) VALUES ({})'.format(self.__table__, ','.join(fields), ','.join(params))
print('SQL:', sql)
print('ARGS:', args)

class Field: # 数据库字段
def __init__(self, name, column_type, primary_key=False):
self.name = name
self.column_type = column_type
self.primary_key = primary_key

def __str__(self):
return '<{}:{}>'.format(self.__class__.__name__, self.name)

class StringField(Field): # 字符串类型字典-对应varchar
def __init__(self, name, primary_key=False):
super().__init__(name, 'varchar(100)', primary_key)

class IntegerField(Field): # 整型字典-对应bigint
def __init__(self, name, primary_key=False):
super().__init__(name, 'bigint', primary_key)

使用这个ORM框架,我们可以定义一个继承自Model的类,并在其中定义字段。例如

1
2
3
4
5
6
7
8
9
class User(Model):
id = IntegerField('id', primary_key=True)
name = StringField('username')
email = StringField('email')
password = StringField('password')

# 我们就可以创建一个User对象,并将其保存到数据库中
user = User(id=1, name='Alice', email='alice@example.com', password='123456')
user.save()

python程序执行机制

在Python中,源代码文件(.py)在第一次被执行时,会经过编译过程生成为字节码文件(.pyc),然后由Python解释器执行该字节码文件。编译的过程会生成一个PyCodeObject对象,其中包含了字节码指令、常量、变量等信息。

具体的执行流程如下:

  1. 当 Python 解释器第一次运行一个 Python 源代码文件时,它会首先将源代码进行编译,生成字节码对象(PyCodeObject)。
  2. 这个字节码对象会被存储在内存中,并同时在同一目录下生成一个与源代码文件同名的.pyc文件,作为编译后的字节码文件。
  3. 当下次再次运行这个同样的 Python 源代码文件时,解释器会首先检查是否存在对应的.pyc文件。
  4. 如果存在.pyc文件,并且与源文件的修改时间一致,解释器会加载并执行.pyc文件;如果.pyc文件不存在或已过期,则重新对源文件进行编译生成新的字节码文件。
  5. 当解释器执行完整个字节码文件后,程序执行完成。

.pyc 文件通常是为了提高 Python 程序的执行速度而存在的,通过避免重复编译源文件,减少了开发和执行的时间,提高了执行效率。但并不是每个 Python 解释器都会生成或使用 .pyc 文件,特别是在交互式环境下或者某些特定配置下可能不会生成 .pyc 文件。

并发编程

单核CPU操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行

对于操作系统来说,一个任务就是一个进程(Process)

Python既支持多进程,又支持多线程。python实现多任务有3中模式:

  • 多进程模式;
  • 多线程模式;
  • 多进程+多线程模式。

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。

全局解释器锁 - GIL

相比c/c++,python在一些特殊场景下,pythonn比c++慢100-200倍

原因:

  • 动态类型语言,边解释边执行
  • 全局锁 - GIL,无法利用多核CPU并发执行

全局解释器锁 - Global Interpreter Lock

是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即使在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程

怎么规避GIL限制

  1. 多线程 - threading机制依然是有用的,用于IO密集型计算

    IO期间,线程会释放GIL,实现cpu和io并行,因此多线程在io密集型计算依然可以大幅度提升速度。但多线程用于CPU密集计算时,只会拖慢速度

  2. 使用multiprocessing

多进程

Unix/Linux操作系统提供了一个fork()系统调用,fork()系统调用在Unix/Linux操作系统中非常特殊,它会创建一个新的进程(子进程),该子进程是父进程的副本。父进程和子进程在执行fork()系统调用之后会继续执行接下来的代码。父进程中,fork()函数返回子进程的PID(Process ID),而在子进程中,fork()函数返回0。

调用fork - Unix/Linux

os模块封装了常见的系统调用,其中fork可以在Python程序中轻松创建子进程

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

def child_process():
# 子进程代码
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))

def parent_process():
# 父进程代码
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))

print('Process (%s) start...' % os.getpid())
pid = os.fork()

if pid == 0:
# 子进程执行child_process函数
child_process()
else:
# 父进程执行parent_process函数
parent_process()

multiprocessing

由于Windows没有fork调用,上面的代码在Windows上无法运行。
multiprocessing模块就是跨平台版本的多进程模块

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 multiprocessing  
import os

def child_process():
# 子进程代码
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))

def parent_process():
# 父进程代码
print('I (%s) just created a child process.' % os.getpid())

if __name__ == '__main__':
# 主程序开始执行
print('Process (%s) start...' % os.getpid())

# 创建子进程并指定执行的函数为child_process()
p = multiprocessing.Process(target=child_process)

# 启动子进程
p.start()

# 等待子进程结束,这样可以确保父进程在子进程完成后再继续执行。
p.join()

# 执行parent_process()函数,此时已经是父进程了。
parent_process()

Pool - 进程池

如果要启动大量的子进程,可以用进程池的方式批量创建子进程

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
import multiprocessing  
import os

def worker(num):
# 子进程代码
print('Worker %s is running (pid=%s)...' % (num, os.getpid()))

if __name__ == '__main__':
# 主程序开始执行
print('Parent process is running (pid=%s)...' % os.getpid())

# 创建一个包含4个进程的进程池
pool = multiprocessing.Pool(processes=4)

# 使用进程池执行worker函数,参数为0~3
pool.map(worker, range(4))

# 关闭进程池
pool.close()

# 等待所有子进程结束
pool.join()

# 所有子进程结束后,主进程继续执行
print('All workers done.')

子进程 - 交互

很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。

subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。

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
import subprocess
import os

def run_command(command):
# 运行外部命令并获取输出
result = subprocess.run(command, shell=True, capture_output=True, text=True)

# 检查命令是否成功执行
if result.returncode == 0:
print(f"Command '{command}' executed successfully.")
print("Output:")
print(result.stdout)
else:
print(f"Error executing command '{command}'.")
print("Error output:")
print(result.stderr)


if __name__ == '__main__':
# 主程序开始执行
print('Parent process is running (pid=%s)...' % os.getpid())

# 定义要运行的命令,可以根据不同操作系统选择不同的命令
command = "ls -l" if os.name != 'nt' else "dir"

# 运行命令
run_command(command)

通过 communicate() 方法与进程交互

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
import subprocess
import os


def run_command(command):
# 运行外部命令并获取输出
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,stdin=subprocess.PIPE, text=True)

# 与进程交互,可以向进程输入数据
input_data = "Input data to the process"
process.stdin.write(input_data)
process.stdin.close()

# 获取进程的输出和错误信息
output, error = process.communicate()

# 检查命令是否成功执行
if process.returncode == 0:
print(f"Command '{command}' executed successfully.")
print("Output:")
print(output.decode('utf-8'))
else:
print(f"Error executing command '{command}'.")
print("Error output:")
print(error.decode('utf-8'))


if __name__ == '__main__':
# 主程序开始执行
print('Parent process is running (pid=%s)...' % os.getpid())

# 定义要运行的命令,可以根据不同操作系统选择不同的命令
command = "ls -l" if os.name != 'nt' else "dir"

# 运行命令
run_command(command)

进程间通信

进程与进程之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信

Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

  1. 在使用 multiprocessing 模块进行进程间通信时,需要注意数据的序列化和反序列化问题。因为不同进程之间的内存空间是独立的,所以需要将数据序列化后再传输到另一个进程中进行反序列化。
  2. 在使用 subprocess 模块启动子进程并与其进行交互时,需要注意输入和输出的缓冲区问题。如果子进程的输出数据量很大,可能会导致缓冲区溢出,从而导致程序出现异常。可以使用 communicate() 方法来避免这个问题,该方法会等待子进程结束并获取其输出和错误信息。
Queue - 队列

Python 中的队列(Queue)是一个线程安全的数据结构,用于在多线程或多进程之间安全地传递数据。Python 提供了 queue 模块来实现队列功能,其中最常用的是 Queue 类。Queue 类实现了一个简单的 FIFO(先进先出)队列。

相比于 queue 模块中的 Queue 类,multiprocessing 模块中的 Queue 类实际上是基于管道(Pipe)和信号量(Semaphore)实现的,可以在多进程之间安全地传递数据。

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

def producer(queue):
for i in range(10):
queue.put(i)
print(f"Producer put {i} into queue.")

def consumer(queue):
while True:
item = queue.get()
if item is None:
break
print(f"Consumer got {item} from queue.")

if __name__ == '__main__':
# 创建一个进程间通信的队列
queue = multiprocessing.Queue()

# 创建一个生产者进程和一个消费者进程
producer_process = multiprocessing.Process(target=producer, args=(queue,))
consumer_process = multiprocessing.Process(target=consumer, args=(queue,))

# 启动进程
producer_process.start()
consumer_process.start()

# 等待生产者进程结束并向队列中添加结束标志
producer_process.join()
queue.put(None)

# 等待消费者进程结束
consumer_process.join()
Pipe - 管道
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 multiprocessing  

def sender(conn):
conn.send("Hello, receiver!")
conn.close()

def receiver(conn):
msg = conn.recv()
print(f"Received message: {msg}")
conn.close()

if __name__ == '__main__':
# 创建管道
parent_conn, child_conn = multiprocessing.Pipe()

# 创建发送者进程和接收者进程
sender_process = multiprocessing.Process(target=sender, args=(parent_conn,))
receiver_process = multiprocessing.Process(target=receiver, args=(child_conn,))

# 启动进程
sender_process.start()
receiver_process.start()

# 等待进程结束
sender_process.join()
receiver_process.join()

多线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成
进程是由若干线程组成的,一个进程至少有一个线程
由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持
由于全局解释器锁(GIL)的存在导致的。GIL是Python解释器中的一个机制,它保证同一时刻只有一个线程在执行Python字节码,这样就导致了在多线程环境中,无法真正实现多线程并行执行,线程之间会出现竞争条件,从而导致线程执行的无序性。

threading

Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

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

# 定义一个函数作为线程的执行体
def thread_task(name):
for i in range(5):
print(f'Thread {name}: {i}') # 打印当前线程名和循环计数值
time.sleep(1) # 线程休眠1秒

# 创建两个线程并启动它们
if __name__ == "__main__":
t1 = threading.Thread(target=thread_task, args=('A',)) # 创建线程t1,调用thread_task函数,传入参数('A',)
t2 = threading.Thread(target=thread_task, args=('B',)) # 创建线程t2,调用thread_task函数,传入参数('B',)

# 启动线程
t1.start() # 启动线程t1,线程会执行thread_task函数
t2.start() # 启动线程t2,线程会执行thread_task函数

# 等待两个线程结束
t1.join() # 等待线程t1结束
t2.join() # 等待线程t2结束

案例

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

# 假设这是你的银行存款
balance = 0
count = 0

# 这个函数用来修改银行存款
def change_it(n):
global balance
# 先加上 n 再减去 n
balance += n
balance -= n

# 这个函数模拟多线程同时对一个变量进行操作
def run_thread(n):
global count
# 循环一千万次
for i in range(10000000):
count += 1
# 对银行存款进行操作
change_it(n)

# 创建两个线程,分别传入参数 5 和 8
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8))

t1.start()
t2.start()

t1.join()
t2.join()

# 打印循环计数和最后的银行存款
'''
当循环次数达到一定高度时,count会小于正常值,balance也不会等于0
'''
print(f"Final count: {count}")
print(f"Final balance: {balance}")

注意项:

  • 全局变量共享问题:多个线程可以访问和修改相同的全局变量,因此需要确保对共享数据的访问是安全的。
  • 线程同步:使用锁或者其他同步机制来避免多个线程同时修改共享资源。
  • GIL(全局解释器锁):Python语言的GIL限制了同一时间只能有一个线程执行Python字节码。因此在CPU密集型任务中,并发性能可能无法得到提升。

Lock - 互斥锁

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

  • 使用方法
1
2
3
4
5
6
7
8
9
10
'''
Lock(互斥锁):最基本的锁类型,同一时刻只有一个线程可以获取锁,其他线程需要等待锁释放后才能获取锁。可以通过 acquire() 获取锁,通过 release() 释放锁。
'''
import threading

lock = threading.Lock()

lock.acquire() # 获取锁
# 临界区
lock.release() # 释放锁
  • 修改上述案例

如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改

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 threading  
# 假定这是你的银行存款:
balance = 0
lock = threading.Lock() # 创建一个锁对象

def change_it(n):
global balance
balance += n
balance -= n

def run_thread(n):
for i in range(10000000):
lock.acquire() # 获取锁 另一个线程需要等待锁释放。保证每次执行change_it函数时只有一个线程
try:
change_it(n)
finally:
lock.release() # 释放锁

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()

print(balance)
  • 创建两个线程交替打印0-100
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
import threading

# 定义一个全局变量作为当前要打印的数字
current_number = 1

# 定义一个锁,用于控制线程输出数字的交替顺序
lock = threading.Lock()

# 定义一个函数作为线程的执行体,负责打印数字
def print_numbers(thread_id):
global current_number

while current_number <= 100:
lock.acquire() # 获取锁
if current_number % 2 == thread_id: # 判断当前线程是否该打印
print(f'Thread-{thread_id}: {current_number}')
current_number += 1
lock.release() # 释放锁

# 创建两个线程并启动它们
t1 = threading.Thread(target=print_numbers, args=(0,))
t2 = threading.Thread(target=print_numbers, args=(1,))

# 启动线程
t1.start()
t2.start()

# 等待两个线程结束
t1.join()
t2.join()

RLock(可重入锁)

  • 使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'''
RLock(可重入锁):允许同一个线程多次获取锁,避免死锁。同一个线程多次调用 acquire() 后,需要对应多次调用 release() 才能释放锁。
'''
import threading

# 创建一个可重入锁对象
rlock = threading.RLock()

# 定义一个函数,使用可重入锁保护临界区
def nested_section():
rlock.acquire()
print("First level of nesting")
rlock.acquire()
print("Second level of nesting")
rlock.release()
rlock.release()

# 创建一个线程并启动
t = threading.Thread(target=nested_section)
t.start()
t.join()

Semaphore(信号量)

  • 使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'''
Semaphore(信号量):允许多个线程同时访问共享资源,但是在给定时刻只有有限数量的线程可以访问。可以通过设置信号量的计数值来控制并发访问数量。
'''
import threading

# 创建一个信号量对象,允许最多同时有两个线程访问资源
semaphore = threading.Semaphore(value=2)

# 定义一个函数,使用信号量限制并发访问数量
def controlled_access():
semaphore.acquire()
print("Inside controlled access section")
semaphore.release()

# 创建多个线程并启动
threads = []
for i in range(4):
t = threading.Thread(target=controlled_access)
threads.append(t)
t.start()

# 等待所有线程结束
for t in threads:
t.join()
  • 案例 - 使用3个线程打印 0-100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import threading
import time

# 定义一个函数,模拟对共享资源的访问
def access_resource(semaphore, resource_id):
semaphore.acquire()
time.sleep(3)
print(f'{resource_id}')
# 模拟对共享资源的访问
semaphore.release()

# 创建一个 Semaphore 对象,并指定初始计数值为 3,表示最多允许 3 个线程访问共享资源
semaphore = threading.Semaphore(value=3)

# 创建多个线程并启动
for i in range(100):
t = threading.Thread(target=access_resource, args=(semaphore, i))
t.start()

# 等待所有线程结束
for t in threading.enumerate():
if t != threading.current_thread():
t.join()

Event(事件)

  • 使用方法
1
2
3
4
5
6
7
8
9
10
11
'''
Event(事件):一种线程间通信的方式,一个线程发送事件或信号,其他线程等待接收事件并进行处理。主要用于线程的同步和通信。
'''
import threading
event = threading.Event()
# 等待事件被设置
event.wait()
# 设置事件
event.set()
# 重置事件
event.clear()
  • 案例 - 使用3个线程打印 0-100,需要在有序输出
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
import threading
import time

# 创建一个 Event,初始状态为 True,用于控制线程打印的顺序
event = threading.Event()
event.set()


# 定义一个函数,模拟对共享资源的访问
def access_resource(semaphore, resource_id):
event.wait() # 等待上一个线程打印完成,Event会阻塞后续线程的开始
semaphore.acquire()
print(f'{resource_id}')
time.sleep(3)
# 模拟对共享资源的访问
semaphore.release()
event.set() # 设置 Event,允许下一个线程开始打印


# 创建一个 Semaphore 对象,并指定初始计数值为 3,表示最多允许 3 个线程访问共享资源
semaphore = threading.Semaphore(value=3)

# 创建多个线程并启动
threads = []
for i in range(1, 101):
t = threading.Thread(target=access_resource, args=(semaphore, i))
threads.append(t)
t.start()

# 等待所有线程结束
for t in threads:
t.join()

线程池

  • 使用方式
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
'''
ThreadPoolExecutor的主要方法:

submit(fn, *args, **kwargs): 提交任务给线程池,并返回一个Future对象,用于获取任务的执行结果。
map(func, *iterables): 类似于内置的map函数,对可迭代的参数应用函数,并返回结果。
shutdown(wait=True): 关闭线程池,等待所有任务执行完成后才关闭。
'''
from concurrent.futures import ThreadPoolExecutor
import time

def task(i):
print(f'Started task {i}')
time.sleep(2)
result = f'Result of task {i}'
print(f'Finished task {i}')
return result

with ThreadPoolExecutor(max_workers=3) as executor: # 最多同时执行3个任务
futures = []
for i in range(1, 6): # 提交5个任务
future = executor.submit(task, i)
futures.append(future)

for future in futures: # 获取任务结果
print(future.result())
  • 案例 - 使用3个线程打印 0-100,需要在有序输出

    在上述案例中,使用Semaphore(信号量),其实是使用了大量线程只是限制了控制并发执行的线程数量

1

多线程队列(Queue)

在Python中,queue 模块中的 Queue 类是线程安全的队列,可以在多线程环境中安全地传递数据。

  • 使用方式
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
import queue  
import threading

# 定义一个函数,用于向队列中放入数据
def put_data(q):
for i in range(5):
q.put(i)

# 定义一个函数,用于从队列中取出数据
def get_data(q):
while not q.empty():
item = q.get()
print(item)

q = queue.Queue()

# 创建两个线程,一个用于放入数据,一个用于取出数据
put_thread = threading.Thread(target=put_data, args=(q,))
get_thread = threading.Thread(target=get_data, args=(q))

put_thread.start()
get_thread.start()

put_thread.join()
get_thread.join()

乐观锁和悲观锁

在并发编程中,乐观锁和悲观锁是两种常用的锁技术,用于处理并发编程时访问共享资源的竞争情况。
悲观锁适用于对共享资源竞争较为激烈的情况,因为获得锁之后,其他线程无法访问共享资源,从而保证数据的一致性;而乐观锁适用于共享资源竞争较少的情况,因为乐观锁不会一直持有锁,可以提高系统的并发性能。

悲观锁 (Pessimistic Locking)

悲观锁的思想是在处理共享资源时,假设其他线程或进程会导致数据不一致或冲突,因此在访问共享资源之前先获取锁,确保对共享资源的独占访问。典型的悲观锁机制包括数据库的行级锁、表级锁等。

在Python中,可以使用数据库的事务锁来实现悲观锁。

乐观锁 (Optimistic Locking)

乐观锁的思想是在处理共享资源时假设没有竞争,多个线程或进程同时访问共享资源,但只在更新时检查资源的版本号或标记是否与自己的相匹配,如果匹配则继续更新,否则放弃更新。乐观锁不会一直持有锁,只在更新时检查共享资源的一致性。

在Python中,可以使用一些标记或版本号来实现乐观锁,比如使用数据库中的版本号字段来检查数据是否被其他线程修改过。

乐观锁&悲观锁 示例

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
mport threading  

# 共享资源
shared_resource = 0
lock = threading.Lock()

# 悲观锁示例
def pessimistic_lock():
with lock:
global shared_resource
shared_resource += 1
print(f"Pessimistic lock - Shared resource value: {shared_resource}")

# 乐观锁示例
def optimistic_lock():
global shared_resource
# 假设获取资源
acquired = True
while acquired:
# 检查资源
old_value = shared_resource
new_value = old_value + 1
# 尝试更新资源
acquired = not shared_resource.compare_and_swap(old_value, new_value)
print(f"Optimistic lock - Shared resource value: {shared_resource}")

def main():
# 演示悲观锁
pessimistic_lock()

# 演示乐观锁
optimistic_lock()

if __name__ == "__main__":
main()

CPU密集型 vs. IO密集型

是否采用多任务的第二个考虑是任务的类型。我们可以把任务分为计算密集型和IO密集型。

  • 计算(CPU)密集型

    计算密集型任务的特点是要进行大量的计算,消耗CPU资源。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低
    I/O短时间内就能完成,CPU需要大量计算。例如:压缩解压,加密解密,正则搜索

  • IO密集型

    IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成。对于IO密集型任务,任务越多,CPU效率越高
    大部分时间CPU在等在I/O读写操作。CPU执行效率低。例如:文件处理程序,网络爬虫,数据库读写程序

异步编程

CPU和IO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。

现代操作系统对IO操作就是支持异步IO。充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型

协程

协程,又称微线程

协程是一种轻量级的并发编程方式,它可以在一个线程中实现多个任务的切换和调度。Python中的协程通过使用asyncio库来实现。

  1. 异步IO操作:协程通常用于处理IO密集型任务,例如网络请求或文件读写。了解如何使用协程来执行异步IO操作,并通过asyncio库中提供的异步IO函数来实现非阻塞的IO操作。
  2. 协程的调度和并发:了解如何使用asyncio库来调度和并发执行多个协程任务。这包括使用asyncio提供的事件循环(event loop)来调度协程的执行顺序,以及使用asyncio中的asyncio.gather()函数来并发执行多个协程任务。
  3. 错误处理和异常处理:了解如何处理协程中可能出现的错误和异常情况。包括使用try-except语句来捕获和处理异常,以及使用asyncio库中提供的错误处理机制来处理协程中的异常情况。

asyncio库

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。asyncio模块内部实现了EventLoop,把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

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
import asyncio
import aiohttp


async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()


async def main():
urls = [
'https://www.baidu.com',
'https://www.taobao.com',
'https://www.jd.com'
]

tasks = [fetch_url(url) for url in urls]

results = await asyncio.gather(*tasks)

for result in results:
print(result)


if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

分布式进程

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。

案例:假设我们有两台机器:Machine A 和 Machine B。原先的多进程程序在 Machine A 上运行,包括发送任务的进程和处理任务的进程。现在我们希望将发送任务的进程迁移到 Machine B 上,与处理任务的进程分开运行。

重点难点

  1. 通信机制更新:需要更新原先基于 Queue 的通信机制,以适应跨机器通信。
  2. 网络配置:确保两台机器能够相互通信,可以考虑使用网络协议如TCP/IP。
  3. 错误处理:处理跨机器通信可能会引入新的错误类型,需要考虑如何处理这些错误情况。
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
# 在 Machine A 上运行的发送任务的进程  
from multiprocessing import Process, Queue
import time
import random

def send_task(queue):
while True:
# 生成一个随机任务
task = random.randint(1, 100)
# 将任务放入队列
queue.put(task)
print(f"Sent task: {task}")
# 等待一段时间再发送下一个任务
time.sleep(random.randint(1, 5))

if __name__ == '__main__':
# 创建一个队列
queue = Queue()
# 创建一个发送任务的进程
send_process = Process(target=send_task, args=(queue,))
# 启动发送任务的进程
send_process.start()

# 在 Machine B 上运行的处理任务的进程
from multiprocessing import Process, Queue
import time

def process_task(queue):
while True:
# 从队列中获取任务
task = queue.get()
print(f"Processing task: {task}")
# 模拟处理任务的时间
time.sleep(5)

if __name__ == '__main__':
# 创建一个队列
queue = Queue()
# 创建一个处理任务的进程
process_process = Process(target=process_task, args=(queue,))
# 启动处理任务的进程
process_process.start()

注意Queue的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,由Worker进程再去共享的磁盘上读取文件。

面向切面编程 (AOP)

面向切面编程是一种编程范式,它允许开发人员在不改变原始代码的情况下,将额外的功能(例如日志记录、性能监控、事务管理等)插入到程序中。通过将这些功能从原始代码中分离出来,AOP提供了一种更好的代码组织方式,并促进了可维护性和可扩展性。

AOP的基本概念

在AOP中,有两个核心概念:切片(Aspect)和连接点(Join Point)。切片是指额外功能的块,而连接点是指程序的特定位置,这些位置可以插入切片。通过在适当的连接点上应用切片,我们可以实现面向切面编程。

在Python中,我们可以使用装饰器(Decorator)来实现AOP。装饰器是Python的一种语法糖,它允许我们在不修改被装饰函数源代码的情况下,为函数添加额外的功能。下面是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with arguments {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper

@log_decorator
def greet(name):
print(f"Hello, {name}!")

greet("Alice")
'''
在上面的示例中,我们定义了一个名为log_decorator的装饰器函数,它接受一个函数作为参数,并返回一个新的函数wrapper。wrapper函数在调用被装饰函数之前打印出函数名称和参数。然后,我们将greet函数用@log_decorator装饰,当我们调用greet("Alice")时,将会输出Calling function greet with arguments ('Alice',), {}和Hello, Alice!。
'''

AOP在实际应用中运用

日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with arguments {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} returned {result}")
return result
return wrapper

@log_decorator
def add(a, b):
return a + b

result = add(2, 3)
print(result)
'''
定义了一个名为log_decorator的装饰器函数,它添加了在函数调用前记录函数名称和参数,以及在函数调用后记录函数名称和返回值的功能。
'''

性能监控

通过在关键点插入性能监控代码,我们可以获取函数的执行时间,以便进行性能分析和优化。

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

def performance_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"Function {func.__name__} executed in {execution_time} seconds")
return result
return wrapper

@performance_decorator
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)

result = fibonacci(10)
print(result)
'''
定义了一个名为performance_decorator的装饰器函数,它添加了在函数调用前记录起始时间,在函数调用后计算执行时间并输出的功能
'''

控制反转 (IOC) | 依赖注入(DI)

pass

python 注释的其他用法

当在编写 Python 代码时,开发人员可能会对代码中的某些部分进行注释,以控制代码静态分析工具的行为或告诉代码审查人员一些信息。

  1. # noqa:F403:忽略 “from module import *” 的错误。

    1
    from module import *  # noqa:F403  
  2. # noqa:F401:忽略模块导入但未使用的警告。

    1
    import module  # noqa:F401  
  3. # type: ignore:用于忽略类型检查器(如 Mypy)的类型错误。

    1
    x: int = "hello"  # type: ignore  
  4. # nosec:用于忽略安全漏洞扫描器(如 Bandit)的安全问题。

    1
    password = "secret"  # nosec  
  5. # type: (type1, type2, ...):指定函数的参数类型和返回值类型。

    1
    def add(a: int, b: int) -> int:  # type: (int, int) -> int  
  6. # flake8: noqa:全局禁用 Flake8 检查。

    1
    # flake8: noqa  

常用库

logging - 日志模块

logging是python自带的日志收集模块

logging库日志级别

默认日志级别为 warning

级别 级别数值 使用时机
DEBUG 10 详细信息,常用于调试
INFO 20 程序正常运行过程中产生的信息
WARNING 30 警告用户,虽然程序还在正常工作,但有可能发生错误
ERROR 40 由于严重问题,程序不能执行一些功能
CRITICAL 50 严重错误,程序已经不能继续运行
1
2
3
4
5
6
7
import logging
# 输出日志信息
logging.debug('This is a debug message')
logging.info('This is an info message') # 设置在INFO,则info以下都会输出
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

logging操作

1
2
3
4
5
6
7
import logging 

# 配置 logging 的日志级别以及输出格式,可以设置日志记录级别、输出格式、处理器(handler)[默认情况下是追加的写入,可以由filemode控制]等
fmt = '%(asctime)s|%(levelname)s|%(message)s|%(filename)%s:%(lineno)%s|%(message)%s'
dateType = "%Y=%m-%d %H:%M:%S"
logFile = 'log.log'
logging.basicConfig(level=logging.INFO, format=fmt, datefmt=dateType, filename=logFile, filemode='w')

logging四大组件

  • 记录器 - logger : 是程序的入口,主要用来记录日志的信息
  • 处理器 - handler :决定了日志的输出目的地
  • 格式器 - formatter :设置日志内容的组成结构和消息字段
  • 过滤器 - filter :提供更好的颗粒度控制,决定那些日志会被输出

日志流程图

配置/创建日志记录器 - logger

1
2
3
4
5
import logging
# 创建日志记录器 logger是单例的由__name__控制
logger = logging.getLogger(__name__)
# 可以通过单独对 logger 对象的 setLevel方法控制记录的日志级别
logger.setLevel(level=logging.INFO)

日志记录器常用方法

1
2
3
4
5
6
7
8
9
# 设置日志级别
logger.setLevel(level=logging.INFO)

# 输出日志信息
logger.debug('This is a debug message')
logger.info('This is an info message') # 设置在INFO,则info以下都会输出
logger.warning('This is a warning message')
logger.error('This is an error message')
logger.critical('This is a critical message')

处理器 - handler

作用:决定日志输出的目的地, 将日志分发到不同的目的地。可以是文件,标准输出,邮件

常用处理器
  • logging.StreamHandler - 将日志信息输出到控制台

    标准输出到stdout 分发器
    创建方法:sh = logging.StreamHandler(stream=None)

  • logging.FileHandler - 将日志信息输出到文件

    创建方法:fh = logging.FileHandler(filename, mode=’a’, encoding=None, delay=False)

  • 其他处理器

常用方法
  • handler.setLevel() - 设置日志级别
  • handler.setFormatter() - 设置当前handler对象使用的消息格式

格式器 - Formatters

Formatter对象用来最终设置日志信息的顺序、结构、内容

使用方法
1
2
3
4
5
fmt = '%(asctime)s|%(levelname)s|%(message)s|%(filename)%s:%(lineno)%s|%(message)%s'
datefmt = "%Y=%m-%d %H:%M:%S"
logger_fmt = logging.Formatter(fmt=fmt, datefmt=datefmt, style=' %')
# datefmt 默认为 '%Y-%m-%d %H:%M:%S'
# style=' %',为匹配名:'%(levelname)s'
formatters 格式
属性 格式 描述
asctime %(asctime)s 日志产生的时间,默认格式为
YYYY-mm-dd HH:MM:SS
created %(created)f time.time()生成的日志创建时间戳
filename %(filename)s 生成日志的程序名
funcName %(funcName)s 调用日志的函数名
levelname %(levelname)s 日志级别
levelno %(levelon)s 日志级别对应的数值
lineno %(lineno)d 日志所针对的代码行号
module %(module)s 生成日志的模块名
msesc %(msecs)d 日志生成时间的毫秒部分
message %(message)s 具体日志信息
name %(name)s 日志调用者
pathname %(pathname)s 生成日志文件的完整路径
process %(process)d 生成日志的进程id
processName %(processName)s 进程名
thread %(thread)d 生成日志的线程id
threadName %(threadName)s 线程名

案例:将日志存储在文本文件内

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 logging
import os
import datetime

# logger 记录器
# 创建日志记录器
logger = logging.getLogger('my_logger')
# 设置日志等级
logger.setLevel(logging.INFO)


# handler 处理器
# 获取当前时间作为文件后缀
current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# 判断运行的根目录下是否有logs文件夹,没有则创建
logs_dir = os.path.join(os.getcwd(), 'logs')
if not os.path.exists(logs_dir):
os.mkdir(logs_dir)
# 在logs文件夹下创建log文件
log_filename = f"logs/log_{current_time}.log"
# 创建文件处理器
file_handler = logging.FileHandler(log_filename)



# formatter 格式器
fmt = '%(asctime)s|%(levelname)s|%(message)s|%(filename)%s:%(lineno)%s|%(message)%s'
datefmt = "%Y=%m-%d %H:%M:%S"
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt, style=' %')


# 将格式器(formatter)放入处理器(handler)
file_handler.setFormatter(formatter)
# 将处理器(handler)放入记录器(logger)
logger.addHandler(file_handler)


# 结束和清理
# 去除处理器(handler)
logger.removeHandler(file_handler)
# 关闭处理器(handler)
file_handler.close()

日志模块封装

对日志模块进行封装可以让代码更加模块化和易于维护,也方便其他模块调用

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
import logging  
import os
import datetime

class CustomLogger:
def __init__(self, log_dir='logs', log_level=logging.INFO):
# 初始化日志记录器和设置日志级别
self.logger = logging.getLogger('my_logger')
self.logger.setLevel(log_level)
self.formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

# 获取当前时间并创建日志文件夹
current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
logs_dir = os.path.join(os.getcwd(), log_dir)
if not os.path.exists(logs_dir):
os.mkdir(logs_dir)

# 设置日志文件路径
log_filename = os.path.join(logs_dir, f"log_{current_time}.log")

# 创建文件处理器并添加格式化器
file_handler = logging.FileHandler(log_filename)
file_handler.setFormatter(self.formatter)
self.logger.addHandler(file_handler)

def info(self, message):
# 记录INFO级别日志
self.logger.info(message)

def warn(self, message):
# 记录WARNING级别日志
self.logger.warning(message)

def error(self, message):
# 记录ERROR级别日志
self.logger.error(message)

def critical(self, message):
# 记录CRITICAL级别日志
self.logger.critical(message)

os & sys - 系统调用

os

获取当前python脚本目录路径 - os.getcwd()
返回指定目录的所有文件和目录名 - os.listdir()
删除一个文件 - os.remove()
删除多个目录 - os.removedirs(r”c:\python”)
检验路径是否是一个文件 - os.path.isfile()
检验给出的路径是否是一个目录 - os.path.isdir()
判断会否是绝对路径 - os.path.isabs()
检验地址是否存在 - os.path.exists()
返回一个目录名和文件名 - os.path.split(‘/user/local/test.py’) // (‘/user/local’, ‘test.py’)
分离扩展名 - os.path.splitext(‘/user/local/test.py’) // 返回 (‘/user/local/test’, ‘py’)
获取路径名 - os.path.dirname()
获取文件名 - os.path.dirname()
获取绝对路径 - os.path.abspath()
获取文件名 - os.path.basename()
运行shell命令 - os.system()
获取操作系统环境变量HOME的值 - os.getenv(“HOME”)
返回操作系统重所有的环境变量 - os.environ
设置系统环境变量,仅在该次程序运行时有效 - os.environ.setdefault(‘HOME’, ‘/home/alex’)
给当前平台使用行终止符 - os.linesep // win 使用 ‘\r\n’ ; Linux and Mac 使用 ‘\n’
指示当前使用平台 - os.name // win 是 ‘nt’ ;Linux and Mac 是’posix’
文件重命名 - os.rename(old, new)
创建单级目录 - os.mkdir(‘test’)
创建多级目录 - os.makedirs(r’c:\python\test’)
获取文件属性 - os.stat(file)
修改文件权限与时间戳 - os.chmod(file)
获取文件大小 - os.path.join(dir, filename)
改变工作目录到dirname - os.chdir(dirname)
获取当前终端的大小 - os.get_terminal_size()
杀死进程 - os.kill(10884, signal.SIGKILL)

sys

获取命令行参数List,第一个为为程序本身路径 - sys.argv
退出程序,正常退出为exit(0) - sys.exit(n)
获取python解释器版本信息 - sys.maxint
最大Int值 - sys.maxint
返回模块的搜索路径,初始化时使用PYTHONPATH 环境变量的值 - sys.path
返回操作系统平台名称 - sys.platform
标准输出 - sys.stdout.write(‘please:’) // 可以用print代替
标准输入 - sys.stdin.readline()[:-1]
获取最大递归层数 - sys.getrecursionlimit()
设置最大递归层数 - sys.setrecursionlimit(1000)
设置解释器默认编码 - sys.getdefaultencoding()
获取内存数据存在文件里的默认编码 - sys.getfilesystemcoding

datetime & time - 时间处理模块

平常代码中,常常与时间打交道。在python中,与时间处理有关的模块包括:time,datetime,calendar(少用)

一般时间处理归类为:

  1. 时间显示
  2. 时间转换
  3. 时间运算

time

返回当前时间的时间戳 - time.time()
将线程推迟指定时间运行 - time.sleep() // 单位s
将时间戳转换为当前时区的struct_time输出 - time.localtime()
1
2
3
4
time.struct_time()
# time.struct_time(tm_year=2024, tm_mon=10, tm_mday=20, tm_hour=14, tm_min=22, tm_sec=10, tm_wday=6, tm_yday=294, tm_isdst=0)
time.asctime()
# 'Sun Oct 20 14:24:00 2024'
日期格式化 - time.strftime(“%Y-%m-%d %H:%M:%S %p:%j”,time.localtime())
1
2
3
time.strftime("%Y-%m-%d %H:%M:%S %p/%j", time.localtime())
# 把time.localtime()的时间格式 转换成自定义格式
# '2024-10-20 14:27:09 PM/294'
将指定日期转换成 localtime - time.strptime()
1
2
time.strptime("2024-04-01 12:12:12 PM","%Y-%m-%d %H:%M:%S %p")
# time.struct_time(tm_year=2024, tm_mon=4, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=92, tm_isdst=-1)

datetime

相比于time模块,datetime模块的接口更直观,更容易调用

datetime模块定义了下面几个类:

  • datetime.date:表示日期的类,常用属性有year,month,day
  • datetime.time:表示时间的类,常用属性有hour,minute,second,microsecond
  • datetime.datetime:表示日期时间
  • datetime.timedelta:表示时间间隔,即两个时间点之间的长度
  • datetime.tzinfo:与时区有关的相关信息。
返回当前时间 - datetime.datetime.now()
1
2
3
4
5
6
7
8
9
10
11
d = datetime.datetime.now()
# datetime.datetime(2024, 10, 20, 16, 40, 15, 733180)
# 天
d.timestamp() # 返回d的时间戳
d.year # 年
d.month # 月
d.day # 日
d.hour # 时
d.mintue # 分
d.second # 秒
d.timetuple() # 返回 time.struct_time格式 time.struct_time(tm_year=2024, tm_mon=10, tm_mday=20, tm_hour=16, tm_min=45, tm_sec=36, tm_wday=6, tm_yday=294, tm_isdst=-1)
把时间戳
返回的时间加上相应时间 - datetime.datetime.now() + datetim.timedelta(4) // 当前时间+4天
  • 默认(天)
  • datetime.datetime.now() + datetim.timedelta(hours=4) // 当前时间+4小时
时间替换 - d.replace(year=2999, month=11, day=30)
1
2
d.replace(year=2999, month=11, day=30)
# datetime.datetime(2999, 11, 30)
时区 - xxx

random

返回1-10之间的随机数,不包括10 - random(1,10)
返回1-10之间的随机数,包括10 - random.randint(1,10)
随机选取0-100的偶数 - random.randrange(0, 100, 2)
返回一个浮点数 - random.random()
返回给定集合中的随机字符 - random.choice(‘abcd3#$%@123’)
从多个字符中选取特定数量的字符 - random.sample(‘abcdefg’, 3)
生成6位随机数
1
2
3
import string,random
''.join(random.sample(string.ascli_lowercase + string.digits, 6))
# 返回6位随机数
洗牌
1
2
a = [0,1,2,3,4,5,6,7]
random.shuffle(a)

pickle

持续化模块:就是让数据持久化保存

将python数据转换并保存在pickle格式的文件内 - pickle.dump(obj, file)

1
2
3
# 将date转换成字节流并写入文件 
with open('data.pkl', 'wb') as f:
pickle.dump(data, f)

将Python数据转换为pickle格式的bytes字串 - pickle.dumps(obj)

1
2
3
4
5
6
7
import pickle
dic = {"k1":"v1","k2":123}
s = pickle.dumps(dic) # 将对象序列化为字节流
print(s)
# 将字节流写入文件
with open('data.pkl', 'wb') as file:
file.write(serialized_data)

从pickle格式的文件中读取数据并转换为Python的类型 - pickle.load(file)

1
2
3
# 从文件中读取字节流并反序列化为对象  
with open('data.pkl', 'rb') as file:
deserialized_data = pickle.load(file)

将pickle格式的bytes字串转换为Python的类型 - pickle.loads(bytes_object)

1
2
3
4
# 假设以下字节流是通过pickle.dumps()序列化得到的  
serialized_data = b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x05Alice\x94\x8c\x03age\x94K\x1e\x8c\x04city\x94\x8c\x08New York\x94u.'
# 使用pickle.loads()将字节流反序列化为原始对象
deserialized_data = pickle.loads(serialized_data)

json vs pickle

可以使用json模块来处理JSON数据。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于

将Python对象转换为JSON字符串 - json.dumps(data)

1
2
3
4
5
6
7
8
data = {  
"name": "Alice",
"age": 30,
"city": "New York"
}

json_str = json.dumps(data)
print(json_str) # {"name": "Alice", "age": 30, "city": "New York"}

将JSON字符串转换为Python对象 - json.loads(json_str)

1
2
3
4
json_str = '{"name": "Alice", "age": 30, "city": "New York"}'  

data = json.loads(json_str)
print(data) # {'name': 'Alice', 'age': 30, 'city': 'New York'}

读取JSON文件并解析 - json.load(f)

1
2
3
4
with open('data.json', 'r') as f:  
data = json.load(f)

print(data)

将Python对象写入JSON文件 - json.dump(data, f)

1
2
3
4
5
6
7
8
data = {  
"name": "Bob",
"age": 25,
"city": "Los Angeles"
}

with open('output.json', 'w') as f:
json.dump(data, f)

hashlib sha - 加密模块

hashlib

创建一个hashlib对象并计算SHA-256哈希值
1
2
3
4
5
6
data = b"Hello, World!"  # 要加密的数据,需转换为字节字符串  

hash_object = hashlib.sha256(data) # 创建一个SHA-256对象
hex_dig = hash_object.hexdigest() # 计算哈希值并以十六进制表示

print(hex_dig) # '4b5d302c081d93e045ca566f6a71c3d1e2bb61f32047c472aeac7b27937035e0'

各种SHA算法的支持

1
2
3
4
5
6
7
8
9
data = b"Hello, World!"  

hash_sha1 = hashlib.sha1(data)
hash_sha256 = hashlib.sha256(data)
hash_sha512 = hashlib.sha512(data)

print("SHA-1:", hash_sha1.hexdigest())
print("SHA-256:", hash_sha256.hexdigest())
print("SHA-512:", hash_sha512.hexdigest())
计算文件的SHA-256哈希值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import hashlib  # 导入hashlib模块,用于计算哈希值  

def sha256_checksum(filename, block_size=65536):
# 定义一个函数用于计算文件的SHA-256哈希值
# 参数filename为要计算哈希值的文件名,block_size为每次读取文件的块大小,默认为64KB

hash_object = hashlib.sha256() # 创建一个SHA-256哈希对象

with open(filename, 'rb') as file:
# 打开文件,以二进制只读模式打开

for block in iter(lambda: file.read(block_size), b''):
# 以块的形式读取文件数据,并更新哈希对象

hash_object.update(block) # 更新哈希对象

return hash_object.hexdigest() # 返回计算得到的SHA-256哈希值

file_hash = sha256_checksum('example.txt') # 调用函数计算文件的SHA-256哈希值
print(file_hash) # 打印计算得到的哈希值
大数据分块计算哈希值
1
2
3
4
5
6
7
8
9
10
11
12
# 对于大数据,可以分块计算哈希值,以降低内存使用量  
data = b"Hello, World!" # 待计算哈希值的数据,需要转换为字节字符串

hash_object = hashlib.sha256() # 创建一个SHA-256哈希对象

# 将数据拆分为大小为2的数据块,并逐个更新哈希对象
for chunk in [data[i:i+2] for i in range(0, len(data), 2)]:
hash_object.update(chunk)

hex_dig = hash_object.hexdigest() # 计算最终的SHA-256哈希值

print(hex_dig) # 打印计算得到的哈希值

shuti 模块,文件的复制、打包和压缩操作

文件复制

1
2
3
4
5
import shutil  
# 复制文件
shutil.copy('source_file.txt', 'destination_file.txt')
# 复制文件夹
shutil.copytree('source_directory', 'destination_directory')

文件打包(使用 tarfilezipfile)

1
2
3
4
5
6
7
8
9
10
11
12
13
import shutil  
import tarfile
import zipfile

# 创建 Tar 文件
with tarfile.open('archive.tar', 'w') as tar:
tar.add('file1.txt')
tar.add('file2.txt')

# 创建 Zip 文件
with zipfile.ZipFile('archive.zip', 'w') as zip:
zip.write('file1.txt')
zip.write('file2.txt')

文件压缩

1
2
3
4
5
6
7
8
import shutil  

# 压缩文件夹(使用 zipfile 或 tarfile)
shutil.make_archive('archive', 'zip', 'source_directory')
shutil.make_archive('archive', 'tar', 'source_directory')

# 解压缩文件夹
shutil.unpack_archive('archive.zip', 'destination_directory')

正则 - re

什么是正则表达式

  正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。

正则表达式可以干什么

  • 快速高效的查找与分析字符串
  • 进行有规律查找比对字符串,也叫:模式匹配
  • 具有查找、比对、匹配、替换、插入、添加、删除等能力。

re模块

Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有全部的正则表达式功能。

由于Python的字符串本身也用\转义,所以要特别注意:

1
2
3
s = 'ABC\\-001' # Python的字符串
# 对应的正则表达式字符串变成:
# 'ABC\-001'

因此建议使用Python的r前缀,就不用考虑转义的问题了:

1
2
3
s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'

正则表达式模式

使用特殊符号表示字符:用\d可以匹配一个数字,\w可以匹配一个字母或数字,例如:

  • '00\d'可以匹配'007',但无法匹配'00A'
  • '\d\d\d'可以匹配'010'
  • '\w\w\d'可以匹配'py3'。

'.'可以匹配任意字符,所以:

  • 'py.'可以匹配'pyc''py3''py!'等等。

要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符。

来看一个复杂的例子:\d{3}\s+\d{3,8}

我们来从左到右解读一下:

  1. \d{3}表示匹配3个数字,例如'010'
  2. \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' '' '等;
  3. \d{3,8}表示3-8个数字,例如'1234567'

综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。

如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8}

但是,仍然无法匹配'010 - 12345',因为带有空格。所以我们需要更复杂的匹配方式。

进阶

要做更精确地匹配,可以用[]表示范围,比如:

  • [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
  • [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100''0_Z''Py3000'等等;
  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
  • [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'

^表示行的开头,^\d表示必须以数字开头。

$表示行的结束,\d$表示必须以数字结束。

py也可以匹配'python',但是加上^py$就变成了整行匹配,就只能匹配'py'了。

re模常用方法

导入模块
1
import re
匹配多种可能 - []
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
#'run' or 'ran'
res = re.search(r'r[au]n','dog runs to cat')
print(res)
>>> <re.Match object; span=(4, 7), match='run'>

res = re.search(r'r[au]n','dog rans to cat')
print(res)
>>> <re.Match object; span=(4, 7), match='ran'>

#continue 匹配更多种可能
res = re.search(r'r[A-Z]n','dog rans to cat')
print(res)
>>> None

res = re.search(r'r[a-z]n','dog rans to cat')
print(res)
>>> <re.Match object; span=(4, 7), match='ran'>

res = re.search(r'r[0-9]n','dog rans to cat')
print(res)
>>>None

res = re.search(r'r[0-9a-z]n','dog rans to cat')
print(res)
>>> <re.Match object; span=(4, 7), match='ran'>
匹配数字 - \d or \D
1
2
3
4
5
6
7
8
9
# \d : decimal digit 数字的
res = re.search(r'r\dn','run r9n')
print(res)
>>> <re.Match object; span=(4, 7), match='r9n'>

# \D : any non-decimal digit 任何不是数字的
res = re.search(r'r\Dn','run r9n')
print(res)
>>> <re.Match object; span=(0, 3), match='run'>
匹配空白 - \s or \S
1
2
3
4
5
6
7
8
9
# \s : any white space [\t \n \r \f \v]
res = re.search(r'r\sn','r\nn r9n')
print(res)
>>> <re.Match object; span=(0, 3), match='r\nn'>

# \S : 和\s相反,any non-white space
res = re.search(r'r\Sn','r\nn r9n')
print(res)
>>> <re.Match object; span=(4, 7), match='r9n'>
匹配所有字母和数字以及 ‘_’ - \w or \W
1
2
3
4
5
6
7
8
9
# \w : [a-zA-Z0-9_]
res = re.search(r'r\wn','r\nn r9n')
print(res)
>>> <re.Match object; span=(4, 7), match='r9n'>

# \W : opposite to \w 即与\w相反
res = re.search(r'r\Wn','r\nn r9n')
print(res)
>>> <re.Match object; span=(0, 3), match='r\nn'>
匹配空白字符串 - \b or \B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# \b : (only at the start or end of the word)
res = re.search(r'\bruns\b','dog runs to cat')
print(res)
>>> <re.Match object; span=(4, 8), match='runs'>
res = re.search(r'\bruns\b','dogrunsto cat')
print(res)
>>> None

# \B : ( but not at the start or end of the word)
res = re.search(r'\Bruns\B','dog runs to cat')
print(res)
>>> None
res = re.search(r'\Bruns\B','dogrunsto cat')
print(res)
>>> <re.Match object; span=(5, 11), match=' runs '>
匹配任意字符 特殊字符 - \ or .
1
2
3
4
5
6
7
8
9
10
11
12
# \\ : 匹配 \
res = re.search(r'runs\\','dog runs\ to cat')
print(res)
>>> <re.Match object; span=(4, 9), match='runs\\'>

# . : 匹配 anything (except \n)
res = re.search(r'r.ns','dog r;ns to cat')
print(res)
>>> <re.Match object; span=(4, 8), match='r;ns'>
>res = re.search(r'r.ns','dog r\nns to cat')
print(res)
>>> None
匹配句首句尾 - $ or ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ^ : 匹配line beginning
res = re.search(r'^runs','dog runs to cat')
print(res)
>>> None
res = re.search(r'^dog','dog runs to cat')
print(res)
>>> <re.Match object; span=(0, 3), match='dog'>

# $ : 匹配line ending
res = re.search(r'runs$','dog runs to cat')
print(res)
>>> None
res = re.search(r'cat$','dog runs to cat')
print(res)
>>> <re.Match object; span=(12, 15), match='cat'>
是否匹配 - ?
1
2
3
4
5
6
7
8
# ? : may or may nt occur
res = re.search(r'r(u)?ns','dog runs to cat')
print(res)
>>> <re.Match object; span=(4, 8), match='runs'>

res = re.search(r'r(u)?ns','dog rns to cat')
print(res)
>>><re.Match object; span=(4, 7), match='rns'>
多行匹配 - re.M
1
2
3
4
5
6
7
8
9
10
11
12
# 匹配代码后面加上re.M
string = """ 123.
dog runs to cat.
You run to dog.
"""
res = re.search(r'^You',string)
print(res)
>>> None

res = re.search(r'^You',string,re.M)
print(res)
>>> <re.Match object; span=(10, 13), match='run'>
匹配零次或多次 - *
1
2
3
4
5
6
7
8
# * : occur 0 or more times
res = re.search(r'ab*','a')
print(res)
>>> <re.Match object; span=(0, 1), match='a'>

res = re.search(r'ab*','abbbbbbbbbb')
print(res)
>>> <re.Match object; span=(0, 11), match='abbbbbbbbbb'>
匹配一次或多次 - +
1
2
3
4
5
6
7
8
# + :occur 1 or more times
res = re.search(r'ab+','a')
print(res)
>>> None

res = re.search(r'ab+','abbbbbbbbbb')
print(res)
>>> <re.Match object; span=(0, 11), match='abbbbbbbbbb'>
可选匹配次数 - {n, m}
1
2
3
4
5
6
7
8
# {n, m} : occur n to m times
res = re.search(r'ab{1,10}','a')
print(res)
>>> None

res = re.search(r'ab{1,10}','abbbbbbbbbb')
print(res)
>>> <re.Match object; span=(0, 11), match='abbbbbbbbbb'>
匹配多个正则公式,利用group添加命名输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# group
res = re.search(r'ID: (\d+), Name: (.+)','ID: 123456789, Name: a/b*c;d')
print(res.group())
print(res.group(1)) # 利用1, 2 标识顺序
print(res.group(2))
>>> ID: 123456789, Name: a/b*c;d
>>> 123456789
>>> a/b*c;d

# 给group组命名 ?P<name>
res = re.search(r'ID: (?P<id>\d+), Name: (?P<name>.+)','ID: 123456789, Name: a/b*c;d')
print(res.group('id'))
print(res.group('name'))
>>> 123456789
>>> a/b*c;d
去除惰性,寻找所有匹配 - findall
1
2
3
4
5
6
7
8
9
# re.findall()
res = re.findall(r'r[ua]n','run ran ren')
print(res)
>>> ['run', 'ran']

# 另一种写法
res = re.findall(r'(run|ran)','run ran ren')
print(res)
>>> ['run', 'ran']
替换匹配内容 - sub
1
2
3
4
# re.sub(   ,replace,   )
res = re.sub(r'runs','catches','dog runs to cat')
print(res)
>>> dog catches to cat
分裂匹配内容 - split
1
2
3
4
# re.split()
res = re.split(r'[,;\.\\]', 'a,b;c.d\e')
print(res)
>>> ['a', 'b', 'c', 'd', 'e']
将正则匹配规则进行包装 - compile
1
2
3
4
5
# re. compile()
compile_re = re.compile(r'r[ua]n')
res = compile_re.findall('run ran ren')
print(res)
>>> ['run', 'ran']

一些常见的正则表达式

校验数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1、数字:^[0-9]*$
2、n位的数字:^\d{n}$
3、至少n位的数字:^\d{n,}$
4、m-n位的数字:^\d{m,n}$
5、零和非零开头的数字:^(0|[1-9][0-9]*)$
6、非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
7、带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
8、正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
9、有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
10、有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
11、非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
12、非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
13、非负整数:^\d+$ 或 ^[1-9]\d*|0$
14、非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
15、非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
16、非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
17、正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
18、负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
19、浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
校验字符
1
2
3
4
5
6
7
8
9
10
11
12
1、汉字:^[\u4e00-\u9fa5]{0,}$
2、英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3、长度为3-20的所有字符:^.{3,20}$
3、由26个英文字母组成的字符串:^[A-Za-z]+$
5、由26个大写英文字母组成的字符串:^[A-Z]+$
6、由26个小写英文字母组成的字符串:^[a-z]+$
7、由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8、由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9、中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
10、中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11、可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
12、禁止输入含有~的字符:[^~\x22]+
特殊需求
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
1、Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
2、域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3、 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
4、手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
5、电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6、国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7、身份证号(15位、18位数字):^\d{15}|\d{18}$
8、短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
9、帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
10、密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
11、强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
12、日期格式:^\d{4}-\d{1,2}-\d{1,2}
13、一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
14 、一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
15、钱的输入格式:
* 有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
* 这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
* 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
* 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
* 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
* 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
* 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
* 1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
* 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可
* 以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
16、xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
17、中文字符的正则表达式:[\u4e00-\u9fa5]
18、双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
19、空白行的正则表达式:\n\s*\r (可以用来删除空白行)
20、HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
21、首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
22、 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
23、中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
24、IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
25、 IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

Pandas

Pandas 是一个热门的 Python 数据处理库,它提供了用于数据处理和分析的强大工具。

导入 Pandas:

1
import pandas as pd  

创建 Pandas 数据结构:

  • 创建 Series:

    • Series 是一维标记数组,可以存储任意数据类型的元素。
    • Series 由两部分组成:索引(index)和数据(data)
    • Series 也支持向量化操作、标量运算、缺失值处理等
    1
    2
    3
    4
    5
    6
    7
    import pandas as pd  
    data = [1, 2, 3, 4, 5]
    series = pd.Series(data)
    # 获取数据方法
    series[0] # 访问第一个元素
    series.loc[1] # 使用标签访问元素
    series.iloc[2] # 使用位置访问元素
    • 属性和方法
      • values:返回 Series 中的数据部分,以 Numpy 数组的形式呈现。
      • index:返回 Series 中的索引部分。
      • size:返回 Series 中的数据个数。
      • shape:返回 Series 的形状,是一个元组,包含行数和列数。
      • head(n):返回 Series 的前 n 行数据,默认是前5行。
      • tail(n):返回 Series 的后 n 行数据,默认是后5行。
      • describe():返回 Series 数据的基本统计信息,如平均值、标准差、最小值、最大值等。
  • 创建 DataFrame:

    • DataFrame 是一个二维的带有行和列标签的数据结构,类似于电子表格或关系型数据库。
    • DataFrame 包含了行索引(index)、列索引(columns)和数据。
    • DataFrame 通常用于处理结构化数据,支持合并、分组、筛选、计算等操作
    1
    2
    3
    4
    5
    6
    7
    data = {'Name': ['Alice', 'Bob', 'Charlie'],  
    'Age': [25, 30, 35]}
    df = pd.DataFrame(data)
    # 获取数据方法
    df['Name'] # 访问'Name'列
    df.iloc[0] # 访问第一行数据
    df.loc[1, 'Age'] # 访问第二行'Age'列数据
    • 属性和方法
      • columns:返回 DataFrame 的列标签,以 Index 对象的形式呈现。
      • index:返回 DataFrame 的行标签,以 Index 对象的形式呈现。
      • values:返回 DataFrame 中的数据部分,以 Numpy 数组的形式呈现。
      • shape:返回 DataFrame 的形状,是一个元组,包含行数和列数。
      • head(n):返回 DataFrame 的前 n 行数据,默认是前5行。
      • tail(n):返回 DataFrame 的后 n 行数据,默认是后5行。
      • describe():返回 DataFrame 数据的基本统计信息,如平均值、标准差、最小值、最大值等。
      • info():返回 DataFrame 的基本信息,包括列名、非空值个数、数据类型等。
      • loc[]:通过标签选择数据,可以指定行和列。
      • iloc[]:通过位置选择数据,可以指定行和列。

读取和写入数据:

  • 从 CSV 文件读取数据:

    1
    df = pd.read_csv('data.csv')  
  • 将数据写入 CSV 文件:

    1
    df.to_csv('new_data.csv', index=False)  
  • 读取 Excel 文件:

    1
    df = pd.read_excel('data.xlsx')  
  • 写入 Excel 文件:

    1
    df.to_excel('new_data.xlsx', index=False)  
  • 从 SQL 数据库读取数据:

    1
    2
    3
    4
    5
    import sqlite3  
    conn = sqlite3.connect('database.db')
    query = 'SELECT * FROM table_name;'
    df = pd.read_sql(query, conn)
    conn.close()
  • 将数据写入 SQL 数据库:

    1
    df.to_sql('table_name', conn, if_exists='replace', index=False)  
  • JSON 文件:

    • 读取 JSON 文件:

      1
      df = pd.read_json('data.json')  
    • 写入 JSON 文件:

      1
      df.to_json('new_data.json', orient='records')

数据选择和过滤:

  • 选择某一列数据:

    1
    df['column_name']  
  • 根据条件筛选数据:

    1
    df[df['Age'] > 30]

数据操作:

  • 添加新列:

    1
    df['Salary'] = [50000, 60000, 70000]  
  • 根据条件修改数据:

    1
    df.loc[df['Age'] > 30, 'Salary'] *= 1.1 

缺失值处理:

  • 删除包含缺失值的行:

    1
    df.dropna()  
  • 填充缺失值:

    1
    df.fillna(0)  

数据统计和计算:

  • 计算平均值:

    1
    df['Age'].mean()  
  • 按列分组计算统计量:

    1
    df.groupby('Department')['Salary'].mean() 

可视化:

  • 绘制直方图:

    1
    df['Age'].plot(kind='hist')  
  • 绘制散点图:

    1
    df.plot.scatter(x='Age', y='Salary')  

数据库操作

SQLite

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。

Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

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

class SQLiteConnection:
def __enter__(self):
# 连接到SQLite数据库
# 数据库文件是test.db
# 如果文件不存在,会自动在当前目录创建
self.conn = sqlite3.connect('test.db')
# 创建一个Cursor
self.cursor = self.conn.cursor()
return self.cursor

def __exit__(self, exc_type, exc_val, exc_tb):
# 关闭Cursor
if self.cursor:
self.cursor.close()
# 提交事务并关闭连接
if self.conn:
self.conn.commit()
self.conn.close()

with SQLiteConnection() as cursor:
# 插入一条记录
cursor.execute('insert into user (id, name) values (?, ?)', ('2', 'Alice'))
print(cursor.rowcount)

with SQLiteConnection() as cursor:
# 更新记录
cursor.execute('update user set name = ? where id = ?', ('Bob', '2'))
print(cursor.rowcount)

with SQLiteConnection() as cursor:
# 删除记录
cursor.execute('delete from user where id = ?', ('2',))
print(cursor.rowcount)

MySQL

安装mysql驱动

1
pip install mysql-connector-python 

使用案例

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
import mysql.connector


class MySQLConnection:
def __enter__(self):
# 建立数据库连接
self.conn = mysql.connector.connect(user='root', password='root', database='blog')
# 创建游标对象
self.cursor = self.conn.cursor()
return self.cursor

def __exit__(self, exc_type, exc_val, exc_tb):
# 关闭游标
if self.cursor:
self.cursor.close()
# 提交事务并关闭连接
if self.conn:
self.conn.commit()
self.conn.close()


with MySQLConnection() as cursor:
# 创建user表
cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 插入一行记录
cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
print(cursor.rowcount)

with MySQLConnection() as cursor:
# 查询记录
cursor.execute('select * from t_user where id = %s', ('1',))
row = cursor.fetchone()
columns = [col[0] for col in cursor.description] # 获取列名
data = dict(zip(columns, row)) # 将列名和数据对应转换成字典
print(data)

SQLAlchemy

连接已有数据库

连接mysql需要额外安装mysql驱动 mysql-connector-python , 才能成功连接

1
pip install mysql-connector-python  
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
from sqlalchemy import Column, String, create_engine, DateTime, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base

# 创建对象的基类:
Base = declarative_base()


# 定义User对象:
class User(Base):
__tablename__ = 't_user'

# 数据库表结构 可以在mysql中DESCRIBE t_user; 查看表结构
id = Column(String(20), primary_key=True)
avatar = Column(String(255))
create_time = Column(DateTime)
email = Column(String(255))
nickname = Column(String(255))
password = Column(String(255))
type = Column(Integer)
update_time = Column(DateTime)
username = Column(String(255))


# 初始化数据库连接:
engine = create_engine('mysql+mysqlconnector://root:root@localhost:3306/blog')
# 创建DBSession类型:
DBSession = sessionmaker(bind=engine)

查找数据

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
## 所有查找 等同于 select * from t_user;
# 创建会话
session = DBSession()
# 查询所有用户数据
users = session.query(User).all()
# 打印用户数据
for user in users:
print(user.id, user.username) # 根据实际列名打印相应的数据
# 关闭会话
session.close()

## 过滤查找 等同于 select * from t_user where id='admin';
# 创建会话
session = DBSession()
# 查询用户名为 "admin" 的用户
user = session.query(User).filter_by(username="admin").first()
'''
.first() 是 SQLAlchemy 查询的方法,用于获取查询结果中的第一个对象。如果查询返回多个结果,则只返回第一个匹配的对象;如果查询没有找到匹配项,则返回 None。
如果要匹配多个结果,你可以使用 .all() 方法来获取查询返回的所有对象。
users = session.query(User).filter_by(username="example_username").all()
for user in users:
print(user.id, user.avatar, user.email)
'''
if user:
print(user.id, user.avatar, user.email)
# 关闭会话
session.close()

更新数据

1
2
3
4
5
6
7
8
9
10
11
12
# 创建会话  
session = DBSession()

# 查询需要更新的用户
user = session.query(User).filter_by(username="admin").first()

if user:
user.nickname = "admin123" # 更新昵称
session.commit() # 提交更改

# 关闭会话
session.close()

新增数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建会话
session = DBSession()

# 创建一个新用户对象并填充所有字段
new_user = User(
avatar='https://example.com/avatar.jpg',
create_time=datetime.utcnow(),
email='new_email@example.com',
nickname='New User',
password='new_password123',
type=2,
update_time=datetime.utcnow(),
username='new_username'
)

# 将新用户对象添加到会话中
session.add(new_user)

# 提交更改至数据库
session.commit()

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
# 创建会话  
session = DBSession()

# 查询需要删除的用户
user = session.query(User).filter_by(username="admin").first()

if user:
session.delete(user) # 删除用户
session.commit() # 提交更改

# 关闭会话
session.close()

多表查询

1
2
3
4
5
6
7
8
9
10
11
12
# 连接 User, UserPost, 和 Post 表  
query = session.query(User).join(User.posts).join(Post)

# 添加查询条件
query = query.filter(User.username == 'admin')

# 获取查询结果
results = query.all()

# 遍历结果并访问属性
for user, post in results:
print(user.username, post.title, post.content)

匹配以特定字符串开头的值:

1
2
query = session.query(User).filter(User.username.like('prefix%'))  
# 这将返回所有用户名以 "prefix" 开头的用户。

匹配以特定字符串结尾的值:

1
2
query = session.query(User).filter(User.username.like('%suffix')) 
# 这将返回所有用户名以 "suffix" 结尾的用户。

匹配包含特定字符串的值:

1
2
query = session.query(User).filter(User.username.like('%keyword%'))  
# 这将返回所有用户名中包含 "keyword" 的用户。

匹配特定长度的字符串:

1
2
query = session.query(User).filter(User.username.like('___'))  
# 这将返回用户名长度为 3 的用户。

区间查询:

1
2
query = session.query(User).filter(User.age.between(18, 30))  
# 这将返回所有年龄在 1830 岁之间的用户。

区间排序:

1
2
3
4
5
query = session.query(User).order_by(User.age.asc())    
# 这将返回按年龄升序排列的所有用户。

query = session.query(User).order_by(User.age.desc())
# 这将返回按年龄降序排列的所有用户。

回滚操作

1
2
3
4
5
6
7
# 回滚操作示例  
try:
# 执行一些数据库操作
session.commit()
except:
# 如果出现异常,执行回滚操作
session.rollback()

函数操作

1
2
3
4
5
6
7
# 函数操作示例  
def get_user_by_id(user_id):
return session.query(User).filter(User.id == user_id).first()

# 使用函数获取用户对象
user = get_user_by_id(3)
print(user.username)

缓存

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
from sqlalchemy import Column, String, create_engine, DateTime, Integer, Index  
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base

# 创建对象的基类:
Base = declarative_base()

# 定义User对象:
class User(Base):
__tablename__ = 't_user'

# 数据库表结构
id = Column(String(20), primary_key=True)
avatar = Column(String(255))
create_time = Column(DateTime)
email = Column(String(255))
nickname = Column(String(255))
password = Column(String(255))
type = Column(Integer)
update_time = Column(DateTime)
username = Column(String(255))

# 初始化数据库连接:
engine = create_engine('mysql+mysqlconnector://root:root@localhost:3306/blog')
# 创建DBSession类型:
DBSession = sessionmaker(bind=engine)

# 缓存操作示例(需要第三方库支持):
from flask_caching import Cache

cache = Cache()

@cache.memoize(timeout=60)
def get_user_by_id(user_id):
return DBSession().query(User).filter(User.id == user_id).first()

user1 = get_user_by_id(1) # 第一次查询会被缓存
user2 = get_user_by_id(1) # 第二次不会请求数据库,直接取缓存

缓存更新策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 缓存更新策略示例:  
# 1. 缓存失效策略:在数据更新后使缓存失效
def update_user(user_id):
# 更新用户信息的操作
# 在更新完成后,使缓存失效
cache.delete_memoized(get_user_by_id, user_id)

# 2. 定时刷新策略:定期刷新缓存
from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()
scheduler.add_job(update_cache, 'interval', minutes=30) # 每30分钟刷新一次缓存

# 3. 事件驱动更新:使用事件机制通知缓存更新
# 假设有一个事件触发器在用户信息更新时触发事件
def on_user_update(user_id):
# 更新用户信息后,触发事件通知缓存更新
cache.delete_memoized(get_user_by_id, user_id)

Redis

安装redis-py库来实现

1
$ pip install redis

连接到Redis数据库

1
2
3
4
import redis  

# 建立与Redis数据库的连接
r = redis.Redis(host='localhost', port=6379, db=0)

使用Redis命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 设置键值对  
r.set('name', 'Alice')

# 获取键的值
name = r.get('name')
print(name)

# 向列表中添加元素
r.rpush('mylist', 'apple', 'banana', 'orange')

# 获取列表元素
items = r.lrange('mylist', 0, -1)
print(items)

# 向集合中添加成员
r.sadd('myset', 'dog', 'cat', 'rabbit')

# 获取集合成员
members = r.smembers('myset')
print(members)