python学习之路 - python进阶【闭包、装饰器、设计模式、多线程、socket、正则表达式】
目录
- 一、python进阶
- 1、闭包
- a、概念
- b、nonlocal关键字
- c、优缺点
- 2、装饰器
- 3、设计模式
- a、单例模式
- b、工厂模式
- 4、多线程
- a、进程、线程
- b、并行执行
- c、多线程编程
- 5、网络编程
- a、概念
- b、服务端开发
- c、客户端开发
- 6、正则表达式
- a、概念
- b、基础方法
- c、元字符匹配
- 7、递归
一、python进阶
1、闭包
a、概念
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,将使用外部函数变量的内部函数称为闭包
def outer(msg1):
print(msg1)
def inner(msg2):
print(msg1 + "--" + msg2)
return inner
out = outer("这是msg1")
out("这是msg2")
out("这是第二个msg2")
结果为:
这是msg1
这是msg1--这是msg2
这是msg1--这是第二个msg2
- 如上述的案例,outer函数中的msg1只能作用域outer内部,外部无法获取到此变量
- 外部函数的参数只能设置一次
- 内部函数的变量可以设置多次,多次设置inner函数参数时,会多次执行inner函数内容
- 内部函数只能使用外部函数的参数,此种写法无法更改参数内容
b、nonlocal关键字
- 正常写法下,内部函数只能使用外部函数的参数,无法修改外部函数的参数,如果想要修改,则使用此关键字
- 在内部函数中将外部函数的参数用nonlocal关键字修饰,则能更改参数的值
- 多次调用内部函数时,多次的值会累加
def outer(msg1):
def inner(msg2):
nonlocal msg1
msg1 += msg2
print(f"{msg1}---{msg2}")
return inner
out = outer(10)
out(10)
out(10)
结果为:
20---10
30---10
c、优缺点
- 优点:
- 无需定义全局变量即可实现通过函数,持续的访问修改某个值
- 闭包使用的变量在函数内,难以被错误的调用修改
- 缺点:
- 由于内部函数持续引用外部函数的值,会导致部分内存空间不被释放,一直占用内存
2、装饰器
- 介绍:装饰器其实也是一种闭包,在不破坏目标函数原有代码和功能的前提下,为目标函数增加新功能
案例:定义方法睡眠5秒,睡前睡后分别输出内容
普通写法
from time import sleep
def func():
print("开始")
sleep(5)
print("结束")
func()
装饰器简单写法
def outer(func):
def inner():
print("开始")
func()
print("结束")
return inner
def sleep1():
from time import sleep
sleep(5)
out = outer(sleep1)
out()
装饰器高级写法
def outer(func):
def inner():
print("开始")
func()
print("结束")
return inner
@outer
def sleep1():
from time import sleep
sleep(5)
sleep1()
3、设计模式
设计模式是一种编程套路,可以极大方便程序的开发
最常见的设计模式就是面向对象
a、单例模式
在普通情况下,定义了一个类,多次实例化时,所得到的地址是不一样的
class Test:
pass
test1 = Test()
test2 = Test()
print(test1 == test2)
结果为:
False
如果想要多次实例化地址一样,就要用到单例模式
保证一个类只有一个实例,并且提供一个可以访问的全局访问点
第一步:test_tool.py文件中定义内容
class Test:
pass
test_one = Test()
#第二步:test.py文件中定义内容
from test_tool import test_one
test1 = test_one
test2 = test_one
print(test1 == test2)
结果为:
True
b、工厂模式
当需要大量创建一个类的实例的时候,可以使用工厂模式
当有多个类时,每次都要自己分别定义类才会获得对应对象
class Person:
pass
class Student:
pass
person = Person()
student = Student()
使用工厂模式,创建统一的创建对象的入口,便于代码维护
当发生修改时,只需要修改工厂类的方法即可
class Person:
pass
class Student:
pass
class Factory:
def get_person(self,type):
if type == "s":
return Student()
else:
return Person()
person = Factory().get_person("p")
student = Factory().get_person("s")
4、多线程
a、进程、线程
- 进程:就是一个程序,运行在系统之上,则称这个程序为一个运行进程,并分配进程ID方便系统管理
- 线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作量的最小单位
- 注意:进程之间的内存是隔离的,则不同的进程拥有各自的内存空间。线程之间是内存共享的,线程属于进程,一个进程内的多个线程是共享这个进程所拥有的内存空间的
b、并行执行
- 并行执行指同一时间做不同的工作
- 进程之间就是并行执行的,操作系统可以同时运行很多程序,这些程序都是并行执行的
- 线程也是可以并行执行的,同一个程序在同一时间做两件以上的事情,就称为多线程并行执行
c、多线程编程
python的多线程可以通过threading模块实现
基础语法
import threading
thread = threading.Thread(group=, target=, name=, args=, kwargs=, daemon=)
thread.start()
group:暂时无用,未来功能的预留函数
target:执行的目标任务名,就是方法名
args:以元组的方式给执行任务传参
kwargs:以字典的方式给执行任务传参
name:线程名,一般不用设置
案例:创建两个方法并使用多线程同时执行
def one(msg):
while True:
print(msg)
def two(msg):
while True:
print(msg)
import threading
#元组形式传参,只有一个元素时需要带上逗号才是元组
thread = threading.Thread(target=one, args=("one",))
#字典形式传参
thread2 = threading.Thread(target=two, kwargs={"msg": "two"})
thread.start()
thread2.start()
结果为:
one
twoone
twoone
two
two
......
5、网络编程
a、概念
socket:是进程之间通信的一个工具,负责进程之间的网络数据传输,好比数据的搬运工
2个进程之间通过socket进行相互通讯,就需要有服务端和客户端
socket服务端:等待其他进程的链接,接收客户端发来的信息并回复消息socket客户端:主动连接服务端,发送消息给服务端,并接收返回的信息
b、服务端开发
# 第一步:创建socket对象
socket_server = socket.socket()
# 第二步:为服务端绑定ip地址和端口
socket_server.bind(("127.0.0.1", 8888))
# 第三步:监听端口。
# 参数为允许连接的数量
socket_server.listen(1)
# 第四步:等待客户端连接。
# 会返回一个二元元组,第一个元素为客户端和服务端的连接对象,第二个元素为客户端的地址信息
# accept方法是阻塞方法,如果没有客户端链接会一直停留在这一行
socket_client, addr = socket_server.accept()
# 第五步:接收客户端发送的数据。
# 参数为接收数据的最大字节数
# 返回的时一个字节数组,不是字符串,可以通过decode方法转换为字符串
data = socket_client.recv(1024).decode("utf-8")
print(f"客户端接收的消息为:{data}")
# 第六步:向客户端发送数据。
socket_client.send("hello".encode("utf-8"))
# 第七步:关闭连接
socket_client.close() #此关闭的是客户端的连接
socket_server.close() #此关闭的是服务端
c、客户端开发
import socket
# 第一步:创建socket对象
socket_client = socket.socket()
# 第二步:连接到服务端
socket_client.connect(("127.0.0.1", 8888))
# 第三步:发送消息
# 发送的内容默认是通过字节传输的,通过encode方法将字符串转换为字节
socket_client.send("hello".encode("utf-8"))
# 第四步:接收返回的消息
# 参数为接收数据的最大字节数,此方法为阻塞的
# 返回的时一个字节数组,不是字符串,可以通过decode方法转换为字符串
recv_data = socket_client.recv(1024).decode("utf-8")
print(f"服务端回复的消息为:{recv_data}")
# 第五步:关闭连接
socket_client.close() #此关闭的是客户端的连接
6、正则表达式
a、概念
- 正则表达式:也称为规则表达式,是使用单个字符串来描述、匹配某个句法规则的字符串,常被用来检索、替换那些符合某个模式的文本
- 简单来说,正则表达式就是使用字符串来定义规则,并通过规则来验证字符串是否匹配
b、基础方法
- re.match(匹配规则,被匹配字符串)
- 从被匹配字符串的开头进行匹配,如果匹配成功则返回匹配对象,如果匹配不成功则返回空
- 注意是从开头匹配,如果中间有对应字符串是不算的
案例:找出字符串中是否包含python开头的字符串
#情况一
import re
s = "python test python test"
result = re.match("python",s)
print(result)
print(result.span()) #这里是打印匹配字符串开头和结束的下标
print(result.group()) #这里是打印匹配的字符串内容
结果为:
<re.Match object; span=(0, 6), match='python'>
(0, 6)
python
#情况二
import re
s = "1python test python test"
result = re.match("python",s) #因为s是以1开头,所以无法匹配python字符串
print(result)
结果为:
None
- re.search(匹配规则,被匹配字符串)
- 搜索整个字符串,找出匹配的。从前向后,找到第一个后就会停止
- 如果匹配成功,会返回匹配的位置信息。如果未匹配成功,会返回None
案例:找出字符串中是否包含python开头的字符串
#情况一
import re
s = "111python test python test"
result = re.search("python",s)
print(result)
print(result.span()) #这里返回的是匹配字符串的开头和结尾下标位置
print(result.group()) #这里返回的是匹配的字符串
结果为:
<re.Match object; span=(3, 9), match='python'>
(3, 9)
python
#情况二
import re
s = "111 test 2222 test"
result = re.search("python",s)
print(result)
结果为:
None
- re.findall(匹配规则,被匹配字符串)
- 会匹配整个字符串,找出全部的匹配项
- 如匹配成功会返回全部的匹配字符串内容。如果匹配不成功,会返回空List
#情况一
import re
s = "111python test python test"
result = re.findall("python",s)
print(result)
结果为:
['python', 'python']
#情况二
import re
s = "111 test 2222 test"
result = re.findall("python",s)
print(result)
结果为:
[]
c、元字符匹配
在上面我们进行了基础的字符串匹配,正则最强大的功能在于元字符匹配规则
- 单字符匹配
字符 | 功能 |
---|---|
. | 匹配任意1个字符(除了\n),\. 就是匹配点本身 |
[ ] | 匹配 [ ] 中列举的字符 |
\d | 匹配数字,即 0-9 |
\D | 匹配非数字 |
\s | 匹配空白,即空格,tab键 |
\S | 匹配非空白 |
\w | 匹配单词字符,即 a-z、A-Z、0-9、_ |
\W | 匹配非单词字符 |
- 数量匹配
字符 | 功能 |
---|---|
* | 匹配前一个规则的字符出现0至无限次 |
+ | 匹配前一个规则的字符出现1至无限次 |
? | 匹配前一个规则的字符出现0次或1次 |
{m} | 匹配前一个规则的字符出现m次 |
{m,} | 匹配前一个规则的字符出现最少m次 |
{m,n} | 匹配前一个规则的字符出现m到n次 |
- 边界匹配
字符 | 功能 |
---|---|
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
\b | 匹配一个单词的边界 |
\B | 匹配非单词边界 |
- 分组匹配
字符 | 功能 |
---|---|
| | 匹配左右任意一个表达式 |
( ) | 将括号中字符作为一个分组 |
\b | 匹配一个单词的边界 |
\B | 匹配非单词边界 |
案例一:找出字符串中所有数字
import re
s = "test@@1452282!!###python"
# 找出全部数字
result = re.findall(r'\d',s) #字符串前带上r的标记,表示字符串中转义字符无效,就是普通字符
print(result)
结果为:
['1', '4', '5', '2', '2', '8', '2']
案例二:找出字符串中所有特殊字符
import re
s = "test@@1452282!!###python"
# 找出全部特殊字符
result = re.findall(r'\W',s) #字符串前带上r的标记,表示字符串中转义字符无效,就是普通字符
print(result)
结果为:
['@', '@', '!', '!', '#', '#', '#']
案例三:找出字符串中所有英文字母
import re
s = "test@@1452282!!###python"
# 找出全部英文字母
result = re.findall(r'[a-zA-Z]',s) #字符串前带上r的标记,表示字符串中转义字符无效,就是普通字符
print(result)
结果为:
['t', 'e', 's', 't', 'p', 'y', 't', 'h', 'o', 'n']
案例四:匹配字符串中只能包含字母和数字,并且长度为6到18位
import re
r = "^[0-9a-zA-Z]{6,18}$"
result = re.findall(r,"123adalg")
result2 = re.findall(r,"laeooeoaoao11111111oa44252")
print(result)
print(result2)
结果为:
['123adalg']
[]
案例五:匹配字符串为纯数字,长度5到11位,第一位不为0
import re
r = "^[1-9][0-9]{4,10}$"
result = re.findall(r,"012345")
result2 = re.findall(r,"12355")
print(result)
print(result2)
结果为:
[]
['12355']
案例六:匹配字符串的邮箱地址,只允许qq、163、gmail这三种邮箱地址
import re
r = r'(^[\w-]+(\.[\w-]+)*@(qq|163|gmail)(\.[\w-]+)+$)'
result = re.match(r,"12346@162.com")
result2 = re.match(r,"12346@163.com")
print(result)
print(result2.group())
结果为:
None
12346@163.com
7、递归
递归:即方法(函数)自己调用自己的一种特殊的编程写法
案例:使用递归设计一个函数,获取一个路径下的所有文件名
import os
def get_all_files(path):
"""
获取指定路径下的所有文件
:param path: 被判断的文件夹
:return: list,包含全部的文件,如果目录不存在或者无文件则返回一个空list
"""
file_list = []
if os.path.exists(path):
for f in os.listdir(path): # 循环路径下的所有文件和文件夹
new_path = path + "/" + f
if os.path.isdir(new_path): # 判断是否为文件夹
file_list += get_all_files(new_path) # 递归调用
else:
file_list.append(new_path)
else:
print("路径不存在")
return []
return file_list