一、闭包
定义一个ATM功能的存取钱函数,通过全局变量account_amount来记录余额
尽管功能实现是ok的,但是仍有问题:
代码在命名空间上(变量定义)不够干净、整洁
全局变量有被修改的风险
解决方式:将变量定义在函数内部是行不通的 --假如我的初始余额需要修改
我们需要使用闭包:
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
可以看出内部函数依赖外部函数的变量
如何在内部函数中修改外部函数变量的值 nonlocal
闭包注意事项:
优点,使用闭包可以让我们得到:
无需定义全局变量即可实现通过函数,持续的访问、修改某个值
闭包使用的变量的所用于在函数内,难以被错误的调用修改
缺点:
由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存(一般都是变量 占用不大)
"""
演示闭包
"""
# 简单闭包演示
def outer(logo):
def inner(msg):
print(f"<{logo}>{msg}<{logo}>")
return inner
fn = outer("test") # fn--确定了logo变量为test的inner函数
fn("bear")
# 使用闭包实现ATM存取功能
def outer_money(money=0):
def inner_atm(num,deposit):
nonlocal money # 需要使用nonlocal关键字修饰外部函数的变量 才可在内部函数中修改它
if deposit:
money += num
print(f"存款{num}元,余额{money}元")
else:
money -= num
print(f"取款{num}元,余额{money}元")
return inner_atm # 必须要返回内部函数名 否则报错TypeError: 'NoneType' object is not callable
atm = outer_money(200)
atm(300,True)
二、装饰器
装饰器其实也是一种闭包,在闭包函数内调用目标函数。 其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。
1、装饰器的一般写法(闭包写法)
2、装饰器的快捷调用写法
三、设计模式
设计模式是一种编程套路,可以极大的方便程序的开发。最常见、最经典的设计模式,就是我们所学习的面向对象了。 除了面向对象外,在编程中也有很多既定的套路可以方便开发,我们称之为设计模式:
单例、工厂模式
建造者、责任链、状态、备忘录、解释器、访问者、观察者、中介、模板、代理模式 等等模式
1、单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某个类只有一个实例存在。 在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
· 定义: 保证一个类只有一个实例,并提供一个访问它的全局访问点
· 适用场景:当一个类只能有一个实例,而客户可以从一个众所周知的访问点访问它时。节省内存节省创建对象的开销
单例模式的实现:
class Str_Tools:
pass
str_tool = Str_Tools()
from str_tool_class import str_tool
st1 = str_tool
st2 = str_tool
print(id(st1))
print(id(st2))
输出:
1942925989136
1942925989136
2、工厂模式
当需要大量创建一个类的实例的时候, 可以使用工厂模式。即,从原生的使用类的构造去创建对象的形式 迁移到,基于工厂提供的方法去创建对象的形式
"""
演示装饰器的写法
"""
# 1.装饰器的一般写法-
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()
# 2.装饰器的快捷写法-
def outer(func):
def inner():
print("我要睡觉了")
func()
print("我起床了")
return inner
@outer
def sleep():
import random
import time
print("睡眠中.....")
time.sleep(random.randint(1,5))
sleep()
四、多线程
1、进程、线程
进程:一个程序运行在系统之上,那么便称这个程序为一个运行进程,并分配进程ID方便系统管理
线程:线程是属于进程的,一个进程可以开启多个线程 ,执行不同的工作,线程是实际工作的最小单位
进程 -好比一家公司,是操作系统对程序运行管理的单位
线程 -好比公司的员工,进程可以有多个线程(员工),是进程实际的工作者
操作系统中可以运行多个进程,即多任务运行
一个进程内可以运行多个线程,即多线程运行
注意点:
1.进程之间是内存隔离的,即不同的进程拥有各自的内存空间。这就类似于不同的公司拥有不同的办公场所
2.线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是贡献这个进程空间的。这就类似于公司员工之间是共分享办公场所的。
2、并行执行
并行执行的意思指的是同一时间做不同的工作。
进程之间就是并行执行的,操作系统可以同时运行好多程序,即这些程序都是并行执行
除了进程以外,线程其实也可以并行执行的。比如一个Python程序,其实是完全可以做到;
一个线程在输出:你好
一个线程在输出:hello
像这样一个程序在同一时间做多件不同的事,就称之多线程并行执行
3、多线程编程 --threading模块
Python的多线程可以通过threading模块来实现。
"""
演示工厂模式的创建实例
"""
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')
需要传参的话可以通过:
args参数通过元组(按参数顺序)传参
kargs参数通过字典的形式传参
"""
演示多线程的使用
"""
import threading
import time
def sing(msg):
while True:
print(msg)
time.sleep(1)
def dance(msg):
while True:
print(msg)
time.sleep(1)
if __name__ == '__main__':
# 创建一个唱歌的线程
sing_thread = threading.Thread(target=sing,args=("我在唱歌",))
# 创建一个跳舞的线程
dance_thread = threading.Thread(target=dance,args=("我要跳舞",))
# 启动线程
sing_thread.start()
dance_thread.start()
五、网络编程
1、socket网络编程;
socket简称套接字,是进程之间通信的一个工具,好比现实生活中的插座,所有的家用电器想要工作都是基于插座进行,进程之间想要进行网络通信需要socket
socket负责进程之间的网络数据传输,好比数据的搬运工
2、服务端和客户端
2个进程之间通过Socket进行相互通讯,就必须有服务端和客户端
Socket服务端:等待其他的进城连接、可以接受发来的消息、可以回复消息
Socket客户端:主动连接服务端、可以发送消息,可以接收回复
2.1 Socket服务端编程
步骤如下:
"""
演示Socket服务端开发
"""
import socket
# 创建Socket对象
socket_server = socket.socket()
# 绑定IP地址和端口
socket_server.bind(("localhost",8888))
# 监听端口
socket_server.listen(1) # 整数参数表示接收连接的数量
# 等待客户端连接
# result = socket_server.accept() # accept方法返回的是二元元祖(连接对象,客户端地址信息)
# conn = result[0] # 客户端和服务端的连接对象
# address = result[1] # 客户端的地址信息
conn,address = socket_server.accept()
# 注意:accept方法是阻塞的方法,等待客户端的连接,如果没有连接就卡在这一行不再向下执行
print(f"接受到了客户端的连接,客户端的信息是{address}")
while True:
# 接收客户端信息 ,使用客户端和服务端的连接对象
data = conn.recv(1024).decode("UTF-8")
# recv -接受的参数是缓冲区的大小,一般1024即可
# recv方法的返回值是一个字节数组也就是byte对象,可以通过decode方法使用utf-8编码将字节数组转换为字符串对象
print(f"客户端发来的消息是:{data}")
# 发送回复消息
msg = input("请输入你要和客户端回复的消息:") # encode可以将字符串编码为字节数组对象
if msg == 'exit':
break
conn.send(msg.encode("UTF-8"))
# 关闭连接
conn.close()
socket_server.close()
2.1 Socket客户端编程
步骤如下:
"""
演示Socket客户端开发
"""
import socket
# 创建Socket对象
socket_client = socket.socket()
# 连接到服务端
socket_client.connect(("localhost",8888))
while True:
# 发送消息
msg = input("请输入要给服务端发送的消息:")
if msg == 'exit':
break
socket_client.send(msg.encode("UTF-8"))
# 接收返回消息
recv_data = socket_client.recv(1024) # recv是缓冲区大小,一般给1024,同样recv是阻塞的
print(f"服务端回复的消息是:{recv_data.decode('UTF-8')}")
# 关闭连接
socket_client.close()
六、正则表达式
1、简述正则表达式
正则表达式,又称规则表达式(Regular Expression) 时使用单个字符串来描述、匹配某个语法规则的字符串,常被用来检索、替换那些符合某个模式(规则)的文本。
简单来说:正则表达式就是使用字符串定义规则,并通过规则去验证字符串是否匹配。
比如,验证一个字符串是否为符合条件的电子邮箱地址,只需要配置好正则规则,即可匹配任意邮箱。
比如通过正则规则: (^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$) 即可匹配一个字符串是否是标准邮箱格式。
如果不使用正则,使用if else来对字符串做判断就非常困难了。
2、正则表达式的三个基础方法
Python正则表达式,使用re模块,并基于re模块中的三个基础方法来做正则匹配
分别是:match、search、findall
2.1 re.match(匹配规则,被匹配字符串)
从被匹配字符串开头进行匹配,匹配成功返回匹配对象(包含匹配的信息--下标),匹配不成功,则返回None
注意:假如开头没有满足匹配规则的对象,即使后面有也会返回None
2.2 search(匹配规则,被匹配的字符串)
搜索整个字符串,找出匹配的。从前向后,找到第一个后就停止,不再继续向后
2.3 findall(匹配规则,被匹配的字符串)
匹配整个字符串,找出全部匹配项
"""
演示Python正则表达式re模块的3个基础匹配方法
"""
import re
s = "1pythonrning21bearupupythonread"
# match 从头匹配
result = re.match("21",s)
# print(result)
# print(result.span())
# print(result.group())
# search 搜索匹配
result1 = re.search("python",s)
print(result1)
print(result1.span(),result1.group())
# findall 搜索全部匹配
result2 = re.findall("python",s)
print(result2)
3、元字符匹配
3.1 单字符匹配
3.2 数量匹配
3.3 边界匹配
3.4 分组匹配
4、案例
匹配账号,只能由字母和数字组成,长度限制6到10位规则为: ^[0-9a-zA-Z]{6, 10}$
匹配QQ号,要求纯数字,长度5-11,第一位不为0 规则为:^[1-9][0-9]{4, 10}& [1-9]匹配 第一位,[0-9]匹配后面4到10位
匹配邮箱地址,只允许qq、163、gmail这三种邮箱地址 规则为:^[\w-]+(\. [\w-]+)*@(qq|163|gmail)(\.[\w-]+)+&
"""
演示Python正则表达式使用元字符进行匹配
"""
import re
# s = "ityuii@@python 22#d456opi\duhj2"
# result = re.findall('[0-9]',s) # 字符串前面加上r标记表示字符串中的转义字符无效在,就是普通字符的意思
# print(result)
# 匹配账号,只能由字母和数字组成,长度限制6到10位
# r = '^[0-9a-zA-Z]{6,10}$'
# s = "1234567A_"
# print(re.findall(r,s))
# 匹配QQ号,要求纯数字,长度5-11,第一位不为0
# r = '^[1-9][0-9]{4,10}$'
# s = "24113A9"
# print(re.findall(r,s))
# 匹配邮箱地址,只能允许qq、163、gmail这三种邮箱的地址
# {内容}.{内容}.{内容}@{qq、163、gmail}.{}.{}..
r = r'(^[\w]+(\.[\w])*@(qq|163|gmail)(\.[\w]+)*$)'
s = "[email protected]"
print(re.match(r,s))
七、递归 重要的一种算法
递归:即方法(函数)自己调用自己的一种特殊的编程写法
递归注意点:
注意退出的条件,否则容易变成无限递归
注意返回值的传递,确保从最内层,层层传递到最外层
os模块的3个方法:
os.listdir,列出指定目录下的内容
os.path.isdir,判断给定路径是否是文件夹,是返回True,否返回False
os.path.exists,判断给定路径是否存在,存在返回True,否则返回False
递归实现案例:
最典型的递归场景为找出一个文件夹中全部的文件。如图,在D:/test 文件夹内,有如下嵌套结构和所属的文件, 可以通过递归编程的形式完成