第三章 高阶
1. 闭包
① 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
def outer(logo): def inner(msg): print(f"<{logo}>{msg}<{logo}>") return inner # 将 logo 传给外部函数,此时调用外部函数,此时内部函数使用的 logo 将一直是“黑马程序员” fn1 = outer("黑马程序员") # 此时再调用 fn1,就是使用上一步传过来的 inner 内部函数了 # 此时可以看作是 inner("大家好呀") fn1("大家好呀") # <黑马程序员>大家好呀<黑马程序员> fn1("学Python就来") # <黑马程序员>学Python就来<黑马程序员>
② 如果修改外部函数变量的值,需要使用 nonlocal 关键字修饰外部函数的变量才可在内部函数中修改它。
def outer(num1): def inner(num2): nonlocal num1 # 使用 nonlocal 关键字修饰后才能修改 num1 += num2 print(num1) return inner
③ 注意
Ⅰ. 优点:
-
无需定义全局变量即可实现,通过函数持续的访问、修改某个值。
-
闭包使用的变量是在函数内,难以被错误的调用修改。
Ⅱ. 缺点:
-
由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存。
2. 装饰器
① 装饰器其实也是一种闭包,其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。
② 语法
Ⅰ. 装饰器的一般写法(闭包写法),定义一个闭包函数,在闭包函数内部执行目标函数、完成功能的添加:
例子:希望给 sleep 函数,增加一个功能:
-
在调用 sleep 前输出:我要睡觉了。
-
在调用 sleep 后输出:我起床了。
def outer(func): def inner(): print("我要睡觉了") func() print("我起床了") return inner def sleep(): import random import time print("睡眠中......") time.sleep(random.randint(1, 5)) fn = outer(sleep) fn()
Ⅱ. 糖写法,使用 @outer 定义在目标函数 sleep 之上:
def outer(func): def inner(): print("我要睡觉了") func() print("我起床了") return inner @outer # 在目标函数上使用 @outer def sleep(): import random import time print("睡眠中......") time.sleep(random.randint(1, 5)) sleep()
3. 设计模式
① 单例模式
Ⅰ. 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
-
定义:保证一个类只有一个实例,并提供一个访问它的全局访问点。
-
适用场景:当一个类只能有一个实例,而客户可以从一个众所周知的访问点访问它时。
Ⅱ. 实现方式
-
在一个文件中定义如下代码
class StrTools: pass str_tool = StrTools()
-
在另一个文件中导入对象
from test import str_tool s1 = str_tool s2 = str_tool print(s1) # <test.StrTools object at 0x0000013D001DB910> print(s2) # <test.StrTools object at 0x0000013D001DB910>
Ⅲ. 单例模式就是对一个类,只获取其唯一的类实例对象,持续复用它:
-
节省内存
-
节省创建对象的开销
② 工厂模式
Ⅰ. 当需要大量创建一个类的实例的时候,可以使用工厂模式,即从原生的使用类的构造去创建对象的形式,迁移到基于工厂提供的方法去创建对象的形式。
-
大批量创建对象的时候有统一的入口,易于代码维护
-
当发生修改,仅修改工厂类的创建方法即可
-
符合现实世界的模式,即由工厂来制作产品(对象)
class Person: pass class Worker(Person): pass class Student(Person): pass class Teacher(Person): pass class Factory: def get_person(self, p_type): if p_type == 'w': return Worker() elif p_type == 's': return Student() else: return Teacher() factory = Factory() worker = factory.get_person('w') stu = factory.get_person('s') teacher = factory.get_person('t')
4. 多线程
① 基本概念:
-
进程:就是一个程序,运行在系统之上,那么便称之这个程序为一个运行进程,并分配进程 ID 方便系统管理。
-
线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位。
② 操作系统中可以运行多个进程,即多任务运行;一个进程内可以运行多个线程,即多线程运行。
③ 注意:
-
进程之间是内存隔离的,即不同的进程拥有各自的内存空间。这就类似于不同的公司拥有不同的办公场所。
-
线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的。
④ 多个进程同时在运行,即不同的程序同时运行,称之为:多任务并行执行。一个进程内的多个线程同时在运行,称之为:多线程并行执行。
⑤ 语法
import threading # 创建线程 thread_obj = threading.Thread([group [, target [, name [, args [, kwargs]]]]]) - group: 暂时无用,未来功能的预留参数 - target: 执行的目标任务名 - args: 以元组的方式给执行任务传参 - kwargs: 以字典方式给执行任务传参 -name: 线程名,一般不用设置 # 启动线程 thread_obj.start()
import time import threading def sing(msg): while True: print(msg) time.sleep(1) def dance(msg): while True: print(msg) time.sleep(1) # 创建一个唱歌的线程,通过元组的方式传参,如果元组只有一个元素,要加上一个 "," 号 sing_thread = threading.Thread(target=sing, args=("我要唱歌哈哈哈", )) # 创建一个跳舞的线程,通过字典的方式传参 dance_thread = threading.Thread(target=dance, kwargs={"msg": "我在跳舞哦啦啦啦"}) # 启动线程 sing_thread.start() dance_thread.start()
5. Socket
① socket(简称套接字)是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要 socket。
② 客户端和服务端
-
Socket 服务端:等待其它进程的连接、可接受发来的消息、可以回复消息
-
Socket客户端:主动连接服务端、可以发送消息、可以接收回复
③ 实现
Ⅰ. 服务端
1. 创建 socket 对象
import socket socket_server = socket.socket()
2. 绑定 socket_server 到指定 IP 和地址(只有服务端会有 2、3 步)
socket_server.bind(host,port)
3. 服务端开始监听端口
# backlog 为 int 整数,表示允许的连接数量,超出的会等待,可以不填,不填会自动设置一个合理值 socker_server.listen(backlog)
4. 接收客户端连接,获得连接对象(服务端后续的接收和回复消息都是使用 conn 进行的,因为服务端可以连接多个客户端,而 conn 指定了某个客户端的连接;客户端后续都是使用 socket_client)
# accept 方法是阻塞方法,如果没有连接,会卡在当前这一行不向下执行代码 # accept 返回的是一个二元元组,可以使用上述形式,用两个变量接收二元元组的2个元素 conn, address = socket_server.accept() print(f"接收到客户端连接,连接来自:{address}")
5. 客户端连接后,通过 recv 方法,接收客户端发送的消息
# 可以通过 while True 无限循环来持续和客户端进行数据交互 while True: # recv 方法的返回值是字节数组(Bytes),可以通过 decode 使用 UTF-8 解码为字符串 # recv 方法的传参是 buffsize,缓冲区大小,一般设置为 1024 即可 data = conn.recv(1024).decode("UTF-8") # 可以通过判定客户端发来的特殊标记,如 exit,来退出无限循环 if data == 'exit': break print("接收到发送来的数据:",data)
6. 通过 conn(客户端当次连接对象),调用 send 方法可以回复消息
while True: data = conn.recv(1024).decode("UTF-8") if data == 'exit': break print("接收到发送来的数据: ",data) msg = input("请输入要发送给客户端的信息:") # 消息需要编码为字节数组(UTF-8编码) conn.send(msg.encode("UTF-8"))
7. conn(客户端当次连接对象)和 socket_server 对象调用 close 方法,关闭连接
conn.close() socket_server.close()
Ⅱ. 客户端
1. 创建 socket 对象
import socket socket_client = socket.socket()
2. 连接到服务端
socket_client.connect(("localhost", 8888))
3. 发送消息
# 可以通过无限循环来确保持续的发送消息给服务端 while True: send_msg = input("请输入要发送的消息") # 通过特殊标记来确保可以退出无限循环 if send_msg == 'exit': break socket_client.send(send_msg.encode("UTF-8"))
4. 接收返回消息
while True: send_msg = input("请输入要发送的消息").encode("UTF-8") socket_client.send(send_msg) recv_data = socket_client.recv(1024) print("服务端回复消息为: ", recv_data.decode( "UTF-8"))
5. 关闭链接
# 最后通过 close 关闭连接,但服务端不会断开连接 socket_client.close()
6. 正则表达式
① 基础方法
Ⅰ. re.match(匹配规则, 被匹配字符串)
,从被匹配字符串开头进行匹配,匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空。
s = 'python itheima python itheima python itheima' result = re.match('python', s) print(result) # <re.Match object; span=(0, 6), match='python'> print(result.span()) # (0, 6) print(result.group()) # python s = '1python itheima python itheima python itheima' result = re.match('python', s) print(result) # None
注意:re.match()
会从头部开始匹配,如果头部都不匹配,后面就不会理会。
Ⅱ. re.search(匹配规则, 被匹配字符串)
,搜索整个字符串,找出匹配的。从前向后,找到第一个后,就停止,不会继续向后;整个字符串都找不到,返回 None。
s = '1python666itheima666python666' result = re.search('python', s) print(result) # <re.Match object; span=(1, 7), match='python'> print(result.span()) # (1, 7) print(result.group()) # python s = 'itheima666' result = re.search('python', s) print(result) # None
Ⅲ. re.findall(匹配规则, 被匹配字符串)
,匹配整个字符串,找出全部匹配项;找不到返回空 list: []。
s = '1python666itheima666python666' result = re.findall('python', s) print(result) #['python', 'python'] s = '1python666itheima666python666' result = re.findall('itcast', s) print(result) # []
② 元字符匹配
Ⅰ. 单字符匹配
字符 | 功能 |
---|---|
. | 匹配任意 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 次(m, 和 n 之间不能写空格) |
Ⅲ. 边界匹配
字符 | 功能 |
---|---|
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
\b | 匹配一个单词的边界 |
\B | 匹配非单词边界 |
Ⅳ. 分组匹配
字符 | 功能 |
---|---|
| | 匹配左右任意一个表达式 |
() | 将括号中字符作为一个分组 |
# 匹配账号,只能由字母和数字组成,长度限制 6 到 10位 r = '^[0-9a-zA-Z]{6,10}$' s = '123456_' print(re.findall(r, s)) # None # 匹配 QQ 号,要求纯数字,长度 5-11,第一位不为0 r = '^[1-9][0-9]{4,10}$' s = '123453678' print(re.findall(r, s)) # ['123453678'] # 匹配邮箱地址,只允许 qq、163. gmail 这三种邮箱地址 # abc.efg.daw@qq.com.cn.eu.qq.aa.cc # abc@qq.com # {内容}.{内容}.{内容}.{内容}.{内容}@{内容}.{内容}.{内容} # 字符串的 r 标记,表示当前字符串是原始字符串,即内部的转义字符无效而是普通字符 r = r'(^[\w-]+(\.[\w-]+)*@(qq|163|gmail)(\.[\w-]+)+$)' s = 'a.b.c.d@qq.com.a.z.c' print(re.match(r, s)) # <re.Match object; span=(0, 30), match='a.b.c.d@qq.com.a.z.c'>
7. 递归
① 递归:即方法(函数)自己调用自己的一种特殊编程,写法如:
def func(): if ...: func() return ...
函数调用自己,即称之为递归调用。
② 演示 os 模块的 3 个基础方法:
path = xxx # 路径 print(os.listdir(path)) # 列出路径下的内容 print(os.path.isdir(path)) # 判断指定路径是不是文件夹 print(os.path.exists(path)) # 判断指定路径是否存在
③ 递归案例——获取文件列表
def get_files_recursion_from_dir(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_files_recursion_from_dir(new_path)
else:
# 将文件加入到列表中
file_list.append(new_path)
else:
print(f"指定的目录{path},不存在")
return []
return file_list
if __name__ == '__main__':
print(get_files_recursion_from_dir("D:/test"))
更多学习内容持续更新中……