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中内容的拷贝而非引用。
  • 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 += 1a = 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内随机一个,参数规则同range

  • uniform(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,它返回所有子文本的可迭代对象


python笔记
http://www.lxtyin.ac.cn/2022/06/17/2022-06-17-python笔记/
作者
lx_tyin
发布于
2022年6月17日
许可协议