闭包
简单认识一下闭包
以下代码,内层inner函数不仅依赖于自身的参数b,还依赖于外层outer函数的参数a。inner就是一个闭包函数,既能访问外部变量,又保证外部变量不是全局的,不会被篡改掉,确保了外部变量的安全。
-
def outer(a):
-
def inner(b):
-
print(f"<{a}>{b}<{a}>")
-
return inner
-
n1 = outer('程序员') # n1的类型是一个函数
-
n1('学习python')
-
n1('学习java')
-
n2 = outer('软件测试工程师')
-
n2('功能测试')
-
n2('自动化测试')
如果要在内层函数修改外层函数的变量,需要用nonlocal
修饰,示例代码如下:
-
def outer(num1):
-
def inner(num2):
-
# 要对外层num1进行修改的话,需要nonlocal修饰
-
nonlocal num1
-
num1 += num2
-
print(num1)
-
return inner
-
fn = outer(10)
-
fn(5)
-
# 输出为15
'
运行
运行
案例:使用闭包方式简单实现ATM存取款
-
def account_create(initial_amount=0):
-
def atm(num, deposit=True):
-
nonlocal initial_amount
-
if deposit:
-
initial_amount += num
-
print(f"存款:+{num},账户余额:{initial_amount}")
-
else:
-
if initial_amount<num:
-
print(f"钱不够{num}了,取不出来!")
-
else:
-
initial_amount -= num
-
print(f"取款:-{num},账户余额:{initial_amount}")
-
return atm
-
atm = account_create()
-
atm(100, deposit=True)
-
atm(500, deposit=True)
-
atm(200, deposit=False)
-
atm(1000, deposit=False)
'
运行
运行
闭包的优缺点:
优点:
无需定义全局变量即可实现通过函数,持续的访问、修改某个值
闭包使用的变量位于在函数内,难以被错误的调用修改
缺点:
由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存
装饰器
装饰器其实也是一种闭包,其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。
示例代码1:
-
def outer(func):
-
def inner():
-
print("我要睡觉了......")
-
func()
-
print("睡醒了,我要起床了......")
-
return inner()
-
@outer # 相当于给sleep增加outer的装饰器
-
def sleep():
-
import random
-
import time
-
print("睡眠中......")
-
time.sleep(random.randint(1, 5))
-
sleep()
示例代码2:
(1)代码实现:统计一个函数的运行时间:
-
import time
-
# 统计一个函数的运行时间
-
def timer(func):
-
def gf():
-
start_time = time.time()
-
func()
-
end_time = time.time()
-
print("func运行的时间为:", end_time - start_time)
-
return gf
-
@timer
-
def foo():
-
time.sleep(3)
-
print("in foo")
-
foo()
(2)被装饰函数带参数
import time
-
# 统计一个函数的运行时间
-
def timer(func):
-
def gf(*args, **kwargs):
-
start_time = time.time()
-
func(*args, **kwargs)
-
end_time = time.time()
-
print("func运行的时间为:", end_time - start_time)
-
return gf
-
@timer
-
def foo(name, age):
-
time.sleep(3)
-
print("in foo", name, age)
-
foo("测试", 22)
(3)装饰器本身带参数
-
import time
-
# 统计一个函数的运行时间
-
def timer(timer_type):
-
print(timer_type)
-
def outer(func):
-
def inner(*args, **kwargs):
-
start_time = time.time()
-
func(*args, **kwargs)
-
end_time = time.time()
-
print("func运行的时间为:", end_time - start_time)
-
return inner
-
return outer
-
@timer(timer_type='second')
-
def foo(name, age):
-
time.sleep(3)
-
print("in foo", name, age)
-
foo("测试", 22)
(4)被装饰函数有返回值
-
import time
-
# 统计一个函数的运行时间
-
def timer(timer_type):
-
print(timer_type)
-
def outer(func):
-
def inner(*args, **kwargs):
-
start_time = time.time()
-
res = func(*args, **kwargs) # 接收返回值
-
end_time = time.time()
-
print("func运行的时间为:", end_time - start_time)
-
return res # 返回
-
return inner
-
return outer
-
@timer(timer_type='second')
-
def foo(name, age):
-
time.sleep(3)
-
print("in foo", name, age)
-
return name # 被装饰函数返回name
-
print(foo("测试", 22))
单例模式
背景
-
class Tool:
-
pass
-
t1 = Tool()
-
t2 = Tool()
-
print(t1)
-
print(t2)
-
# 输出结果:
-
# <__main__.Tool object at 0x000001CEB2B190D0>
-
# <__main__.Tool object at 0x000001CEB2B190A0>
通过print语句可以看出,它们的内存地址是不相同的,即t1和t2是完全独立的两个对象。
某些场景下,我们需要一个类无论获取多少次类对象,都仅仅提供一个具体的实例,用以节省创建类对象的开销和内存开销,比如某些工具类,仅需1个实例,即可在各处使用。
这就是单例模式所要实现的效果。
定义:
保证一个类只有一个实例,并提供一个访问它的全局访问点
适用场景:当一个类只能有一个实例,而客户可以从一个众所周知的访问点访问它
单例的实现模式:
工厂模式
背景:
当需要大量创建一个类的实例的时候,可以使用工厂模式即,从原生的使用类的构造去创建对象的形式迁移到,基于工厂提供的方法去创建对象的形式
-
class Person:
-
pass
-
class Worker(Person):
-
pass
-
class Student(Person):
-
pass
-
class Teacher(Person):
-
pass
-
worker = Worker()
-
stu = Student()
-
teacher = Teacher()
以上是传统方式构建基于Person的不同类对象。
采用工厂模式,代码就会变成如下:
-
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')
使用工厂类的get_person()方法去创建具体的类对象优点:
大批量创建对象的时候有统一的入口,易于代码维护;
当发生修改,仅修改工厂类的创建方法即可;
符合现实世界的模式,即由工厂来制作产品(对象)
多线程编程(threading)
进程:就是一个程序,运行在系统之上,那么便称之这个程序为一个运行进程,并分配进程ID方便系统管理。
线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位
并行执行:
多个进程同时在运行,即不同的程序同时运行,称之为: 多任务并行执行;
一个进程内的多个线程同时在运行,称之为:多线程并行执行;
写一段代码,我们先看下单线程运行下结果:
-
import time
-
def sing():
-
while True:
-
print("我在唱歌,啦啦啦......")
-
time.sleep(1)
-
def dance():
-
while True:
-
print("我在跳舞,呱呱呱......")
-
time.sleep(2)
-
if __name__ == '__main__':
-
sing()
-
dance()
要想输出既在唱歌,又在跳舞,是无法满足的。
使用多线程的话,一个线程在唱歌,一个线程在跳舞,就可以满足需求。
语法:
import threading
thread_obj = threading.Thread([group [,target [,name [, args [,kwargs]]]]])
- group: 暂时无用,未来功能的预留参数
- target: 执行的目标任务名
- args: 以元组的方式给执行任务传参
- kwargs:以字典方式给执行任务传参
- name: 线程名,一般不用设置
启动线程,让线程开始工作thread_obi.start()
-
import time
-
import threading
-
def sing():
-
while True:
-
print("我在唱歌,啦啦啦......")
-
time.sleep(1)
-
def dance():
-
while True:
-
print("我在跳舞,呱呱呱......")
-
time.sleep(2)
-
if __name__ == '__main__':
-
# 创建一个唱歌的线程
-
sing_thread = threading.Thread(target=sing)
-
# 创建一个跳舞的线程
-
dance_thread = threading.Thread(target=dance)
-
# 运行线程
-
sing_thread.start()
-
dance_thread.start()
Socket服务端编程
主要分为如下几个步骤
1.创建socket对象
import socket
socket_server = socket.socket(0)
2.绑定socket_server到指定IP和地址
socket_server.bind(host, port)
3.服务端开始监听端口
socket_server.listen(backlog)
backlog为int整数,表示允许的连接数量,超出的会等待,可以不填,不填会自动设置一个合理值
4.接收客户端连接获得连接对象
conn.address = socket_server.accept()
print(f"接收到客户端连接,连接来自: {address}")
accept方法是阻塞方法,如果没有连接,会卡在当前这一行不向下执行代码
accept返回的是一个二元元组,可以使用上述形式,用两个变量接收二元元组的2个元素
5.客户端连接后,通过recv方法,接收客户端发送的消息
-
while True:
-
data = conn.recv(1024).decode("UTF-8")
-
# recv方法的返回值是字节数组(Bytes》,可以通过decode使用UTF-8解码为字符串
-
# recv方法的传参是buffsize,缓冲区大小,一般设置为1024即可
-
if data == 'exit':
-
break
-
print("接收到发送来的数据:",data)
-
# 可以通过while True无限循环来持续和客户端进行数据交互
-
# 可以通过判定客户端发来的特殊标记,如exit,来退出无限循环
6.通过conn(客户端当次连接对象)调用send方法可以回复消息
-
while True:
-
data = conn.recv(1024).decode("UTF-8")
-
if data == 'exit':
-
break
-
print("接收到发送来的数据:", data)
-
conn.send("你好呀哈哈哈".encode("UTF-8”))
7.conn(客户端当次连接对象)和socket_server对象调用close方法,关闭连接
Socket客户端编程
主要分为如下几个步骤:
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")) # 消息需要编码为字节数组(UTF-8编码)
4.接收返回消息
-
while True:
-
send_msg = input("请输入要发送的消息").encode("UTF-8")
-
socket_client.send(send_msg)
-
recv_data = socket_client.recv(124) # 1024是缓冲区大小,一般1024即可
-
#recv方法是阻塞式的,即不接收到返回,就卡在这里等待
-
print("服务端回复消息为:",recv_data.decode("UTF-8"))#接受的消息需要通过UTF-8解码为字符串
5.关闭链接
socket_client.close()
正则表达式
正则表达式,又称规则表达式(Regular Expression),是使用单个字符串来描述、匹配某个句法规则的字符串,常被用来检索、替换那些符合某个模式(规则)的文本。
简单来说,正则表达式就是使用: 字符串定义规则,并通过规则去验证字符串是否匹配。
三个基础方法:
使用re模块,并基于re模块(import re
)中三个基础方法来做正则匹配。
(1)match
re.match(匹配规则,被匹配字符串)
从被匹配字符串开头进行匹配,匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空。
(2)search
re.search(匹配规则,被匹配字符串)
搜索整个字符串,找出匹配的。从前向后,找到第一个后,就停止,不会继续向后
(3)findall
re.findall(匹配规则,被匹配字符串)
匹配整个字符串,找出全部匹配项,找不到返回空list:[]
元字符匹配:
(1)单字符匹配:
实例:
-
import re
-
s = "learn @@python3 12EEAA!!66 ##study3"
-
# 找出全部数字
-
result1 = re.findall(r'\d', s)
-
print(result1) # ['3', '1', '2', '6', '6', '3']
-
# 找出特殊字符
-
result2 = re.findall(r'\W', s)
-
print(result2) # [' ', '@', '@', ' ', '!', '!', ' ', '#', '#']
-
# 找出全部英文字母
-
result3 = re.findall(r'[a-zA-Z]', s)
-
print(result3) # ['l', 'e', 'a', 'r', 'n', 'p', 'y', 't', 'h', 'o', 'n', 'E', 'E', 'A', 'A', 's', 't', 'u', 'd', 'y']
(2)数量匹配:
(3)边界匹配:
(4)分组匹配
实例:
-
# 匹配账号,只能由字母和数字组成,长度限制6到10位
-
r1 = '^[0-9A-Za-z]{6,10}$'
-
s1 = '12a3C6'
-
print(re.findall(r1, s1)) # 输出['12a3C6'],证明匹配成功
-
# 匹配QQ号,要求纯数字,长度5-11,第一位不为0
-
r2 = '^[1-9][0-9]{4,10}$'
-
s2 = '123456987'
-
print(re.findall(r2, s2)) # 输出['123456987'],证明匹配成功
-
# 匹配邮箱地址,只允许qq、163、gmail这三种邮箱地址
-
r3 = r'(^[\w-]+(.[\w-]+)*@(qq|163|gmail)(.[\w-]+)+$)'
-
s3 = '907218846@qq.com'
-
print(re.match(r3, s3))
递归
概念:方法(函数)自己调用自己的一种特殊编程写法
案例:找出一个文件夹中全部的文件
-
import os
-
def test_os():
-
# OS模块中的基础方法
-
# 将文件夹里面的内容显示出来
-
print(os.listdir("E:/python"))
-
# 判断给的路径是不是个文件夹
-
print(os.path.isdir("E:/python/libs"))
-
# 判断指定路径是否存在
-
print(os.path.exists("E:/python"))
-
def get_file(path):
-
file_list = []
-
if os.path.exists(path):
-
for f in os.listdir(path):
-
new_path = path + "/" + f
-
if os.path.isdir(new_path):
-
# 表明目录是文件夹不是文件,使用递归了
-
get_file(new_path)
-
else:
-
file_list.append(new_path)
-
else:
-
print(f"指定的目录{path}不存在")
-
return []
-
return file_list
-
if __name__ == '__main__':
-
print(get_file("E:/python"))
感谢每一个认真阅读我文章的人!!!
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。