python进阶
正则表达式
正则表达式概述
- 正则表达式:程序在数据处理时按照的一套可以通用的规则
- python中需要通过正则表达式对字符串进行匹配的时候,可以使用模块re
- 使用re.match(正则表达式,要匹配的字符串)进行匹配操作,如果有返回值,则说明要匹配的字符串符合正则表达式的要求,否则不符合,
- 如果想获得符合要求的字符串,则使用**返回值对象.group() **
匹配单个字符
- 匹单个字符可用如下字符进行匹配
- 如果想让字符 . 也能匹配\n,可以在正则表达式中传入参数re.S,即re.match(正则表达式,要匹配的字符串,re.S)
匹配多个字符
- 匹多个字符可用如下字符配合单个字符进行匹配
- match默认判断开头,不能判断结尾
- 要判断开头,则在表达式最前面加上一个^
要判断结尾,则在表达式最后面加上一个$
- 正则表达式使用举例:匹配出网易163的邮箱地址,且@符号之前有4到20位(数字、字母或者下划线),例如hello@163.com。程序如下:
import re
def demo():
addr = input("请输入邮箱地址:")
res = re.match(r"^[a-zA-Z0-9_]{4,20}@163\.com$", addr)
if res:
print("%s 该邮箱是网易163邮箱地址" % res.group())
else:
print("%s 该邮箱不是网易163邮箱地址" % addr)
if __name__ == "__main__":
demo()
分组
- 对于上面匹配163邮箱的demo,如果需要再匹配其他邮箱,则在正则表达式中几个邮箱两边加上一个小括号,并通过或运算(|)即可,如下面正则表达式
r"^[a-zA-Z0-9_]{4,20}@163\.com$"
- 对正则表达式进行分组可以使用如下一些字符
re模块的高级用法
- search :不从开头开始匹配,只要匹配到了一个符合正则表达式要求的数据,就算匹配成功并直接返回结束,适合找需要的内容
- findall :re.findall(正则表达式,要匹配的字符串) 可以匹配所有的符合正则表达式要求的数据
- sub :re.sub(正则表达式,要替换的内容,要匹配的字符串) 即可替换所有符合正则表达式的数据,此外要替换的内容也可以为一个函数的引用(调用函数)
- split:按照正则表达式进行切分字符串
GIL锁
什么是GIL
- GIL也叫全局解释器锁,实际上python语言和GIL并没有关系,只是在Cpython解释器中由于历史原因,难以移除GIL
- GIL给多线程带来的问题:每个线程在执行过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,从而多线程受到GIL的影响其实并不是真正的多线程
- 注意:GIL只对线程有影响,对进程和协程并没有影响
- 线程释放GIL锁的情况:在IO操作等可能会引起阻塞的system call(系统调用)耗时操作时,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL
- 线程释放GIL锁的方式:python3.x中使用计时器,当执行时间达到阈值后,当前线程释放GIL;python2.x中使用tickets计数,达到100时释放GIL
- python使用多进程是可以利用多核的CPU资源的
- 计算密集型程序使用多进程,io密集型程序使用多线程或者协程
- 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
- 如何解决GIL问题
⭕ 换用别的解释器 (不用Cpython)
⭕ 用别的语言编写的代码替代线程中的代码(python作为一门胶水语言在此可以体现)
深拷贝与浅拷贝
浅拷贝
- 浅拷贝:浅拷贝是对一个对象的顶层拷贝(只拷贝引用,并不会拷贝数据)
- 用模块copy里面的copy.copy()为浅拷贝,仅仅拷贝最上面一层(顶层)(如下图所示)
- 注意:当用copy.copy() 拷贝的数据存在不可变数据类型时,那么它不会进行浅拷贝,而是直接指向数据(因为不可变数据类型无法进行增删查改操作,因此用copy.copy()的时候它会自动判断数据的类型)例如下面这个案例
深拷贝
- 深拷贝:深拷贝是对一个对象所有层次的拷贝(递归拷贝引用和数据)
- 用模块copy里面的copy.deepcopy()为深拷贝,将每一层的内容都进行拷贝(如下图所示)
- 注意:当用copy.deepcopy() 拷贝的数据都是不可变数据类型时,那么它不会进行深拷贝,而是直接指向数据(但是如果数据中存在可变数据类型时,copy.deepcopy()仍然会进行深拷贝),例如下面这个案例
拷贝的其他方式(以下方式都是浅拷贝)
- 列表的切片
- 字典的copy方法
- 函数的实参传递给形参
私有化
python中几种变量名的含义
- xx(代表变量名):公有变量
- _xx:变量名前面有一个下划线,私有化属性或者方法,使用from module import 不能导入,但是类对象和子类可以访问(这只是一个python命名约定,表示这个名称是供内部使用的。 它通常不由python解释器强制执行,仅仅作为一种对程序员的提示)
- __xx:变量名前面有两个下划线,避免与子类中的属性命名冲突,无法在外部(包括子类)直接访问(名字重整所有访问不到)
- xx__:变量名后面有两个下划线,用于避免与python关键字产生冲突
- 通过name mangling(名字重整(目的就是以防子类意外重写基类的方法或者属性)如:_Class__object)机制就可以访问private了
*args与 **kwargs
*args
*args
:*
会使解释器把传递参数中多余的没有名字的参数(例如下图中的11, 22, 33, 44, 55, 66)传递给一个元组args
**kwargs
**
会使解释器把传递的参数中多余的有名字的参数(例如下图中的name=“laowang”, age=18)传递给一个字典kwargs,例如
得到的结果为
*
和**
不仅可以在形参中使用,还可以对形参中的*args
和**kwargs
进行拆包(例如上例可以在test1调用test2时传递的实参中的args加上*
,kwargs加上**
,则得到的结果与test1的结果是相同的,而不是args作为一个元组并且元素为另一个元组和一个字典,kwargs为空字典)- 如果定义的函数要保证可以接收不定长参数,则形参中的
*
和**
分别可以接收没有名字和有名字的多个以元组和字典的方式保存的参数 - 而实参中的
*
和**
表示的是拆包
property属性(封装的体现)
- 什么是property属性:一种用起来像是使用的实例对象属性的特殊属性,可以对应于某个方法
- 调用property属性下的方法时,调用方法不需要加括号(),相当于调用属性,例如下面这个例子(注意参数只有self)
property属性的应用
- property属性内部进行一系列的计算,最终将计算结果返回,而调用者不需要知道其是如何进行计算的,因此可以简化调用者在获取数据时的流程
- 使用property属性升级私有变量的get方法和set方法,甚至可以直接取代get方法和set方法
property属性的方式
- 使用装饰器方式在python2的经典类中只有@property一种方式
- 使用装饰器在python3中(或者是新式类中)有三种方式
⭕ @property
⭕ @price.setter ------> 可以进行设置值
⭕ @price.deleter -------> 可以删除值
如下图所示
- 使用类属性的方式:将property(方法名)的返回值传递给类属性
- property()方法有四个参数
⭕ 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
⭕ 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
⭕ 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
⭕ 第四个参数是字符串,调用 对象.属性.__ doc__ ,此参数是该属性的描述信息
with、上下文管理器
- 对于系统资源如文件、数据库连接、socket等等而言,应用程序打开这些资源并执行完成业务逻辑之后,必须要进行关闭资源
- 打开一个文件对应一个文件描述符,但是文件描述符在一个进程中的个数是有限的,当所有文件描述符用完后再需要打开文件,则会出现 “Too many open files” 的错误
- with:with的作用其实与try / finally语句是一样的,但是用with可以使代码更加简洁,例如
使用try/finally
使用with
- with的实现原理是使用上下文管理器
- 上下文管理器:任何实现了__ enter__() 和 __ exit__() 方法的对象都可以叫做上下文管理器,上下文管理器对象可以使用with关键字,例如
with会自动调用类中的__enter__内置方法,将__enter__方法的返回值传递给 f ,执行f.write()后无论是否产生异常都会调用__exit__内置方法使用__enter__方法的返回值关闭资源
- python3中实现上下文管理器的另一种方式:使用contextmanager装饰器
- 使用contextmanager装饰器(@contextmanager)通过yield将函数分割成两个部分,yield之前的部分在__enter__方法中执行,yield之后的部分在__exit__方法中执行,而紧跟在yield后面的值是该函数的返回值,例如