python笔记
本文最后更新于:9 个月前
python特点
我用下来的最大感受是:很多情况下,py和编写者之间需要达成某种约定,编写者只要按照“正常”的格式来写代码,py就能解释。无需关注py怎么处理奇怪的输入:它会报错。
写python的时候就像是说自然语言,习惯了严谨程序代码后去写py会感到很不适应:这是怎么跑通的?这会不会太智能了?
以我目前的认知看来,py是不严谨,经不起推敲的(或者说,要想正确地解释py如何运行,需要在逻辑中加入一大堆的特判)
总之,学python(尤其是pandas这种库)时,深挖会发现它没有一个 简单合理的逻辑,它大多数逻辑都是通过反复的特判来符合我们的自然直觉。
一些语法
- 列表解析式
[i*i for i in range(1, 10) if i % 2 == 0]
- 可以多层
[m+n for m in ['ABC', 'DEF'] for n in m]
- 可以多值
[m+n for m, n in dic.items()]
,dic为字典
- 可以多层
- 列表解析式能生成列表,其实是用到了生成器表达式
g = (i*i for i in range(1, 10) if i % 2 == 0)
- 创建的
g
是一个生成器对象,上面被转化为了列表,它并不实际存储数据,而是存储规则,且也是可迭代的: for i in g:
使用生成器遍历,内存使用效率更高。
按自然语言理解即可
允许连续不等式
if a < b < c: ...
,if a < b > c: ...
python中每个对象都有真/假的固有属性,都可以用于条件判断,空的容器,数字0,None都被视为假;
and和or这种逻辑运算,返回的是操作对象,即确定表达式值的对象
2 or 3 >>> 2 #2就已经确定表达式为true
0 or 4 >>> 4 #4才能确定表达式为true
[] or {} >>> {} #{}确定了表达式为false
[] and 0 >>> [] #[]就已经确定了表达式为false
2 and [] >>> [] #[]确定了表达式为false
- 使用 is 判断两个变量是否为同一对象
- 使用 in 判断容器中有无某值
一些好用的处理函数
map(func, arr)
将可迭代对象的每个元素丢进func函数处理一遍,返回一个可迭代的map对象
filter(func, arr)
将可迭代对象的每个元素丢进func函数判断一遍,仅保留返回值为true的元素,返回filter对象
reduce(func, arr)
将可迭代对象第1,2个元素丢进func,得到的结果再和第3个元素丢进func…如此执行到尾,得到结果。
reduce
在python3中必须导入from functools import reduce
sorted(arr, key=, reverse=)
将可迭代对象排序后返回一个列表,默认递增,可传入函数key,用每个元素丢进key后的返回值来作为关键字排序。注意这里的key和reverse都是默认的命名关键字参数,指定时需给出名称
这里只能给出key函数,如果想像C++那样自定义比较函数的话,可以用到 functools.cmp_to_key(comp)
传入comp为一个两参数的函数,这个函数需要返回这两参数的比较结果(>0,==0,<0),而cmp_to_key
能非常神奇地根据给出的比较函数,返回一个值函数,这个值函数将我们需要比较的元素映射到一个可比较的对象,它们的偏序关系是相同的。
import functools
def comp(x, y):
return x[1] - y[1]
ls.sort(key=functools.cmp_to_key(comp))
注意comp的返回值是int,不能返回True和False
zip(a, b, c)
将任意数量个可迭代对象的元素一一拼成元组,组成一个可迭代的zip对象返回
内置函数 len, sum, max, min
,适用于大多数对象
- 像诸如map对象,filter对象,dict_item等等这种中间过程对象往往不能用这些内置函数,先转化为list比较好
基础结构
列表
a = [1, 2, 3, 4]
b = a #a,b为同一对象
b = a[:] #使用切片索引即为拷贝,不同对象
b = a.copy() #拷贝
c = a + b #合并
a.extend(b) #合并(扩展)
c = a * 3 #重复
a.index(3) #第一个值为3的位置(返回下标)
a.count(3)
a.sort(key = )
a.reverse()
字符串
大多内容和列表相同,但字符串不可变
s = "abcde"
s.index('cd') #可以直接寻找子串索引,返回的是起始位置
s.find('cd') #同index,但不会报错而是返回-1
s = "I am %d years old."%(10) #字符串格式化,规则和C的print大多相同
'{0} is {1:<8.3f}.'.format('tyj', '3.14159')
#format格式化,{}中:后的部分为格式化规则,和上条相同,:前为序号,指定使用format中的第几个参数
s.strip('a') #去除两端的所有'a'(默认空格)
s.replace('a', 'de') #替换
字符串连接,可以省略+号,如"abc" 'der' = 'abcder'
略记格式化规则:
- 最后一个字符表示类型,f为浮点数,d为整数,s为字符串等
x.y
中x表示占据的宽度,y表示有效位数(浮点数下为小数点后位数)- 前面加
#^
表示使用#填充空格,中间对齐(不填#默认使用空格填充),<>
分别为向左/右对齐
各种转换替换函数没啥特别的 在此处不记录
s = "ababb abb bac"
s.split('a') #分割时前后和aa之间的空字符串会保留
','.join([1, 2, 3])
str()
强转字符串:遵循目标print出来是什么样,转成字符串就是什么样
元组
可以不加括号地表示一个元组,例如:
>>> a = 2, 3, 1
>>> a
(2, 3, 1) #输出时总是有括号的
>>> b = 1, #单个值的元组,在后面加上,以区分
(1,)
>>> a, b, c = 2+3, "ds", 2313
>>> a, b, c
(5, 'ds', 2313)
>>> b
'ds'
这样可以同时给多个变量赋值,函数返回时也可以直接这样返回多个结果,本质上是因为都转化成了tuple
字典
字典是无序,可变的,本质是散列
字典的Key不一定要是字符串,可以是任意不可变的数据类型。
dic.values(), dic.keys(), dic.items()
都返回可迭代对象,但注意他们都不是列表。
遍历 dic.items()
得到的是键值对元组
集合
集合是无序,可变,不重复的,可以存放不同类型的数据,本质也是散列
集合中不能存储可变对象,和字典的Key是一个道理,哈希值不能变。
a = {1, 2, 3, 3} #自动去重
a = set([1, 2, 3, 3]) #从列表转化而来
a.add('3')
a.remove(2)
a & b #交并差对称差
a | b
a - b
a ^ b
判断字典,集合中有无元素都可以用 in 操作符方便地完成。
不可变集合 frozenset
可变、不可变类型
可以认为,python中一般的数据类型(int,tuple,string等)都是直接存储,而list和字典是可变对象,可以把list看成一个“指针”,而字典是一个哈希表
具体表现为:
在函数形参中使用list,它相当于“引用传递”,修改形参会同时修改实参的值
- list可以通过切片索引修改内容,但是如果切片索引作为右值,它是拷贝而非引用,如:
a = [1, 2, 3], b = a[0:2]
,b是a中内容的拷贝而非引用。
- list可以通过切片索引修改内容,但是如果切片索引作为右值,它是拷贝而非引用,如:
tuple元组是一个不可变的数据类型,但是:
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
这里,tuple还是指向同一个对象,没有变动,而这个对象本身可以更改,是合理的。宏观上看来,不可变对象包含可变对象,则还是可变的。
- 字典不可使用可变对象作为key值,也不能使用包含可变对象的元组作为key值,具体而言,一个对象作为key值需要它是可hash的
为什么说int,string都是不可变的?
- 区分变量和对象,
str = "asd"
,str是变量,”asd”才是字符串对象,这个对象是不可改变的 - 可以令
str = "asd".replace('a', 'A')
,但这样其实是让str这个变量指向了另一个字符串对象,并没有修改 “asd” 本身的值 int
居然也是个不可变对象,我们在令b = a
时,它们指向的是同一个对象,而在单独修改b时,如b = 20
,其实是让b指向了一个新的int对象20
str[0] = 'A'
是错误的,总结一下:我们可以直接给这个变量重新赋值到另一个对象,但不能修改这个不可变对象本身。- 更进一步,我们可以认为所有不可变对象其实都是固定的常量,比如让完全不想关的两个int一番操作后都等于2,他们也是同一个对象。
- 注意可变对象,例如
ndarray
,执行a += 1
与a = a + 1
是两码事。前者是改变了a
所指的对象,后者是让a
指向了一个新对象。
常见的不可变对象还有元组,不可变集合(frozenset
)
函数
py的函数可以在任何地方定义,且函数也可以作为一个普通对象存储。
值传递&引用传递
python有时值传递,有时引用传递这样的说法肯定是无法接受的
其实python压根就没管值传递还是引用传递,出现各种情况的原因,来自于数据类型的可变性不同。
假如形参是 $x$,实参是 $a$,那么python在传参的时候就是无脑 x=a
def f(b):
print(id(b))
b = 10
print(id(b))
a = 20
print(id(a))
f(a)
print(a, id(a))
这个程序跑一下,会发现:
2322675729232
2322675729232
2322675728912
20 2322675729232
a传参到b之后,我们发现b与a仍然有同一id(指向同样对象),然后给b赋值,按照上文中的知识,我们知道其实b是指向了一个新对象20,int对象本身是不可变的。所以造成了看起来是值传递的现象。
如果传递的是一个可变对象,就需要注意:首先传递过去后,形参b和a指向同一个对象,b[0] = 1这样的操作可以修改这个对象,但类似b = [2, 3]这样的操作,其实是让b这个引用变量更改了引用对象,从此之后它和a再没有关联了。
可以这么认为:python的变量全都可以看做是指针,它们指向一个具体对象。
默认参数
同样可以使用默认参数,默认参数需在必选参数后面
def power(x, p = 2, m = 7):
...
调用时可以直接指定参数位置,可以不必顺序一致
power(m = 9, x = 2)
而依据我们上面所说的py参数传递特性,如果我们在默认参数这里使用可变对象的话:
def appd(ls = []):
ls.append('a')
return ls
print(appd())
print(appd())
#结果:
>>> ['a']
['a', 'a']
事实上,作为默认参数的列表对象一开始就被确定了,就是这个[],调用时始终令ls = 此默认对象,如果修改了这个对象,未来的默认参数也相当于被更改。
可变参数
可以用这样的方式定义一个可变长参数,它将参数组装为一个元组
def func(*lls):
print(lls)
func(1, 2, 3, 4)
>>> (1, 2, 3, 4)
这里疑似出现返祖现象(*
如果我们尝试探究这个*的具体意义的话:
print(*[1, 2, 3, 4])
>>> 1 2 3 4
尝试输出 type(*[1, 2, 3, 4])
会得到一个报错,指明type不能传入多个参数
我理解*这个操作符能将列表拆开,在代码中散成 1, 2, 3, 4
的形式,可以拿去填充函数参数,如下:
a, b, c = *[1, 2, 3] #错!
def func(a, b, c):
...
func(*[1, 2, 3]) #对!
关键字参数
可以用这种方式定义一个关键字参数,它将参数组装成一个字典
def func(**kw):
print(kw)
func(age=2, city='hz')
>>> {'age': 2, 'city': 'hz'}
注意传递参数时,key值不需要加引号,会自动加上,我理解为语法糖
和上文的*类似,可以对字典进行**操作,将其展开成 name = 'tyj', age = 20
:
def func(**kw):
print(kw)
dic = {'name': 'tyj', 'age': 20}
func(**dic)
>>> {'name': 'tyj', 'age': 20}
命名关键字参数
def func(a, b, *, city, name='ttt'):
...
func(1, 2, city='hz', name='tyj') #city是命名关键字参数,必须指定
以*或者可变参数作为分割,后面的都是命名关键字参数,它不再是随意的,而是必须的,可以有缺省值,传递时必须指定参数名
应该没人这么写吧
def func(a, b, c=0, *args, name, city='hz', age, **kw)
必选参数,默认参数,可变参数/*,命名关键字参数,关键字参数,必须是这个顺序
我的理解是:
- 可变参数或*将普通参数和命名参数分割开来
- 普通参数和命名参数部分都可以有缺省值,普通参数的缺省值必须靠右
- 传递时依然从左往右传,上例中,优先给c传参,然后再是可变的args
- 可变参数的传递以命名的参数为终止
- 命名参数部分和左边同理,不过因为有名字作为索引,缺省值的位置和传入顺序都随意
- **kw这个参数必须在最后,传入的参数如果在前面没有对应key值就直接塞进kw
def func(*args, **kw)
这样的函数即可传入任意参数
重载
python在语法上不支持重载,我们可以传入可变的参数,再分类讨论
文件操作
注意点:
read
返回所有字符组成的字符串readline
返回一行字符串readlines
返回字符串列表,每项为一行write
只能写入字符串writelines
可以写入字符串或字符串列表,但他并不会自动加换行
#os库基本操作
os.mkdir(path) #创建空文件夹
os.rmdir(path) #只能删除空目录
os.listdir(path) #返回列表 包含path下所有文件(夹)名
os.remove(path) #删除文件
os.rename(path) #删除文件
os.getcwd() #获取当前工作路径
os.walk(path) #返回一个生成器,可以递归遍历path下所有文件,元素格式为(当前路径, [子目录], [子文件])
#os.path主要包含一些路径字符串操作
os.path.join(path, name): 在路径字符串后接name,能自动处理末尾的斜杠
os.path.abspath(path): 返回path对应文件的绝对路径
os.path.isfile(path)和os.path.isdir(path)函数分别检验给出的路径是一个文件还是目录。
os.path.exists(path): 用来检验给出的路径是否真地存在
os.path.split(path): 将path最后的文件(夹)和前面的路径名分开返回
os.path.splitext(): 分离文件名与扩展名,返回的前面文件名是包括路径的
os.path.basename(path): 返回path最后的文件(夹)名
os.path.dirname(path): 返回path中的文件夹路径(即若最后是文件就去掉),参数填__file__获得当前工作路径
os.path.getsize(name): 获得文件大小,如果name是目录返回0L
#shutil包含一些其他的文件操作
shutil.copy(src, dst)
shutil.copytree(src, dst)
shutil.move(src, dst)
#压缩等,略过
#pickle序列化
dumps(obj) #返回序列化对象
loads(pic_obj) #反序列化
dump(obj, file) #序列化到文件,必须是wb的文件
load(file) #将文件反序列化,必须是rb的文件
模块和包
注意点:
dir()
可以以列表方式返回模块中的所有内容包本身也是一个模块,其内容即
__init__.py
的内容模块引入后,其中的变量和函数都可使用
random模块
seed(x)
设置种子random()
返回[0, 1)之间的浮点数randint(a, b)
返回[a, b]之间的整数(注意右端包含)randrange(from, to, step)
在这个range内随机一个,参数规则同rangeuniform(a b)
返回[a, b]之间的浮点数,如果b>a能自动交换,但randint
会报错choice(seq)
从序列中随机抽取一个shuffle(seq)
将序列随机打乱顺序,注意是原地打乱而非返回
math模块
sin cos
输入都是弧度值radians
角度转弧度degrees
弧度转角度exp(x)
$e^x$log(x, base=e)
py面向对象
class node:
name = "lx" #此处声明的,既是静态变量(类成员),也是对象的成员
def __init__(self, s):
self.x = s
self.y = s
self.__z = s
self.name = 'asd' #成员和类变量同名,通过对象调用则为成员,通过类调用则为类变量
name = "23" #仅为此域内的一个name变量,和类无关
def size(self):
return self.x * self.y
!需要注意,python的类成员和对象成员不是定死的,而是随时可以新定义的,规范起见最好所有成员都在init中定义。
使用时,通过对象调用的就是对象成员,通过类名调用的就是类成员。
两个下划线开头的变量自动认为是私有变量,其他均为公有。
所有成员函数,第一个参数必须为self(调用时无视)
修改成员必须使用<对象名/类名>.成员名,这样才能辨别是类成员还是对象成员,直接写 name='23'
无效
继承
class shape(node):
def __init__(self, s, d):
node.__init__(self, s)
self.z = d
def size(self):
return super().size() + 2;//通过super调用其父类函数
允许多类继承
class shape(node1, node2, node3):
python在调用函数方面简单很多,逻辑就是:在当前类中找这个函数,没有就去父类中找,有多个父类时,找的顺序是深度优先,从左到右,这种找的逻辑自然构成了重写和多态。
isinstance(obj, clas)
判断obj是否是type或继承自type的实例
issubclass(clas1, clas2)
判断clas1是不是clas2的子类
Magic Method
python中有许多 __init__
形式的内置函数,可以自行实现。
__del__
:析构时调用
__getitem__
:相当于重载[]
__call__
:类可以像方法一样调用
__len__
:可以使用len()
等等
多态
python是一个弱类型语言,我们不需要用统一的父类指针,就能统一指定多种对象了,然后对它们都调用同名方法,自然就形成了多态
其实这是动态语言的“鸭子类型”特性,一个东西只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
所以只要保证方法同名 甚至不需要继承也能实现多态?
Numpy
用的最多是的ndarray,多维数组
它本质上是一个一维数组加上了每一维的长度信息
广播
核心是向右对齐
例如:shape(5, 3, 4)
可以与 shape(5, 3, 1)
运算,从右边开始逐个对齐,未对齐的那一方必须为1,将自动复制以匹配。
例:shape(5, 3, 4)
还可以与 shape(3, 1)
, shape(5, 1, 1)
运算,但不能和 shape(5, 3, 2)
,shape(5, 3)
运算。
索引方式
ndarray索引方式及其强大,但一些较复杂的操作不易于理解。
首先我个人认为从图表(二维、三维图表)的角度理解ndarray的各种操作非常困难,我会通过“索引集合”(自己编的名字)的角度去理解。
即:例如,对于一个四维的ndarray,我认为它是一个大集合,用 (x, y, z, k)
索引其中的单个元素,索引共有四维。
同时,我们也只需要关注最常用的用法,Python还是一门实用语言,一些过于偏怪的写法此处就不深究了。
切片索引
和列表类似,但有所不同。列表的切片作为右值时是拷贝,而数组切片仍然是引用。
在一个维度上用单值,会压缩掉这一维
a # shape(4, 3, 5)
a[:, 0:2, 0:4] # shape(4, 2, 4)
a[:, :, 0] # shape(4, 3)
整数序列
在某一维索引上传入一个整数序列,即在这一维单独选取这几个索引出来,注意这种方式作右值是拷贝而非引用
a # shape(4, 3, 5)
a[..., [0, 3]] # shape(4, 3, 2)
a[..., [0]] # shape(4, 3, 1) 不压维
同时在多个维度上使用整数序列,效果并不是它们的组合,不常用,为避免将简洁的问题复杂化,暂且跳过。
布尔索引
可以输入一个bool数组,这个数组的shape应该与原数组shape靠左对齐,例如:
a # shape(4, 3, 5)
b # bool shape(4, 3)
a[b] # shape(x, 5)
如上所示,布尔数组与前两位匹配,则它可以对前两维进行筛选,选出 $x$ 个布尔数组中值为True的索引(对),缩为一维,结果的shape为 (x, 5)
。
注意,这里虽然对齐逻辑和广播有些类似,但索引并没有广播机制,shape必须能严格向左对齐才行。
需要注意,布尔数组与整数序列索引作为右值时是复制,但作为左值时,仍然为引用。不管他们索引出来的形状多么奇怪,他们仍然是原数组的印象,修改可以直接反映到原数组中。
布尔数组索引最常见的用法就是对原数组做筛选:
a # shape(4, 3, 5)
a[a > 2] = 1 # 将a中所有大于2的元素变为1
a[a[..., 0] == 1] = 2 # 最后一维视为整体操作
混合索引
暂时没遇到什么实用写法。
ufunc
>>> arr = np.array([1, 2, 3])
>>> brr = np.array([1, 3, 4])
>>> arr + brr
array([2, 5, 7])
>>> arr + 5
array([6, 7, 8])
>>> arr > 2
array([False, True, True])
ndarray可以进行大多基础运算(+-*/%乘方取余求反等
),效果为每个元素都运算一次,也可以进行数组-数值间的运算,会先将数值视作同等大小的数组(这个特性叫广播)。
广播特性还体现在:矩阵与一个较低维矩阵运算,会自动扩展小矩阵成一个大矩阵再运算。
有了这个搞法,结合布尔数组索引,就可以写出下面这种看起来玄学的代码:
arr[arr > 5] = 0 #将arr中大于5的元素都变成0
还有一些函数 sin, cos, sqrt, exp, log, floor, ceil, round
sum, max, min, mean(均值), std(标准差)
等,可以直接运用到数组上
统计类ufunc可以指定axis,表示在哪一个维度上统计,例如:
[1, 2, 3]
[4, 5, 6]
np.sum(a, axis = 0)
[5, 7, 9]
# axis = 0即将0这一维度压掉,将其他维一样,第0维不一样的合并
自定义ufunc:写一个单值的函数func,然后使用
myufunc = np.frompyfunc(func, 1, 1) #后两个参数为func参数个数,返回值个数
func应用到单值上比较简单,应用到多值ufunc的此处暂略
any
np.any(arr, axis=?)
应用在一个bool数组arr上,若其中有True则返回True。
指定axis:例如axis = 1,每个含有True的行是True,其他行为False,返回一个bool数组。在做筛选时非常有用。
矩阵/向量
一般不使用np的matrix对象,因为ndarray就足够
向量点乘(内积):np.dot(m1, m2)
或 @
运算符
向量叉乘(外积):np.cross(m1, m2)
矩阵乘法使用 @
运算符,其他+-*乘方
运算符均为元素间运算
矩阵转置 a.T
排序:np.sort(a, axis=1)
默认每行一次排序,可以指定axis=None
将所有元素排序成一维数组。注意这里的sort是返回数组而非原地修改,列表的sort是原地修改。
np.argsort
返回的是每个元素排序后的下标,如果每行排一次序,下标即为列号。
Pandas
https://blog.csdn.net/qsx123432/article/details/111415998
Series
拥有索引index
和值value
,可以看做一个字典,索引可以是任何类型
构建:
Series([....])
通过列表构建,默认索引为自增的数字
Series(index=[..], data=[..])
它的索引和值都可以像列表一样有不同类型的数据,也可以指定dtype
DataFrame
表格型数据结构,是共用index的多个Series,每个Series作为一列,有一个列名。
构建:
DataFrame([[1, 2], [1, 3], [1, 4]])
通过可迭代对象构建,默认索引和列名都为自增的整数。
DataFrame({...}, index=)
通过字典构建,列名为字典的key,值为列表表示一列数据,若值为单个值,则必须指定index(值会重复多次)。
可以指定columns等指定列名。
索引:
DataFrame通常是先进行列索引(通过columns索引),他可以df.age
也可以df['age']
,得到的是一个Series,再通过index索引单元格。
索引列时可以给出一个列表,指定多列,返回就不是Series而是子表格了
可以进行行切片索引,得到一个子表格,但不能索引单行,也不支持同时索引:
df[0] #错(除非columns中有个整数0)
df[0:1] #对
df[0:1, 'age'] #错
df[0:1]['age'] #对,但返回的是Series
df['a':'c'] #对 index不为整数也可以切片索引,但这样是末端包含的
要同时索引,只能用专用的切片语法:
df.ix[0:1, 'age']
、df.ix[2, 1:2]
等
这套规则看起来很垃圾,但也是为了避免冲突和歧义,这样如果索引的是单个值,则认为是列索引,切片则认为是行索引,要同时索引则必须使用ix,总体上是没有歧义的。
更明确一点可以用df.loc[行索引]
…这就完全不会有歧义了,使用单值还是切片还是啥的都行。
布尔索引
和numpy类似,Series和DataFrame都可以和数值做一些运算,如:
df > 4
这个东西的返回结果还是一个表格,就是把df当中满足>4的元素改成True,其他改成False(Series同理)
df['B'] > 4
它返回一个Series,满足条件的index下为True
然后就可以用它们来索引:
df[df > 4] = 9
这里其实提供了三个特性:df可以用一个同Index同Columns,内容为Bool的表格来索引,返回的也是一个表格(是df的部分引用,没引用到的地方为NaN),对这个引用赋值,即对非NaN的部分赋值。。。
DataFrame可以使用布尔Series索引,相当于行索引返回子表格(引用),故也可直接更改
df[df['B'] > 2] = 9
一行全都改成9
统计方法
df.mean(axis=0)
看做二维数组,默认是按列聚集,返回series,原来的columns改成index。若axis=1
(按行聚集)则index不变,聚集后dataframe变成series
正则表达式
总的来说,调用方式有几种(以match为例):
re.match(模式串, 匹配串)
re.match(pattern对象, 匹配串)
pattern对象 = re.compile(模式串)
pattern对象.match(匹配串)
re的匹配结果有点乱…
先说一下匹配对象:其中包括了匹配结果的字符串,以及该串在原串中的位置等信息
匹配对象.group()
或 group(0)
返回匹配到的整个串,group(1)/group(name)
可以定向返回此次匹配中某个组的匹配结果
groups()
把所有组的匹配结果打包成元组返回,没有组则返回空
分组的圆括号是可以嵌套的,不影响,顺序按照左括号先后顺序。
match:必须从开头开始匹配,返回第一个匹配对象
search:返回第一个匹配对象
findall:找到所有匹配结果
- 若有多个分组,则返回所有匹配对象
groups()
组成的列表 - 若有一个分组,则不再打包元组,将所有匹配对象该组的匹配结果组成列表返回
- 否则则返回所有匹配到的整个串组成的列表
- 若有多个分组,则返回所有匹配对象
finditer:返回一个可迭代对象,其中每个元素是一个匹配对象
sub:替换 将匹配的所有串替换为另一个串,或按照一个函数替换,函数的参数为匹配对象
- 替代字符串中可以写\d,表示使用第d组的匹配结果
- 注意参数中替代字符串或函数写在前面
split:简单粗暴地将所有匹配的串删除,剩下的部分拆开来作为列表返回
爬虫
大一寒假简单看过爬虫但没看明白 这次突然感觉爬虫简单了很多
urllib
import urllib.request as ur
url = "https://m.huiyi8.com/fengjing/zuimei/"
headers = {
'User-Agent': r'Mozilla/5.0 (windows NT 6.3; WOW64) l\
ApplewebKit/537.36(KHTML, like Gecko) Chrome/43.0.235'}
req = ur.Request(url, headers = headers)
page = ur.urlopen(req).read()
page = page.decode('utf-8') #解码 格式规范化?
核心是urlopen,它直接打开一个url,然后返回一个response对象,这个对象可以直接read得到网页html源码
urlopen所用的url可以直接是网页地址,也可以进行一些包装,Request方法能将一些别的信息包到这个url当中形成一个请求,包括提交的内容,网页头等,返回结果是一个请求类型(不再是字符串了,urlopen当然也可以打开请求类型)。
读到的page解码一下,就和网页上按F12看到的html源代码一样了,是一大坨字符串,随后可以用正则表达式或者BeautifulSoup截取其中的信息。
BeautifulSoup
接上述代码,BeautifulSoup可以帮我们把html字符串再解释一下,变得更容易读取。
soup = BeautifulSoup(page, 'html.parser') #直接解释page 变成一个soup对象
soup中最主要的是Tag对象,即网页上的一个个标签,可以直接soup.p得到第一个p标签,也可soup.findAll(‘p’)返回所有p标签(和列表形式差不多但不是列表),findAll后面有**kwargs
可以加上各种属性的限制条件,如findAll('p', id='link')
标签对象可以tag.attrs
获得其中所有属性,以字典形式
也可以直接tag['src']
,把tag本身当做一个字典来取
tag也有子tag,可以用.content或.children遍历所有子tag,.descendants遍历所有子孙tag,还有各种父节点,兄弟节点差不多略过不谈
tag.string可以获取到tag下(子孙)的文本,因为文本不放在标签内部。若子孙中有多个文本,返回None。还有tag.strings,它返回所有子文本的可迭代对象