基础
简介
Python是一门动态、解释型、强类型语言
- 动态:在运行期间才做数据检查(不用提前声明变量)- 静态语音(C/Java):编译时检查数据类型(编码时需要声明变量类型)
- 解释型:在执行程序时,才一条条解释成机器语言给计算机执行(无需编译,速度较慢)- 编译型语言(C/Java):先要将代码编译成二进制可执行文件,再执行
- 强类型:类型安全,变量一旦被指定了数据类型,如果不强制转换,那么永远是这种类型(严谨,避免类型错误,速度较慢)- 弱类型(VBScript/JavaScript): 类型在运行期间会转化,如 javascript中的 1+”2”=”12”, 1会由隐式转换为字符串
优点
- 简单易学
- 开发效率高
- 高级语言
- 可移植、可扩展、可嵌入
- 庞大的三方库
缺点
- 速度慢
- 代码不能加密
- 多线程不能充分利用多核cpu(GIL全局解释性锁,同一时刻只能运行一个线程
应用领域
- 自动化测试(UI/接口)
- 自动化运维
- 爬虫
- Web开发(Django/Flask/..)
- 图形GUI开发
- 游戏脚本
- 金融、量化交易
- 数据分析,大数据
- 人工智能、机器学习、NLP、计算机视觉
- 云计算
环境搭建
Windows Python3环境搭建
- 从Python官网,下载Python3安装包
- 双击安装,第一个节目选中Add Python3.* to PATH,点击Install Now(默认安装pip),一路下一步
- 验证:打开cmd命令行,输入python,应能进入python shell 并显示为Python 3.6.5版本
Mac OS Python3环境搭建
- 安装brew:在终端执行以下命令
ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
- 使用brew安装python3:
brew install python3
CentOS Python3环境搭建
安装依赖包
1
2yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make
yum install libffi-devel -y下载Python3源码安装
1
2
3
4
5wget 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建立软连接
1
2ln -s /usr/local/python37/bin/python3.7 /usr/bin/python3
ln -s /usr/local/python37/bin/pip3 /usr/bin/pip3添加环境变量
1
2
3export 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 | [global] |
pip 离线安装
- 离线安装
- 从Pypi.org网站查找需要的包,下载.whl文件
- 使用pip install <下载的whl包> 进行本地whl文件安装
- 源码安装
- 从Pypi.org下载源码包,解压,进入解压目录
- 打开命令行,执行
python setup.py install
- 验证:pip list 进行查找
语法
缩进
python 语法没有 { } 代码块,严格按照缩进进行代码块的规范
1 | if x > 0: |
一行多条语句
除了可以利用换行进行语句结束划分,也可以使用 ;
1 | x=1; y=2; print(x+y) |
断行
当一行语句太长时,会显得累赘;可以使用 ‘\‘ 将两行语句归为一行
注释
1 | # 单行注释 |
类型注释
在函数中,可以对参数及返回值类型进行注释
1 | def add(x: int, y: int) -> int: |
变量
变量类型(局部变量、全局变量、系统变量)
变量赋值
多重赋值
x=y=z=1
多元赋值
x,y = y,x
变量自增
x+=1
,x-=1
(不支持x++
,x--
)Python 语言本身没有提供内置的常量机制,但是通常使用全大写命名的变量来表示常量,在 Python 3.8 中引入了
typing.Final
来标识一个变量为最终值;Python 中,使用约定而不是强制,不像Java 有明确的常量关键字final
来定义常量1
2from 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。
- is:判断是同一对象,’==’只判断值是否相等,如
赋值运算符:用于将值赋给变量
- =:赋值,如
a = 1
,将1赋给变量a,支持多重赋值,如a,b = 1,2
或a,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
- and:并且,如
成员运算符:用于判断包含,常用判断字符串、列表、元祖、集合、字典中是否包含某元素(某键值)
- 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低
- 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
内置函数
- abs(x):
返回x的绝对值。如果x是一个复数,返回其模。 - all(iterable):
返回True如果可迭代对象中的所有元素均为真(非零),否则返回False。 - any(iterable):
返回True如果可迭代对象中的任一元素为真(非零),否则返回False。 - ascii(obj):
返回对象的可打印表示形式,使用Python的转义语法。 - bin(x):
返回x的二进制表示形式。 - bool(x):
将x转换为布尔值,返回True或False。 - breakpoint(*args, **kws):
断点调试函数,用于交互式调试。 - bytearray(source, encoding, errors):
创建一个可修改的字节数组对象。 - bytes([source[, encoding[, errors]]]):
返回一个bytes对象,适合存储二进制数据。 - callable(obj):
检查对象是否可调用(函数、方法、类、实例等)。 - chr(i):
返回Unicode码位i对应的字符。 - classmethod(func):
将函数装饰为类方法。 - compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1):
将源代码编译为代码对象。 - complex(real, imag):
创建一个复数。 - delattr(obj, name):
删除对象的属性。 - dict([object]):
创建一个字典对象。 - dir([object]):
返回一个对象的所有属性和方法的列表。 - divmod(a, b):
返回a除以b的商和余数。 - enumerate(iterable, start=0):
返回一个包含索引和值的枚举对象。 - eval(expression, globals=None, locals=None):
执行字符串表达式,并返回结果。 - exec(object, globals, locals):
执行Python代码。 - filter(function, iterable):
使用指定函数过滤可迭代对象,返回一个迭代器。 - float(x):
将字符串或数字转换为浮点数。 - format(value, format_spec):
格式化字符串输出。 - frozenset([iterable]):
创建一个不可变的集合对象。 - getattr(object, name, default=None):
返回对象的属性值。 - globals():
返回全局变量的字典。 - hasattr(object, name):
检查对象是否具有指定属性。 - hash(object):
返回对象的哈希值。 - help([object]):
调用内置帮助系统。 - hex(x):
返回整数x的十六进制表示形式。 - id(object):
返回对象的唯一标识符。 - input(prompt):
从用户获取输入。 - int(x, base=10):
将字符串或数字转换为整数。 - isinstance(object, classinfo):
检查一个对象是否是指定类的实例。 - issubclass(class, classinfo):
检查一个类是否是另一个类的子类。 - iter(object, sentinel):
返回一个迭代器对象。 - len(s):
返回对象的长度。 - list([iterable]):
创建一个列表对象。 - locals():
返回当前局部变量的字典。 - map(function, iterable, …):
将指定函数应用于可迭代对象的每个元素,返回一个迭代器。 - max(iterable, * [, key, default]):
返回可迭代对象中的最大值。 - memoryview(obj):
创建一个内存视图对象。 - min(iterable, * [, key, default]):
返回可迭代对象中的最小值。 - next(iterator, default):
返回迭代器的下一个元素。 - object():
创建一个空对象。 - oct(x):
返回整数x的八进制表示形式。 - open(file, mode=’r’, buffering=-1):
打开文件并返回文件对象。 - ord(c):
返回Unicode字符c的整数表示。 - pow(x, y, z=None):
返回x的y次幂,再模z。 - print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False):
输出对象到标准输出。 - property(fget=None, fset=None, fdel=None, doc=None):
返回一个属性对象。 - range(start, stop, step):
创建一个包含指定范围数字的可迭代对象。 - repr(object):
返回对象的可打印表示形式。 - reversed(seq):
返回逆序的迭代器对象。 - round(number[, ndigits]):
四舍五入函数。 - set([iterable]):
创建一个集合对象。 - setattr(object, name, value):
设置对象的属性值。 - slice(stop):
创建一个切片对象。 - 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 |
字符串遍历
使用
for
循环:1
2
3s = "hello"
for char in s:
print(char)使用
enumerate()
函数(获取索引和字符):1
2
3s = "hello"
for index, char in enumerate(s):
print(index, char)使用
range()
函数和索引:1
2
3s = "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 | tpl='''<html> |
列表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] |
列表遍历
- 使用
for
循环
最直接的方式是使用 for
循环来遍历列表中的每个元素。
1 | lst = [1, 2, 3, 4, 5] |
- 使用
enumerate()
函数
enumerate()
可以同时获取列表的索引和值。
1 | lst = [10, 20, 30, 40, 50] |
- 使用
range()
函数和索引
可以通过 range()
函数生成索引,然后用这些索引来访问列表的元素。
1 | lst = ['a', 'b', 'c', 'd'] |
- 使用列表推导式
列表推导式是一种简洁的方式来处理列表元素。虽然它不直接用于遍历,但可以在创建新列表时处理每个元素。
1 | lst = [1, 2, 3, 4, 5] |
- 使用
map()
函数
map()
函数可以将一个函数应用于列表中的每个元素。
1 | lst = [1, 2, 3, 4, 5] |
- 使用
while
循环
虽然不如 for
循环常用,但 while
循环也可以用于遍历列表。
1 | lst = [1, 2, 3, 4, 5] |
- 使用
itertools
模块
如果你需要更高级的遍历功能,比如在迭代过程中使用多个列表,可以考虑使用 itertools
模块中的工具。
1 | import itertools |
元祖tuple
- 不可改变,常用作函数参数(安全性好)
- 同样支持混合元素以及嵌套
- 只有一个元素时,必须加”,”号,如
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)) |
元组遍历方法
- 使用
for
循环
最简单的方法是使用 for
循环遍历元组中的每个元素。
1 | tup = (1, 2, 3, 4, 5) |
- 使用
enumerate()
函数
enumerate()
可以同时获取元组的索引和值。
1 | tup = ('a', 'b', 'c', 'd') |
- 使用
range()
函数和索引
通过 range()
函数生成索引,并使用这些索引来访问元组的元素。
1 | tup = (10, 20, 30, 40) |
- 使用
while
循环
while
循环也可以用于遍历元组,但通常使用 for
循环会更简单。
1 | tup = (100, 200, 300, 400) |
- 使用列表推导式
虽然列表推导式通常用于创建新列表,但你可以在创建新列表时处理元组的每个元素。
1 | tup = (1, 2, 3, 4, 5) |
- 使用
map()
函数
map()
函数可以将一个函数应用于元组中的每个元素。
1 | tup = (1, 2, 3, 4, 5) |
- 使用
itertools
模块
itertools
模块提供了很多有用的函数来处理迭代器和元组。
1 | import itertools |
序列类型相关操作方法
字符串、列表、元祖等按顺序存储的变量类型,我们统称为序列类型。
序列类型 - 索引
- 正反索引:
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 | # python3.7+ |
遍历序列类型
使用 for 循环:
1
2
3my_list = [1, 2, 3, 4]
for item in my_list:
print(item)使用 while 循环和索引:
1
2
3
4
5my_tuple = (10, 20, 30)
index = 0
while index < len(my_tuple):
print(my_tuple[index])
index += 1使用内置函数 enumerate() 获取索引和值:
1
2
3my_string = "Hello"
for index, value in enumerate(my_string):
print(f"Index: {index}, Value: {value}")列表解析(List comprehension):
1
2
3my_list = [1, 2, 3]
squared_values = [x**2 for x in my_list]
print(squared_values)
- 序列类型支持索引操作,因此可以使用循环或者内置函数来获取每个元素。
- 序列类型的元素有顺序,因此我们可以按照它们在序列中的位置来进行遍历。
- 序列类型通常具有固定长度,因此我们可以使用内置函数
len()
来获取其长度
遍历非序列类型
集合 (set) 示例:
1 | my_set = {10, 'a', True} |
字典 (dict) 示例:
1 | my_dict = {'name': 'Alice', 'age': 25} |
某些非序列对象可能具有特定方法用于迭代。例如,在 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 | s = {'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 | data1 = [{'name': '王六', 'score': 75}, |
集合对象自带方法
方法 | 说明 | 示例 |
---|---|---|
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)) |
集合遍历
- 使用
for
循环
最直接的方法是使用 for
循环来遍历集合中的每个元素。
1 | my_set = {1, 2, 3, 4, 5} |
- 使用
enumerate()
函数
虽然 enumerate()
通常与列表一起使用,但你也可以将其与集合一起使用,以获取元素的索引和对应的值。然而,由于集合是无序的,索引的值将是基于集合的遍历顺序,而不是原始的顺序。
1 | my_set = {'a', 'b', 'c', 'd'} |
- 使用
while
循环
while
循环可以用来遍历集合,但需要小心,因为集合没有顺序。
1 | my_set = {10, 20, 30, 40} |
- 使用集合推导式
集合推导式可以用于处理集合的每个元素并创建一个新的集合。虽然它主要用于生成新的集合,但它可以在生成过程中处理每个元素。
1 | my_set = {1, 2, 3, 4, 5} |
- 使用
map()
函数
map()
函数可以应用一个函数到集合中的每个元素。因为集合是无序的,返回的结果集合也将是无序的。
1 | my_set = {1, 2, 3, 4, 5} |
- 使用
itertools
模块
itertools
模块提供了很多有用的函数来处理迭代器和集合。例如,itertools.chain()
可以用来连接多个集合。
1 | import itertools |
- 集合的有序遍历(使用排序)
虽然集合本身是无序的,但你可以将集合转换为列表,并对列表进行排序,从而以特定顺序遍历元素。
1 | my_set = {3, 1, 4, 2, 5} |
案例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
2d = dict(a=1, b=2, c=3)
d = {'a': 1, 'b': 2, 'c': 3}dict.fromkeys(key1, key2, key3, … , default): 以默认值创建包含多个键的字典
1
2d = 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():
- 遍历key:
字典常用方法
方法 | 说明 | 示例 |
---|---|---|
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()) |
字典遍历
- 遍历字典的键(Keys)
使用 for
循环遍历字典的键,可以通过字典的 keys()
方法获取所有的键。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 遍历字典的值(Values)
使用 for
循环遍历字典的值,可以通过字典的 values()
方法获取所有的值。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 遍历字典的键值对(Items)
使用 for
循环遍历字典的键值对,可以通过字典的 items()
方法获取所有的键值对。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 使用字典推导式
字典推导式是一种创建新字典的简洁方法,可以在创建过程中处理字典的每个元素。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 使用
enumerate()
函数
enumerate()
可以用于获取字典项的索引和对应的键值对,尽管字典本身没有固定顺序。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 使用
while
循环和迭代器
通过创建字典项的迭代器并使用 while
循环遍历字典。
1 | my_dict = {'a': 1, 'b': 2, 'c': 3} |
- 使用
itertools
模块
itertools
模块提供了一些用于迭代的函数,可以与字典结合使用。
1 | import itertools |
- 使用
defaultdict
和collections
模块
collections.defaultdict
是一个字典的子类,允许在访问不存在的键时提供默认值。可以用它来处理遍历中的特殊情况。
1 | from collections import defaultdict |
更新字典数据
通过key值修改
1
2api = {"url": "/api/user/login": data: {"username": "张三", "password": "123456"}}
api['data']['username'] = "李四"通过update() 方法修改
1
2api = {"url": "/api/user/login": data: {"username": "张三", "password": "123456"}}
api['data'].update({"username": "李四"})
哈希与可哈希元素
- 哈希是通过计算得到元素的存储地址(映射), 这就要求不同长度的元素都能计算出地址,相同元素每次计算出的地址都一样, 不同元素计算的地址必须唯一, 基于哈希的查找永远只需要一步操作, 计算一下得到元素相应的地址, 不需要向序列那样遍历, 所以性能较好
- 可哈希元素: 为了保证每次计算出的地址相同, 要求元素长度是固定的, 如数字/字符串/只包含数字,字符串的元组, 这些都是可哈希元素
python3 字典视图对象
在Python 3中,字典(Dictionary)、集合(Set)和映射(Mapping)对象都具有视图对象(View Objects)。这些视图对象提供了对原始数据结构的动态“视图”,可以在不复制原始数据的情况下查看和操作数据。Python 3中常见的视图对象有三种:
dict_keys
:包含字典的所有键的视图对象。它代表了字典的键集合,支持集合操作(比如并集、交集、差集等)和迭代操作。
1 | d = {'name': 'Alice', 'age': 30, 'city': 'New York'} |
dict_values
:包含字典的所有值的视图对象。它代表了字典的值集合,支持集合操作和迭代操作。
1 | values_view = d.values() |
dict_items
:包含字典的键值对(项)的视图对象。它代表了字典的键值对集合,支持迭代操作。
1 | items_view = d.items() |
使用这些视图对象进行检索、遍历、过滤和集合操作,而不必复制字典中的数据。视图对象会实时反映原始数据结构的更改,因此在对视图对象进行操作时会直接影响原始数据。这种动态“视图”能够提高代码的效率和减少内存消耗。
文件路径读取
获取当前工作目录:
1
2import os
current_dir = os.getcwd()获取脚本文件所在目录:
1
2import os
script_dir = os.path.dirname(os.path.abspath(__file__))拼接文件路径:
1
2import os
file_path = os.path.join(script_dir, 'file.txt')获取用户主目录:
1
2from pathlib import Path
user_home = Path.home()获取环境变量中的路径:
1
2import os
path_from_env = os.getenv('MY_PATH', '/default/path')获取模块的文件路径:
1
2import mymodule
module_path = os.path.abspath(mymodule.__file__)通过
pathlib
获取文件路径:1
2
3from pathlib import Path
p = Path('file.txt')
absolute_path = p.resolve()
isinstance和type的区别
isinstance
和type
是Python中用于检查对象类型的两个函数,它们有以下区别:
type(object)
函数返回对象的类型,通常是对象所属类的类型。例如,type(5)
将返回<class 'int'>
,表示整数类型。type
函数还可以接受一个参数,即要检查的对象,返回该对象的类型。isinstance(object, classinfo)
函数用于检查对象是否是指定类(或其子类)的实例。isinstance
函数接受两个参数,第一个参数是要检查的对象,第二个参数是要检查是否属于的类或类型。如果对象是指定类的实例或子类的实例,isinstance
将返回True
,否则返回False
。例如,isinstance(5, int)
将返回True
,表示整数5是整数类的一个实例。
关键区别:
type
函数返回对象的类型,而isinstance
函数用于检查对象是否是指定类的实例。type
函数返回的是对象的确切类型,而isinstance
函数可以检查对象是否是指定类的实例或其子类的实例。
分支及循环
分支
if条件判断
if …
如果条件满足,才执行其中语句,例如
1 | x = 1 |
if … else …
即,如果条件满足,执行某些语句,否则执行另一些语句,例如
1 | x = 1 |
f… elif … else …
即,如果满足某条件,执行某些语句,否则如果满足另一条件,执行该条件语句,如果都不满足所列条件则执行其他语句。
1 | x = 1 |
真假值
在Python中False、None,数字0,字符串’0’,以及空字符串’’,空列表[]、空字典{},空元祖(,),空集合set(),都被失望假值;否则被视为真。比如可以直接使用if list判断列表为空:
1 | list = [] |
三元表达式
三元表达式即 当条件满足时 变量 为一个值,条件不满足时,变量为另一个值,例如:
1 | max = a if a > b else b |
逻辑符代替if判断
在Python语句中and和or也可以用于逻辑判断。
c = a and b
:如果a为假,则c的值为a,否则c的值为bc = a or b
:想反,如果a为假,c的值为b,否则c的值为a
特别是or语句,常用来确保变量的值非空并赋于默认值,例如:
1 | d = {'a': 1} |
循环
Python中的循环有for循环和while循环两种
for循环
for循环(也称遍历),是从一个含有多个变量的数据集合中,依次取出每一项,进行操作。
for循环可以对字符串、列表、元祖、集合、字典及生成器、迭代器、文件指针等可以迭代的对象进行遍历输出。
遍历获取一组顺序的数字
1
2for i in range(10): # range(10)即生成0-9的10个数字,不包括10
print(i)遍历字符串
1
2
3
4str_var = 'hello,world'
for i in str_var: # i是代表所遍历对象的每一项的一个变量,可以是任意变量名
print(i)遍历列表
1
2
3
4list_var = ['a', 'b', 'c', 'd']
for i in list_var: # i是代表所遍历对象的每一项的一个变量,可以是任意变量名
print(i)在遍历时如果想连同该项的索引一起输出,可以使用enumerate实现
1
2
3
4list_var = ['a', 'b', 'c', 'd']
for index, item in enumerate(list_var): # index代表索引,item代表每一项的值
print(index, item)遍历字典
1
2
3
4dict_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 | i = 0 |
break和continue
break用于结束当前循环,continue用于结束本次循环,直接开始下次循环
1 | # break 当有多层循环嵌套时,break每次只能跳出一层循环 |
循环中的else
循环结束有break结束和全部循环完结束两种,为了判断是哪种结束方式,可以使用else。当非break结束时执行else。
由于实际不会break,因此运行结果会打印’循环完毕’,while…else的使用和for…else类似:
1 | for in range(10): |
函数
Python中的函数可以理解为一种预先设定的处理过程。一般过程都会包含输入、处理、和输出三个部分。
- 输入,即函数参数,可以有多个参数;
- 处理:函数内部的处理过程,可以调用其他函数及模块;
- 输出:即返回值,也有可以返回多个。
数学中的函数指一种映射的变换关系,如f(x)=2x+1
,转换为Python函数为:
1 | def f(x): |
函数定义和调用
函数分为函数定义和函数调用两部分。
- 函数定义即设计函数,是对参数、处理过程和返回值的描述。
- 函数调用及使用函数,是使用实际的数据,运行函数并得到实际的返回值。
定义函数
定义一个函数使用def
关键字,格式如下。
1 | def 函数名(参数1,参数2, ...): |
调用函数
定义的函数需要调用才能执行,调用是按定义的格式传入和参数对应的实际数据,调用方式如下
1 | 函数名(数据1,数据2,...) |
如果需要获取函数的返回结果,可以使用将函数调用复制给变量
1 | 变量 = 函数名(数据1,数据2,...) |
函数参数
参数是指使用函数时需要提供的信息,如一个登录函数add(a, b)
,需要提供加数和被加数,才能进行运算。
1 | def add(x, y): # 定义函数 |
形参和实参
函数分为定义和调用,在定义函数时的参数称为形式参数,如def add(x, y): ...
,这里的x
和y
便是形式参数,形式参数是函数内部使用的。
在调用函数时需要传入实际的数据,如add(3,5)
,这里的3
和5
便是实际参数。实际参数也可以是预先定义好的变量
1 | def add(x, y): |
参数类型注解
Python3.5版本以后提供了类型注解
1 | def add(x: int, y: int) -> int: # 说明x,y应为整型 -> 返回值也会整数 |
参数默认值-必选和可选参数
1 | def add(x, y=1): # x 为必传参数, y 为可选传参数,不传默认值为1; 提供默认值的参数必须写到后面 |
位置参数和关键字参数
参数在定义时只有必选和可选之分,可选的参数需要写到后面。
但是在函数调用时就有如下两种传参方式
1 | add(3,5) |
不定参数
当一个函数使用方式不确定,需要设计其支持任意多个、任意方式(位置/关键字形式)传入时。可以使用*args
和**kwargs
args即参数(复数):arguments的缩写,kwargs即关键词参数(复数):keyword arguments的缩写
1 | def add(*args, **kwargs): |
限定关键字参数
函数中可以使用*
参数,其后的参数在使用时只能按key=value
形式使用
1 | def add(*, a, b): |
限定位置参数
函数中可以使用/
参数,其前的参数在使用时只能按位置参数形式使用
1 | def add(a, b, /): |
函数中也可以使用不定参 def add(*args): ...
来仅允许使用位置参数,使用起来不如上述形式方便,例如
1 | def add(*args): |
函数返回值
函数中使用关键return
返回结果,return操作后,函数结束(后面的语句不会再执行)
如果想函数每次调用都返回一个值后并不终止,而是暂停等待下次调用,可以使用yield
代替return
,这样便得到一个生成器函数。
变量的作用域
函数内部的变量被称为局部变量,局部变量是私有变量,一般情况下,一个函数无法访问其他函数的局部变量。
如果需要在一个函数中声明一个变量,让所有函数都可以使用,可以使用global关键字声明其为全局变量。
1 | def add(x, y): |
1 | a = 3 # 模块中的全局变量 |
匿名函数
1 | lambda 参数: 返回值 |
高阶函数
函数用于显示一个函数的信息。
1 | def add(x,y): |
装饰器
装饰器也是一种典型的以函数为参数的函数,装饰器旨在通过包装,在不改变原函数的情况下,为函数来增加功能。
1 | # 直接使用@info为add函数加上装饰器 |
装饰器的执行流程
- 定义装饰器函数:首先需要定义一个装饰器函数。这个函数将接收一个被装饰的目标函数作为参数,并返回一个新的内嵌(包裹)了目标函数功能的封闭(wrapper)函数。
- 应用装饰器:使用
@
符号将定义好的装饰器应用到目标函数上。这相当于将目标函数作为参数传递给了装饰器,并重新赋值给了原来的名称。 - 装饰过程:当调用被修饰后的原始目标函时数时,实际上调用并执行了封闭(wrapper) 函数。这个封闭函(wrapper)数接收到相同参数并执行了额外操作或者修改后再调用内部保存着目标函数引用 的实际逻辑代码。
1 | def uppercase_decorator(func): |
常用高阶函数
map, filter和reduce是Python中常用的3个高阶函数
map()
map用于使用一个函数,对一个序列进行批量操作,示例如下1
2
3
4add1 = 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
4is_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
6from 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) # 得到 55sorted()
Python内置的
sorted()
函数就可以对list进行排序1
2sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]它还可以接收一个
key
函数来实现自定义的排序
绝对值大小排序 - key = abs1
2sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]忽略大小写 - key = str.lower
1
2sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']进行反向排序 - reverse=True
1
2sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
函数嵌套 和闭包
函数嵌套
函数嵌套是指在一个函数内部定义了另一个函数。这种嵌套的函数被称为内部函数,而包含它的外部函数被称为外部函数
嵌套的内部函数可以访问外部函数的参数,但是外部函数无法访问内部函数的参数。
如果想在内部函数内声明一个具有外部函数范围的参数可以使用nolocal
关键字声明其为自由变量
优点:内部函数可以使用外部函数中的参数 (当内部函数使用了外部函数的参数时,就形成了闭包。)
1 | def outer_function(): |
闭包
闭包是指在一个内层函数中引用了其外层(封闭)作用域中变量的情况。这样的情况下,如果返回了内层函数,则该内层函数将继续保持对其封闭作用域中变量的引用,即使这些变量已经超出了其正常生命周期或者超出了原本作用域。
1 | def counter(): |
总结:
- 函数嵌套是指在一个函数里定义另一个子级或者局部级别别名。
- 当子级或者局势级别师徒能够访问父级别名称空间时就形成了闭包关系。
- 这种情况下,如果返回了子级或者局势级别师徒则他将会继续保存父亲名称空间数据区块信息
递归函数和尾递归
递归函数
- 必须有出口条件,如最后一层1,有明确的结果1。
- 每层只负责乘以本层数字,调用自己推给下一层
- 整个推导过程要逐步趋于出口
递归函数是指在函数的定义中调用函数本身的过程。递归函数通常用于解决可以被分解为相同问题的子问题的情况,每次递归调用都会将问题规模缩小,直到达到基本情况(递归终止条件)。
1 | def factorial(n): |
尾递归
尾递归是一种特殊的递归形式,指的是递归函数的最后一个操作是递归调用本身。在尾递归中,递归调用是函数的最后一步操作,不会再有其他操作。
尾递归函数可以通过优化来避免栈溢出的问题,因为它们可以被转换为循环的形式,不会在每次递归调用时创建新的栈帧。
1 | # 计算斐波那契数列的尾递归函数的示例 |
总结:
- 递归函数是指在函数的定义中调用函数本身的过程,用于解决可以被分解为相同问题的子问题的情况。
- 尾递归是一种特殊的递归形式,指的是递归函数的最后一个操作是递归调用本身。
- 尾递归函数可以通过优化来避免栈溢出的问题,因为它们可以被转换为循环的形式,不会在每次递归调用时创建新的栈帧。
类与对象
类(Class)和对象(Object),也称作实例(Instance)是面向对象编程(OOP)中的重要概念。类的主要作用如下:
- 在同一模块中,对多个函数进行分组,并共享其中的变量;
- 按动作主体归类函数动作,使得逻辑更清晰。
面向过程及面向对象
面向过和面向对象是两种编程风格。
- 面向过程:主要考虑功能的实现步骤和过程,即怎么去实现,多使用函数相互组合调用实现。
- 面向对象:主要考虑动作的主体和相互关系,即谁去实现,怎么配合,使用类的继承或组合实现。
面向过程的实现逻辑如下:
- 拆分过程
- 定义函数实现每个过程(过程可以包含子过程及相互调用)
- 在主函数中组合调用各个过程函数,完成整个流程。
面向对象实现逻辑如下:
- 根据动作主体进行建模,即需要几种对象(角色),每个对象需要哪些属性和方法
- 设计各个对象需要的类
- 在主流程中将每个类生成对象,组合对象完成整个流程操作。
类与对象(实例)的关系
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
对象初始化方法
Python类中拥有很多魔术方法,起不同的作用,其中__init__(self)
方法称作对象初始化方法,在调用类创建对象时自动调用,通常作用如下:
- 将创建类传人的参数,绑定到对象属性
- 做一些对象初始化操作
1 | class Student: |
类属性及实例属性
类中的属性称为类属性 ( 类共享),绑定self的属性称为实例属性 (实例独有),同时实例自动继承类属性
- 类属性:可以使用类名或对象访问
- 对象属性:一般使用对象进行访问
1 | class Student: |
访问限制 - 私有变量
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
1 | class Student(object): |
类方法、实例方法及静态方法
类和实例是两种不同的范畴,因此在类中可以实例方法,也可以有类方法,如果方法根类和实例都没有关系,则可以设置成静态方法
1 | from datetime import datetime |
继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
1 | class Animal(object): |
获取对象属性
dir()
获得一个对象的所有属性和方法,可以使用
dir()
函数,它返回一个包含字符串的list1
2dir('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
13len('ABC')
3
'ABC'.__len__()
3
# 给自己的类写一个__len__()方法
class MyDog(object):
def __len__(self):
return 100
...
dog = MyDog()
len(dog)
100hasattr()
判断某个对象中是否有该属性
1
2hasattr(obj1, 'x') # 有属性'x'吗?
Truegetattr()
获取某个对象中的属性;当前属性不存在时会报错,建议结合hasattr() 使用
1
2
3
4
5
6
7
8
9if 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
10from 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 | print('b模块,当前模块名', __name__) |
间接导入问题
间接导入是指a模块导入b模块,b模块导入c模块,运行a模块时c模块被间接导入
根本原因是:相对路径问题
例如:
1 | 假如目录结构为: |
aaa.py 导入了bbb.py ,但是 bbb.py 里面已经导入了ccc.py
此时,运行a.py,间接导入c模块时便会出现异常
1 | ... |
原因为,运行a.py时,间接导入import c
时,只会在a.py所在目录(当前运行目录是package2, 在bbb.py 中导入的路径是package1,导致找不到模块ccc.py)及PYTHONPATH、三方包site-packages中查找。而不会切换目录到b.py所在目录进行查找。
需要为当前python模块添加package1添加环境变量
1 | import os |
循环导入问题
循环导入指a导入了b模块,b模块又直接或间接导入了a模块
- 使用
import 模块
导入时,允许循环导入,模块只在第一次导入时及作为主模块时运行 - 使用
from ... import ...
时不允许循环导入
处理方法有以下几种:
- 使用
import 模块
方式导入 - 将
from ... import ...
改到函数内部(延迟执行) - 使用包
__init__.py
统一规划导入方式
导入立即执行及调用(延迟)执行
在导入模块时,有些是立即执行,有些时调用时才执行的
第一次导入模块时立即执行的有:
- 直接写模块中的语句
- 装饰器
- 类变量
调用时才执行的有:
- 定义的函数
- 定义的类及方法
1 | def deco(func): # 装饰器函数 |
包 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
的一些用法:
声明包中的模块:在
__init__.py
中可以通过导入其他模块或子包来声明该包中包含的模块,使得外部用户可以直接通过包名访问这些模块。例如:1
2
3
4# package/__init__.py
# 将该包下的模块的方法,导入__init__.py
from .module1 import *
from .module2 import *1
2
3
4import mypackage
# 这样我们就可以直接导入包,不需要详细到某个模块内的方法
mypackage.module1.my_function()
obj = mypackage.module2.MyClass()执行初始化代码:在
__init__.py
中可以执行一些初始化操作,例如设置全局变量、加载配置、注册插件等。这些操作会在包被导入时执行一次。例如:1
2
3# __init__.py
print("Initializing mypackage...")
CONFIG = {'debug': True, 'name': 'mypackage'}控制包的导入行为:通过在
__init__.py
中定义__all__
变量,可以控制包被导入时哪些模块会被导入。例如:1
2
3
4
5# __init__.py
'''
__all__变量:在__init__.py中,可以使用__all__来控制使用from package import *时导入的模块和变量。只有__all__中声明的模块和变量才会被导入。
'''
__all__ = ['module1', 'module2']实现”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
4f = open('demo.txt', 'r', encoding='utf-8')
data = f.read() # 读取文件全部内容
print(data)
f.close()按行读取
1
2
3
4
5
6f = open('demo.txt', 'r', encoding='utf-8')
line = f.readline() # 读取一行(包括结尾的换行符)
print(line)
line = f.readline() # 读取下一行(包括结尾的换行符)
print(line)
f.close()结合for循环按行读取文件全部内容
1
2
3
4f = 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
6data = '''hello
world
'''
f = open('demo2.txt', 'w', encoding='utf-8')
f.write(data)
f.close()写入多行数据
有一行一行文本组成的列表型数据,可以使用f.writelines()写入1
2
3
4data = ['hello\n', 'world\n']
f = open('demo2.txt', 'w', encoding='utf-8')
f.writelines(data)
f.close()
使用上下文格式自动关闭文件
1 | # 退出with语句(如print时),自动关闭文件。 |
非纯文件读写
二进制格式读写图片等非二进制文件,有一张图片hua.jpg,我们可以读写来复制文件
1 | # 使用二进制读/写模式打开时无需指定编码 |
读写模式
标示 | 文本模式 (默认)。 |
---|---|
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(Python 3 不支持)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
其他类型文件读取
数据及配置文件之争
数据及文件通常有三种类型:
- 配置文件型:如ini,conf,properties文件,适合存储简单变量和配置项,最多支持两层,不适合存储多层嵌套数据
- 表格矩阵型:如csv,excel等,适合于存储大量同类数据,不适合存储层级结构的数据
- 多层嵌套型:如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 | // 数据文件data.csv |
1 | # getCsvFile.py 读取 |
1 | # setCsvFile.py 写入 |
使用字典格式的数据:DictReader, DictWriter
!!! 使用此方法时,必须有标题行才能使用
- reader=csv.DictReader(f):直接将标题和每一列数据组装成有序字典(OrderedDict)格式,无须再单独读取标题行
- writer=csv.DictWriter(f, 标题行列表):写入时可使用writer.writeheader()写入标题,然后使用writer.writerow(字典格式数据行)或write.writerows(多行数据)
1 | # getCsvFile.py |
1 | # setCsvFile.py |
ini文件 - configparser
ini文件即Initialization File初始化文件,在应用程序及框架中常作为配置文件使用,是一种静态纯文本文件,使用记事本即可编辑。
配置文件的主要功能就是存储一批变量和变量值,在ini文件中使用[章(Section)]
对变量进行了分组,基本格式如下。
1 | # filename: config.ini |
以上文件中,有3个Section段,分别user、mysql和log
ini文件中使用#
或者;
添加注释,最好独占一行,不能写在变量后面
读取
读取ini配置文件需要使用Python3自带的configparser库,使用示例如下
1 | from configparser import ConfigParser # Python2中是from ConfigParser import ConfigParser |
其他常用的读取方法如下:
- 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 | for key, value in conf['mysql'].items(): |
公共变量
假如我们每个Section变量组都有一批相同的重复变量,如:
1 | [dev] |
对应这种,我们可以设置[DEFAULT]段公用变量,公用变量会自动添加到每一个段中,修改后如下
1 | [DEFAULT] |
参数化
在ini文件中我们还可以使用%(变量名)s
的占位符进行参数化,这种特性被称为Interpolation(插值)。
1 | [DEFAULT] |
上例中,我们在[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 | from configparser import ConfigParser |
JSON文件 - json
JSON(JavaScript Object Notation)即JavaScript对象表示法,一种轻量级,通用的文本数据格式。
JSON语法支持对象(Object),数组(Array),字符串,数字(int/float)以及true/false和null。
JSON拥有严格的格式,主要格式如下:
- 只能用双引号,不能用单引号
- 元素之间用逗号隔开,最后一个元素不能有逗号
- 不支持注释
- 中文等特殊字符传输时应确保转为ASCII码(\uXXX格式)
- 支持多层嵌套Object或Array
1 | { |
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 | import json |
JSON字符串转字典
1 | import json |
JSON文件与字典的相互转换
将字典保存为JSON文件或从JSON文件转为字典
- json.dump(字典, f):将字典转为JSON文件(句柄)
- json.loads(f):将打开的JSON文件句柄转为字典
字典转成JSON文件
1 | import json |
JSON文件转成字典
1 | import json |
!!! 字典转为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"
- 数字:
123
或123.45
- true/false:
true
/false
,TRUE
/FALSE
,True
/False
或on
/off
,ON
/OFF
,On
/Off
- null:
null
,NULL
,Null
或~
1 | # 注释:示例yaml文件 |
1 | // 转译成json文件 |
类型转换
使用!!str
, !!float
等可以将默认类型转为指定类型
1 | - !!float 3 |
对应JSON格式
1 | [ |
多行文本及拼接
|
保留多行文本(保留换行符)>
将多行拼接为一行
1 | a: | |
对应JSON格式
1 | { |
锚点,引用及插入
在-
或:
后 加上&锚点名
为当前字段建立锚点,下面可使用*锚点名
引用锚点,或使用<<: *锚点名
直接将锚点数据插入到当前的数据中,示例如下:
1 | users: |
对应JSON格式:
1 | { |
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 | import yaml |
yaml 文件转成字典
1 | import yaml |
字典 转成 yaml字符串或文件
1 | import yaml |
1 | # demo5.yaml |
自定义tag
YAML常用于配置文件,当配置文件中需要配置一些用户名密码时,直接写在YAML文件并上传到代码仓库中则很容易造成密码泄露。
解决的方法有两种:
- 配置文件仅本地使用,不传到代码仓库中
- 将密码配置到执行机器的环境变量中,在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 | # demo.yml |
1 | # tag在python中使用 |
为tag分配匹配模式
此时YAML文件中环境变量只能使用强制类型声明!env ${变量名}
来使用,如果想直接使用${变量名}
来使用则需要为该tag指定一种正则匹配模式,即识别到类似${变量名}
格式时自动使用!env这个tag。
1 | # demo.yml |
1 | # tag在python中使用 |
一个节点使用多个变量
如果我们想要在一个节点中使用多个变量,如
1 | # demo.yml |
则需要对节点值value(字符串格式)进行逐个替换。
首先我们需要修改我们的匹配模式,允许${变量}前后可以拥有多个任意字符
1 | import os |
Excel文件 - openpyxl / xlrd / xlwt
Python中常用的操作Excel的三方包有xlrd,xlwt和openpyxl等
- xlrd支持读取.xls和.xlsx格式的Excel文件,只支持读取,不支持写入。
- xlwt只支持写入.xls格式的文件,不支持读取。
- openpyxl不支持.xls格式,但是支持.xlsx格式的读取写入,并且支持写入公式等。
xlsx文件格式
读取所有数据
1 | import openpyxl |
按行读取
1 | import openpyxl |
读取单元格数据
1 | import openpyxl |
写入文件
1 | import openpyxl |
xls文件格式
读取 所有数据
1 | import xlrd |
按行读取
1 | import xlrd |
读取单元格数据
1 | import xlrd |
写入数据
1 | import xlwt |
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
11from 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 | from io import StringIO |
BytesIO
StringIO
操作的只能是str
,如果要操作二进制数据,就需要使用BytesIO
1 | from io import BytesIO |
os 文件及目录操作
os.name
通过name的值可以判断是什么系统
如果是posix
,说明系统是Linux
、Unix
或Mac OS X
,如果是nt
,就是Windows
系统。
详细信息可以使用 os.uname()
环境变量 - os.environ
在操作系统中定义的环境变量,全部保存在os.environ
这个变量中, 要获取某个环境变量的值,可以调用os.environ.get('key')
操作文件和目录
1 | import os |
序列化
在程序运行的过程中,所有的变量都是在内存中
把变量从内存中变成可存储或传输的过程称之为序列化
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化
Python提供了pickle
模块来实现序列化。
序列化
1 | import pickle |
反序列化
1 | import pickle |
JSON处理
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,例如XML,JSON
JSON类型 | Python类型 |
---|---|
{} | dict |
[] | list |
“string” | str |
1234.56 | int或float |
true/false | True/False |
null | None |
1 | import json |
1 | import json |
异常处理
try… except
一般情况下,异常会导致程序中断退出,为避免程序中断,我们需要对异常进行处理,在Python中我们使用try ... except ...
语句处理异常
1 | def div(a, b): |
一般来说,建议对不同类型的异常进行单独处理
1 | def div(a, b): |
except也可以一次捕获多个异常,对任意一种异常做同一处理,例如try: ... except: (ZeroDivisionError, TypeError): ...
无异常及无论是否有异常都执行的操作
异常处理支持使用try: ... except: ...
后使用else: ...
在无异常时执行某些操作,及使用finally: ...
,无论是否有异常都执行某些语句
1 | def div(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 | def div(a, b): |
自定义异常
也可以自定义异常类型进行抛出,以使的错误类型更清晰
1 | """ |
装饰器
装饰器是Python中的一个重要概念,多用于在不修改原函数的基础上,为函数增加额外的功能
基础装饰器
案例: 你要喝水
1 | def DrinkWater(): |
但你杯子里没水了,你还需要往杯子里装水
1 | def drinkWater(): |
装饰器本质上就是以函数作为参数,对函数做一些处理,并替换原函数的一种高阶函数。
1 | # 装水 |
装饰器处理函数参数
如果需要给喝水加入一个参数,控制喝几口水
1 | def fillWater(fn): |
带参装饰器
如果给杯子倒水也加上倒多少毫升,通过装饰器传递多少毫升
1 | def fillWater(ms): |
!!! 装饰器在导入模块时立即计算的,即没调用drinkWater()
之前就已经执行生成定制后的new_gift
生成器和迭代器
可迭代对象
实现了__iter__方法, __iter__方法返回一个迭代器
迭代器
在Python中,迭代器(iterator)是一个对象,它实现了迭代协议,即具有__iter__()
和__next__()
方法的对象。迭代器允许我们遍历容器对象(例如列表、元组、集合、字典等)中的元素,而不需要了解容器的内部结构。
以下是关于迭代器的一些重要信息:
迭代器协议:
__iter__()
方法返回迭代器对象自身。__next__()
方法返回容器中的下一个元素,如果没有更多元素,则抛出StopIteration异常。
使用迭代器:
- 使用
iter()
函数可以将可迭代对象(如列表、元组)转换为迭代器。例如:my_iter = iter([1, 2, 3])
- 使用
next()
函数从迭代器中获取下一个元素。例如:next(my_iter)
- 使用
迭代器示例:
1
2
3
4
5
6numbers = [1, 2, 3]
iter_numbers = iter(numbers)
print(next(iter_numbers)) # 输出: 1
print(next(iter_numbers)) # 输出: 2
print(next(iter_numbers)) # 输出: 3迭代器的优点:
- 节省内存:迭代器一次只加载一个元素,不需要一次性加载整个容器。
- 惰性计算:在需要时计算元素值,延迟执行。
迭代器与可迭代对象的区别:
- 可迭代对象(iterable)定义了
__iter__()
方法,返回一个新的迭代器。 - 迭代器会保存当前状态,每次调用
next()
方法获取下一个值,直到遍历完成。
- 可迭代对象(iterable)定义了
生成器
生成器(generator)是一种特殊的迭代器,可以通过生成器函数或生成器表达式来创建。生成器能够动态生成值,而不需要一次性将所有值存储在内存中,这使得生成器在处理大量数据或需要惰性计算时非常有用。
以下是有关生成器的一些重要信息:
生成器函数:
- 使用
yield
关键字来生成值,而不是return
。 - 生成器函数在被调用时返回一个迭代器对象。
- 使用
生成器表达式:
- 类似于列表推导式,但使用圆括号而不是方括号。
- 通过惰性计算的方式生成值。
使用生成器:
- 使用
next()
函数从生成器中获取下一个值。 - 生成器会在生成每个值时暂停,直到需要下一个值。
- 使用
生成器示例:
- 生成器函数示例:
1
2
3
4
5
6
7
8
9def 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
3gen = (x**2 for x in range(5))
for num in gen:
print(num) # 输出: 0, 1, 4, 9, 16生成器的优点:
- 节省内存:不需要一次性存储所有值。
- 惰性计算:按需生成值,延迟执行。
列表推导式
!!! 推倒式:当我们对一批可迭代的数据(如列表或字典)进行提取或处理,最后要得到一个新的列表或字典时,推导式是一种非常简洁的表达方式。
1 | data = [ |
多重循环
推导式还支持多重循环
1 | for x in range(1,5) |
批量执行操作
推导式就是一种循环操作,我们也可以使用推导式来批量执行一些相似操作
1 | def step1(driver): |
字典推导式
当我们需要遍历一批数据最后得到一个字典时,同样可以使用字典推导式
1 | data = [ |
生成器
生成器实际上是一种包含初始数据和推导法则的对象
对应大量的数据或者CSV/Excel文件中的数据,生成器可以大量的节省内存,比如csv.Reader(f)就是一个生成器,只存了当前位置和读取下一行数据的方法;当你需要遍历时,它再每次给你读取一行数据给你
1 | 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实例添加name
和age
属性。
为了达到限制的目的,Python允许在定义class
的时候,定义一个特殊的__slots__
变量,来限制该class
实例能添加的属性:
1 | class Student(object): |
!!! 使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
1 | class ParentClass: |
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
1 | class ParentClass: |
面相对象 - MixIn
通过多重继承,一个子类就可以同时获得多个父类的所有功能
mixln - 可以让一个类除了继承其他类外,再同时集成其他类。这种设计通常称之为MixIn。
1 | # 这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类 |
面相对象 - 订制类
__str__
打印一个类实例会打印这种信息
1 | class Student(object): |
我们可以通过 __str__ 进行输出格式化操作
1 | class Student(object): |
如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象
1 | class Fib(object): |
__getitem__
__iter__() 可以让类进行遍历,但不能拿到某一个单一的值,因为它不是了列表。不能用下标获取
但可以通过实现__getitem__ 可以实现用下标获取某一个值
1 | class Fib(object): |
__getattr__ 和 __setattr__
__getattr__()
方法在访问一个不存在的属性时会被调用,可以在该方法中自定义返回值。
而__setattr__()
方法在给对象的属性赋值时会被调用,可以在该方法中自定义赋值操作。
1 | # __getattr__ |
__call__
只需要定义一个__call__()
方法,就可以直接对实例进行调用
1 | class Student(object): |
面向对象 - 枚举类
- 枚举类中的成员是从1开始计数的int常量。
- 可以通过名称或值来获取对应的枚举成员,使用
EnumName.member_name
或EnumName('member_value')
。 - 枚举成员的值是自动赋给成员的int常量。
- 枚举类型会自动实现比较和排序功能。
列表类型
当需要定义的类型,只含有值,可以这样定义
1 | from enum import Enum |
键值对类型
1 | from enum import Enum, unique |
面向对象 - @property
当类中使用私有变量来限制访问,通过get和set方法来获取和设置,属性过多,就需要定义多的get和set方法;@property可以通过装饰器的方式解决这个问题
@property
该装饰器,可以直接把一个get方法书写方式的返回值
@fn.setter
设置只读属性 - 只定义getter方法,不定义setter方法
1 | class Student(object): |
面相对象 - 元类 [补充]
元类是什么
在面向对象(OOP)编程中,我们可以用不同的类来描述不同的实体及操作,可以通过父类来设计一些“默认”操作,也可以用MixIn类来组合扩展一些额外操作,也可以用抽象类及抽象方法来描述要实现的接口,面向接口编程。
元类是一种type
(type的子类),是一种自定义类型,可以定制类的调用、对象创建、初始化、销毁等各种操作。
元类的使用场景
多数情况下元类用来对普通类来加以限制和规范。使用元类(自定义类型)可以在类的创建(__new__
)、初始化()比如限制类必须包含特定属性和实现特定方法、限制类只能有一个实例对象。
典型使用场景如下:
- 不允许类实例化
- 单例模式:每个类只允许创建一个对象
- 根据属性缓存对象:当某一属性相同时返回同一对象
- 属性限制:类中必须包含某些属性或实现某些方法
- ORM框架:用类及类属性来描述数据库表并转为数据库操作
元类使用示例
不允许类实例化
在某些情况下,假设我需要限制一些类不允许创建对象(只允许使用类名操作),可以使用元类加以限制
1 | class NoInstances(type): # 定义元类-继承type |
单例模式
某些情况下仅允许类创建一个实例对象,也可以使用元类进行限制
1 | class Singleton(type): # 单例类型-定制的元类 |
根据属性缓存对象
这是单例模式的扩展,针对特定属性,完全相同的属性组合创建同一对象
1 | import weakref |
限制类必须包含特定属性
1 | class TestCaseType(type): |
ORM框架
ORM(Object-Relational Mapping)是一种将对象和关系数据库之间的映射的技术,它可以让我们使用面向对象的方式来操作数据库。
Django中的ORM模型、以及SQLAlchemy都是基于元类实现的,将数据库操作映射为 类声明和对象操作,下面是一个基于元类的,简单的ORM框架的实现
1 | class ModelMeta(type): # 元类 |
使用这个ORM框架,我们可以定义一个继承自Model
的类,并在其中定义字段。例如
1 | class User(Model): |
python程序执行机制
在Python中,源代码文件(
.py
)在第一次被执行时,会经过编译过程生成为字节码文件(.pyc
),然后由Python解释器执行该字节码文件。编译的过程会生成一个PyCodeObject
对象,其中包含了字节码指令、常量、变量等信息。
具体的执行流程如下:
- 当 Python 解释器第一次运行一个 Python 源代码文件时,它会首先将源代码进行编译,生成字节码对象(PyCodeObject)。
- 这个字节码对象会被存储在内存中,并同时在同一目录下生成一个与源代码文件同名的
.pyc
文件,作为编译后的字节码文件。 - 当下次再次运行这个同样的 Python 源代码文件时,解释器会首先检查是否存在对应的
.pyc
文件。 - 如果存在
.pyc
文件,并且与源文件的修改时间一致,解释器会加载并执行.pyc
文件;如果.pyc
文件不存在或已过期,则重新对源文件进行编译生成新的字节码文件。 - 当解释器执行完整个字节码文件后,程序执行完成。
.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限制
多线程 - threading机制依然是有用的,用于IO密集型计算
IO期间,线程会释放GIL,实现cpu和io并行,因此多线程在io密集型计算依然可以大幅度提升速度。但多线程用于CPU密集计算时,只会拖慢速度
使用multiprocessing
多进程
Unix/Linux操作系统提供了一个fork()
系统调用,fork()系统调用在Unix/Linux操作系统中非常特殊,它会创建一个新的进程(子进程),该子进程是父进程的副本。父进程和子进程在执行fork()系统调用之后会继续执行接下来的代码。父进程中,fork()函数返回子进程的PID(Process ID),而在子进程中,fork()函数返回0。
调用fork - Unix/Linux
os
模块封装了常见的系统调用,其中fork
可以在Python程序中轻松创建子进程
1 | import os |
multiprocessing
由于Windows没有fork
调用,上面的代码在Windows上无法运行。multiprocessing
模块就是跨平台版本的多进程模块
1 | import multiprocessing |
Pool - 进程池
如果要启动大量的子进程,可以用进程池的方式批量创建子进程
1 | import multiprocessing |
子进程 - 交互
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess
模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
1 | import subprocess |
通过 communicate() 方法与进程交互
1 | import subprocess |
进程间通信
进程与进程之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信
Python的multiprocessing
模块包装了底层的机制,提供了Queue
、Pipes
等多种方式来交换数据。
- 在使用
multiprocessing
模块进行进程间通信时,需要注意数据的序列化和反序列化问题。因为不同进程之间的内存空间是独立的,所以需要将数据序列化后再传输到另一个进程中进行反序列化。 - 在使用
subprocess
模块启动子进程并与其进行交互时,需要注意输入和输出的缓冲区问题。如果子进程的输出数据量很大,可能会导致缓冲区溢出,从而导致程序出现异常。可以使用communicate()
方法来避免这个问题,该方法会等待子进程结束并获取其输出和错误信息。
Queue - 队列
Python 中的队列(Queue)是一个线程安全的数据结构,用于在多线程或多进程之间安全地传递数据。Python 提供了
queue
模块来实现队列功能,其中最常用的是Queue
类。Queue
类实现了一个简单的 FIFO(先进先出)队列。
相比于 queue
模块中的 Queue
类,multiprocessing
模块中的 Queue
类实际上是基于管道(Pipe)和信号量(Semaphore)实现的,可以在多进程之间安全地传递数据。
1 | import multiprocessing |
Pipe - 管道
1 | import multiprocessing |
多线程
多任务可以由多进程完成,也可以由一个进程内的多线程完成
进程是由若干线程组成的,一个进程至少有一个线程
由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持
由于全局解释器锁(GIL)的存在导致的。GIL是Python解释器中的一个机制,它保证同一时刻只有一个线程在执行Python字节码,这样就导致了在多线程环境中,无法真正实现多线程并行执行,线程之间会出现竞争条件,从而导致线程执行的无序性。
threading
Python的标准库提供了两个模块:_thread
和threading
,_thread
是低级模块,threading
是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。
1 | import threading |
案例
1 | import threading |
注意项:
- 全局变量共享问题:多个线程可以访问和修改相同的全局变量,因此需要确保对共享数据的访问是安全的。
- 线程同步:使用锁或者其他同步机制来避免多个线程同时修改共享资源。
- GIL(全局解释器锁):Python语言的GIL限制了同一时间只能有一个线程执行Python字节码。因此在CPU密集型任务中,并发性能可能无法得到提升。
Lock - 互斥锁
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
- 使用方法
1 | ''' |
- 修改上述案例
如果我们要确保balance
计算正确,就要给change_it()
上一把锁,当某个线程开始执行change_it()
时,该线程因为获得了锁,因此其他线程不能同时执行change_it()
,只能等待,直到锁被释放后,获得该锁以后才能改
1 | import threading |
- 创建两个线程交替打印0-100
1 | import threading |
RLock(可重入锁)
- 使用方法
1 | ''' |
Semaphore(信号量)
- 使用方法
1 | ''' |
- 案例 - 使用3个线程打印 0-100
1 | import threading |
Event(事件)
- 使用方法
1 | ''' |
- 案例 - 使用3个线程打印 0-100,需要在有序输出
1 | import threading |
线程池
- 使用方式
1 | ''' |
案例 - 使用3个线程打印 0-100,需要在有序输出
在上述案例中,使用Semaphore(信号量),其实是使用了大量线程只是限制了控制并发执行的线程数量
1 |
多线程队列(Queue)
在Python中,queue
模块中的 Queue
类是线程安全的队列,可以在多线程环境中安全地传递数据。
- 使用方式
1 | import queue |
乐观锁和悲观锁
在并发编程中,乐观锁和悲观锁是两种常用的锁技术,用于处理并发编程时访问共享资源的竞争情况。
悲观锁适用于对共享资源竞争较为激烈的情况,因为获得锁之后,其他线程无法访问共享资源,从而保证数据的一致性;而乐观锁适用于共享资源竞争较少的情况,因为乐观锁不会一直持有锁,可以提高系统的并发性能。
悲观锁 (Pessimistic Locking)
悲观锁的思想是在处理共享资源时,假设其他线程或进程会导致数据不一致或冲突,因此在访问共享资源之前先获取锁,确保对共享资源的独占访问。典型的悲观锁机制包括数据库的行级锁、表级锁等。
在Python中,可以使用数据库的事务锁来实现悲观锁。
乐观锁 (Optimistic Locking)
乐观锁的思想是在处理共享资源时假设没有竞争,多个线程或进程同时访问共享资源,但只在更新时检查资源的版本号或标记是否与自己的相匹配,如果匹配则继续更新,否则放弃更新。乐观锁不会一直持有锁,只在更新时检查共享资源的一致性。
在Python中,可以使用一些标记或版本号来实现乐观锁,比如使用数据库中的版本号字段来检查数据是否被其他线程修改过。
乐观锁&悲观锁 示例
1 | mport threading |
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
库来实现。
- 异步IO操作:协程通常用于处理IO密集型任务,例如网络请求或文件读写。了解如何使用协程来执行异步IO操作,并通过
asyncio
库中提供的异步IO函数来实现非阻塞的IO操作。 - 协程的调度和并发:了解如何使用
asyncio
库来调度和并发执行多个协程任务。这包括使用asyncio
提供的事件循环(event loop)来调度协程的执行顺序,以及使用asyncio
中的asyncio.gather()
函数来并发执行多个协程任务。 - 错误处理和异常处理:了解如何处理协程中可能出现的错误和异常情况。包括使用
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开始引入了新的语法async
和await
,可以让coroutine
的代码更简洁易读。
1 | import asyncio |
分布式进程
Python的multiprocessing
模块不但支持多进程,其中managers
子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。
案例:假设我们有两台机器:Machine A 和 Machine B。原先的多进程程序在 Machine A 上运行,包括发送任务的进程和处理任务的进程。现在我们希望将发送任务的进程迁移到 Machine B 上,与处理任务的进程分开运行。
重点难点
- 通信机制更新:需要更新原先基于 Queue 的通信机制,以适应跨机器通信。
- 网络配置:确保两台机器能够相互通信,可以考虑使用网络协议如TCP/IP。
- 错误处理:处理跨机器通信可能会引入新的错误类型,需要考虑如何处理这些错误情况。
1 | # 在 Machine A 上运行的发送任务的进程 |
注意Queue的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,由Worker进程再去共享的磁盘上读取文件。
面向切面编程 (AOP)
面向切面编程是一种编程范式,它允许开发人员在不改变原始代码的情况下,将额外的功能(例如日志记录、性能监控、事务管理等)插入到程序中。通过将这些功能从原始代码中分离出来,AOP提供了一种更好的代码组织方式,并促进了可维护性和可扩展性。
AOP的基本概念
在AOP中,有两个核心概念:切片(Aspect)和连接点(Join Point)。切片是指额外功能的块,而连接点是指程序的特定位置,这些位置可以插入切片。通过在适当的连接点上应用切片,我们可以实现面向切面编程。
在Python中,我们可以使用装饰器(Decorator)来实现AOP。装饰器是Python的一种语法糖,它允许我们在不修改被装饰函数源代码的情况下,为函数添加额外的功能。下面是一个简单的示例:
1 | def log_decorator(func): |
AOP在实际应用中运用
日志记录
1 | def log_decorator(func): |
性能监控
通过在关键点插入性能监控代码,我们可以获取函数的执行时间,以便进行性能分析和优化。
1 | import time |
控制反转 (IOC) | 依赖注入(DI)
pass
python 注释的其他用法
当在编写 Python 代码时,开发人员可能会对代码中的某些部分进行注释,以控制代码静态分析工具的行为或告诉代码审查人员一些信息。
# noqa:F403
:忽略 “from module import *” 的错误。1
from module import * # noqa:F403
# noqa:F401
:忽略模块导入但未使用的警告。1
import module # noqa:F401
# type: ignore
:用于忽略类型检查器(如 Mypy)的类型错误。1
x: int = "hello" # type: ignore
# nosec
:用于忽略安全漏洞扫描器(如 Bandit)的安全问题。1
password = "secret" # nosec
# type: (type1, type2, ...)
:指定函数的参数类型和返回值类型。1
def add(a: int, b: int) -> int: # type: (int, int) -> int
# flake8: noqa
:全局禁用 Flake8 检查。1
# flake8: noqa
常用库
logging - 日志模块
logging是python自带的日志收集模块
logging库日志级别
默认日志级别为 warning
级别 | 级别数值 | 使用时机 |
---|---|---|
DEBUG | 10 | 详细信息,常用于调试 |
INFO | 20 | 程序正常运行过程中产生的信息 |
WARNING | 30 | 警告用户,虽然程序还在正常工作,但有可能发生错误 |
ERROR | 40 | 由于严重问题,程序不能执行一些功能 |
CRITICAL | 50 | 严重错误,程序已经不能继续运行 |
1 | import logging |
logging操作
1 | import logging |
logging四大组件
- 记录器 - logger : 是程序的入口,主要用来记录日志的信息
- 处理器 - handler :决定了日志的输出目的地
- 格式器 - formatter :设置日志内容的组成结构和消息字段
- 过滤器 - filter :提供更好的颗粒度控制,决定那些日志会被输出
日志流程图
配置/创建日志记录器 - logger
1 | import logging |
日志记录器常用方法
1 | # 设置日志级别 |
处理器 - 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 | fmt = '%(asctime)s|%(levelname)s|%(message)s|%(filename)%s:%(lineno)%s|%(message)%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 | # 导入必要的模块 |
日志模块封装
对日志模块进行封装可以让代码更加模块化和易于维护,也方便其他模块调用
1 | import logging |
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(少用)
一般时间处理归类为:
- 时间显示
- 时间转换
- 时间运算
time
返回当前时间的时间戳 - time.time()
将线程推迟指定时间运行 - time.sleep() // 单位s
将时间戳转换为当前时区的struct_time输出 - time.localtime()
1 | time.struct_time() |
日期格式化 - time.strftime(“%Y-%m-%d %H:%M:%S %p:%j”,time.localtime())
1 | time.strftime("%Y-%m-%d %H:%M:%S %p/%j", time.localtime()) |
将指定日期转换成 localtime - time.strptime()
1 | time.strptime("2024-04-01 12:12:12 PM","%Y-%m-%d %H:%M:%S %p") |
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 | d = datetime.datetime.now() |
把时间戳
返回的时间加上相应时间 - datetime.datetime.now() + datetim.timedelta(4) // 当前时间+4天
- 默认(天)
- datetime.datetime.now() + datetim.timedelta(hours=4) // 当前时间+4小时
时间替换 - d.replace(year=2999, month=11, day=30)
1 | d.replace(year=2999, month=11, day=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 | import string,random |
洗牌
1 | a = [0,1,2,3,4,5,6,7] |
pickle
持续化模块:就是让数据持久化保存
将python数据转换并保存在pickle格式的文件内 - pickle.dump(obj, file)
1 | # 将date转换成字节流并写入文件 |
将Python数据转换为pickle格式的bytes字串 - pickle.dumps(obj)
1 | import pickle |
从pickle格式的文件中读取数据并转换为Python的类型 - pickle.load(file)
1 | # 从文件中读取字节流并反序列化为对象 |
将pickle格式的bytes字串转换为Python的类型 - pickle.loads(bytes_object)
1 | # 假设以下字节流是通过pickle.dumps()序列化得到的 |
json vs pickle
可以使用json模块来处理JSON数据。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于
将Python对象转换为JSON字符串 - json.dumps(data)
1 | data = { |
将JSON字符串转换为Python对象 - json.loads(json_str)
1 | json_str = '{"name": "Alice", "age": 30, "city": "New York"}' |
读取JSON文件并解析 - json.load(f)
1 | with open('data.json', 'r') as f: |
将Python对象写入JSON文件 - json.dump(data, f)
1 | data = { |
hashlib sha - 加密模块
hashlib
创建一个hashlib
对象并计算SHA-256哈希值
1 | data = b"Hello, World!" # 要加密的数据,需转换为字节字符串 |
各种SHA算法的支持
1 | data = b"Hello, World!" |
计算文件的SHA-256哈希值
1 | import hashlib # 导入hashlib模块,用于计算哈希值 |
大数据分块计算哈希值
1 | # 对于大数据,可以分块计算哈希值,以降低内存使用量 |
shuti 模块,文件的复制、打包和压缩操作
文件复制
1 | import shutil |
文件打包(使用 tarfile
或 zipfile
)
1 | import shutil |
文件压缩
1 | import shutil |
正则 - re
什么是正则表达式
正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
正则表达式可以干什么
- 快速高效的查找与分析字符串
- 进行有规律查找比对字符串,也叫:模式匹配
- 具有查找、比对、匹配、替换、插入、添加、删除等能力。
re模块
Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有全部的正则表达式功能。
由于Python的字符串本身也用\
转义,所以要特别注意:
1 | s = 'ABC\\-001' # Python的字符串 |
因此建议使用Python的r
前缀,就不用考虑转义的问题了:
1 | s = r'ABC\-001' # Python的字符串 |
正则表达式模式
使用特殊符号表示字符:用\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}
。
我们来从左到右解读一下:
\d{3}
表示匹配3个数字,例如'010'
;\s
可以匹配一个空格(也包括Tab等空白符),所以\s+
表示至少有一个空格,例如匹配' '
,' '
等;\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 | #'run' or 'ran' |
匹配数字 - \d or \D
1 | # \d : decimal digit 数字的 |
匹配空白 - \s or \S
1 | # \s : any white space [\t \n \r \f \v] |
匹配所有字母和数字以及 ‘_’ - \w or \W
1 | # \w : [a-zA-Z0-9_] |
匹配空白字符串 - \b or \B
1 | # \b : (only at the start or end of the word) |
匹配任意字符 特殊字符 - \ or .
1 | # \\ : 匹配 \ |
匹配句首句尾 - $ or ^
1 | # ^ : 匹配line beginning |
是否匹配 - ?
1 | # ? : may or may nt occur |
多行匹配 - re.M
1 | # 匹配代码后面加上re.M |
匹配零次或多次 - *
1 | # * : occur 0 or more times |
匹配一次或多次 - +
1 | # + :occur 1 or more times |
可选匹配次数 - {n, m}
1 | # {n, m} : occur n to m times |
匹配多个正则公式,利用group添加命名输出
1 | # group |
去除惰性,寻找所有匹配 - findall
1 | # re.findall() |
替换匹配内容 - sub
1 | # re.sub( ,replace, ) |
分裂匹配内容 - split
1 | # re.split() |
将正则匹配规则进行包装 - compile
1 | # re. compile() |
一些常见的正则表达式
校验数字
1 | 1、数字:^[0-9]*$ |
校验字符
1 | 1、汉字:^[\u4e00-\u9fa5]{0,}$ |
特殊需求
1 | 1、Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ |
Pandas
Pandas 是一个热门的 Python 数据处理库,它提供了用于数据处理和分析的强大工具。
导入 Pandas:
1 | import pandas as pd |
创建 Pandas 数据结构:
创建 Series:
- Series 是一维标记数组,可以存储任意数据类型的元素。
- Series 由两部分组成:索引(index)和数据(data)
- Series 也支持向量化操作、标量运算、缺失值处理等
1
2
3
4
5
6
7import 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
7data = {'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
5import 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 | import sqlite3 |
MySQL
安装mysql驱动
1 | pip install mysql-connector-python |
使用案例
1 | import mysql.connector |
SQLAlchemy
连接已有数据库
连接mysql需要额外安装mysql驱动 mysql-connector-python , 才能成功连接
1 | pip install mysql-connector-python |
1 | from sqlalchemy import Column, String, create_engine, DateTime, Integer |
查找数据
1 | ## 所有查找 等同于 select * from t_user; |
更新数据
1 | # 创建会话 |
新增数据
1 | # 创建会话 |
删除数据
1 | # 创建会话 |
多表查询
1 | # 连接 User, UserPost, 和 Post 表 |
匹配以特定字符串开头的值:
1 | query = session.query(User).filter(User.username.like('prefix%')) |
匹配以特定字符串结尾的值:
1 | query = session.query(User).filter(User.username.like('%suffix')) |
匹配包含特定字符串的值:
1 | query = session.query(User).filter(User.username.like('%keyword%')) |
匹配特定长度的字符串:
1 | query = session.query(User).filter(User.username.like('___')) |
区间查询:
1 | query = session.query(User).filter(User.age.between(18, 30)) |
区间排序:
1 | query = session.query(User).order_by(User.age.asc()) |
回滚操作
1 | # 回滚操作示例 |
函数操作
1 | # 函数操作示例 |
缓存
1 | from sqlalchemy import Column, String, create_engine, DateTime, Integer, Index |
缓存更新策略
1 | # 缓存更新策略示例: |
Redis
安装redis-py
库来实现
1 | $ pip install redis |
连接到Redis数据库
1 | import redis |
使用Redis命令
1 | # 设置键值对 |