Python入门(1/3):Python入门(1/3)——第一阶段_python最新的解释器-优快云博客
Python入门(2/3):Python入门(2):第二阶段——面向对象-优快云博客
Python入门(3/3):Python入门(3)第三、四阶段:PySpark案例实战和Python高阶技巧-优快云博客
目录
-- 第三阶段 --
-- PySpark案例实战 --
一、 基础准备
1.什么是PySpark
Spark定义:Apache Spark是用于大规模数据处理的统一分析引擎
Spark对Python语言的支持,重点体现在,Python第三方库:PySpark之上。
PySpark是由Spark官方开发的Python语言第三方库。
2.pyspark库的安装
在“CDM”命令提示符内输入:pip install pyspark
或者使用国内代理镜像网站(清华大学源):pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspark
3.构建PySpark执行环境入口对象
PySpark执行环境入口对象是:类SparkContext的类对象
from pyspark import SparkConf,SparkContext
#创建SparkConf对象
conf = SparkConf().setMaster("local[*]").setAppName("test_spark_app")
#基于SparkConf类对象创建SparkContext对象 (sc是后续重要对象)
sc = SparkContext(conf=conf)
#打印PySpark的运行版本
print(sc.version)
#停止SparkContext对象的运行(停止PySpark程序)
sc.stop()
打印PySpark的运行版本结果:
4.Pyspark的编程模型
SparkContext类对象,是PySpark编程中一切功能的入口。
PySpark的编程,主要分为如下三大步骤:
- 数据输入
- 通过SparkContext类对象的成员方法,完成数据的读取操作,读取后得到RDD类对象
- 数据处理计算
- 通过RDD类对象的成员方法,完成各种数据计算的需求
- 数据输出
- 将处理完成后的RDD对象,调用各种成员方法,完成写出文件、转换为list等操作
二、数据输入
1.RDD对象
PySpark支持多种数据的输入,在输入完成后,都会得到一个:RDD类的对象
RDD全称为:弹性分布式数据集(Resilient Distributed Datasets)
PySpark针对数据的处理,都是以RDD对象作为载体,,即:
- 数据存储在RDD内
- 各类数据的计算方法,也都是RDD的成员方法
- RDD的数据计算方法,返回值依旧是RDD对象
2.Python数据容器转RDD对象
PySpark支持通过SparkContext对象的paralielize成员方法,将:list、tuple、set、dict、str转换为PySpark的RDD对象。
from pyspark import SparkConf,SparkContext
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#通过parallelize方法将Python对象加载到Spark内,成为RDD对象
rdd1 = sc.parallelize([1,2,3,4,5])
rdd2 = sc.parallelize((1,2,3,4,5))
rdd3 = sc.parallelize("abcdefg")
rdd4 = sc.parallelize({1,2,3,4,5})
rdd5 = sc.parallelize({"key1":"value1","key2":"value2"})
#如果要查看RDD里面有什么内容,需要有collect()方法
print(rdd1.collect())
print(rdd2.collect())
print(rdd3.collect())
print(rdd4.collect())
print(rdd5.collect())
sc.stop()
#输出结果:
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
[1, 2, 3, 4, 5]
['key1', 'key2']
3.读取文件转RDD对象
PySpark也支持通过SparkContext入口对象,来读取文件,来构建出RDD对象。
在D盘准备一个test.txt内容如下:
from pyspark import SparkConf,SparkContext
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#用textFile方法,读取文件数据加载到Spark内,成为RDD对象
rdd = sc.textFile("D:/test.txt") #文件存放的路径要写对,不然会报错
print(rdd.collect())
sc.stop()
#输出结果:
['hello', '666', 'spark', 'python', 'pyspark']
三、数据计算
1.map方法
功能:map算子,是将RDD的数据一条条处理(处理的逻辑 基于map算子中接收的处理函数),返回新的RDD。
链式调用:可多次调用算子
语法:
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize([1,2,3,4,5])
# #通过map方法将全部数据都乘以10(简单函数可以使用lambda)
# def func(data):
# return data * 10
#链式调用
rdd2 = rdd.map(lambda x : x * 10).map(lambda x : x + 5).map(lambda x : x - 2)
print(rdd2.collect())
#(T) -> U 接收一个传入参数,有一个返回值(对于参数和返回值的类型没有要求)
#(T) -> T 接收一个传入参数,有一个返回值(传入参数和返回值的类型要一致)
sc.stop()
#输出结果:
[13, 23, 33, 43, 53]
注意:Python3.12.5执行以上代码会报错,需要安装低版本的,比如Python3.10.11,再次执行则成功打印结果。
2.flatMap方法
功能:对RDD执行map操作,然后进行解除嵌套操作。
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize(["hello world","Happy Mid-Autumn Festival","nice to meet you"])
#需求:将RDD数据里面的一个个单词提取出来
rdd2 = rdd.flatMap(lambda x : x.split(" "))
print(rdd2.collect())
#输出结果:
['hello', 'world', 'Happy', 'Mid-Autumn', 'Festival', 'nice', 'to', 'meet', 'you']
3.reduceByKey方法
功能:针对KV型RDD,自动按照key分组,然后根据你提供的聚合逻辑,完成组内数据(value)的聚合操作。
KV型:二元元组
#用法:
rdd.reduceByKey(func)
#func:(V,V)->V
#接受2个传入参数(类型要一致),返回一个返回值,类型与传入要求一致
注意:reduceByKey中接收的函数,只负责聚合,不理会分组
分组是自动by key来分组的。
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize([('男',99),('男',22),('女',66),('女',88)])
# 求男生和女生两个组的成绩之和
rdd2 = rdd.reduceByKey(lambda a,b:a+b)
print(rdd2.collect())
#输出结果:
[('男', 121), ('女', 154)]
4.练习案例:单词计数
要求:读取文件,统计文件内,单词的出现量
文件内容如下:
# 1.构建执行环境入口对象
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
# 2.读取数据文件
rdd = sc.textFile("D:/test.txt")
# 3.取出所有单词
word_rdd = rdd.flatMap(lambda x : x.split(" "))
# 4.将所有单词都转换成二元元组,单词为Key,Value设置为1
word_with_one_rdd = word_rdd.map(lambda x : (x, 1))
# 5.分组求和
result_rdd = word_with_one_rdd.reduceByKey(lambda a, b: a + b)
print(result_rdd.collect())
#输出结果:
[('nihao', 4), ('python', 3), ('hello', 6), ('spark', 5), ('pyspark', 3)]
5.filter方法
功能:过滤想要的数据进行保留。
True的数据保留,False的丢弃
语法:
#语法:
rdd.filter(func)
#func:(T) -> bool 传入1个参数进来随意类型,返回值必须是True or False
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize([1,2,3,4,5])
#对RDD数据进行过滤,保留偶数
rdd2 = rdd.filter(lambda num: num% 2 == 0)
print(rdd2.collect())
#输出结果:
[2, 4]
6.distinct方法
功能:对RDD数据进行去重,返回新RDD
#语法
rdd.distinct() 无需传参
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize([1,1,1,2,2,3,4,5,5,5,5,6,8,8])
#对RDD数据进行去重
rdd2 = rdd.distinct()
print(rdd2.collect())
#输出结果:
[1, 2, 3, 4, 5, 6, 8]
7.sortBy方法
功能:对RDD数据进行排序,基于你指定的排序依据。
#语法
rdd.sortBy(func, ascending=False, numPartitions= 1)
#func: (T) -> U:告知按照RDD中的哪个数据进行排序,比如lambda x: x[1]表示按照RDD中的第二列元素进行排序
#ascending:True升序 False降序
#numPartitions:用多少分区排序
# 构建执行环境入口对象
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
# 1.读取数据文件
rdd = sc.textFile("D:/test.txt")
# 2.取出所有单词
word_rdd = rdd.flatMap(lambda x : x.split(" "))
# 3.将所有单词都转换成二元元组,单词为Key,Value设置为1
word_with_one_rdd = word_rdd.map(lambda x : (x, 1))
# 4.分组求和
result_rdd = word_with_one_rdd.reduceByKey(lambda a, b: a + b)
#到这里是上一个案例(单词计数)的代码,结果为[('nihao', 4), ('python', 3), ('hello', 6), ('spark', 5), ('pyspark', 3)]
# 5.对结果进行降序排序
final_rdd = result_rdd.sortBy(lambda x: x[1],ascending=False,numPartitions=1)
print(final_rdd.collect())
#输出结果:
[('hello', 6), ('spark', 5), ('nihao', 4), ('python', 3), ('pyspark', 3)]
8.练习案例
需求,复制以上内容到文件中,使用Spark读取文件进行计算:
- 各个城市销售额排名,从大到小
- 全部城市,有哪些商品类别在售卖
- 北京市有哪些商品类别在售卖
# 构建执行环境入口对象
from pyspark import SparkConf,SparkContext
import os
import json
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
# 读取数据文件得到RDD
file_rdd = sc.textFile("D:/orders.txt")
# 取出一个个JSON字符串
json_str_rdd = file_rdd.flatMap(lambda x : x.split("|"))
# 将所有一个个JSON字符串单转换成字典
dict_rdd =json_str_rdd.map(lambda x: json.loads(x))
# TODO 需求1:城市销售额排名
# 1.1取出城市和销售额数据
#(城市,销售额)
city_with_money_rdd = dict_rdd.map(lambda x: (x['areaName'],int (x['money'])))
# 1.2按城市分组,按销售额聚合
city_result_rdd = city_with_money_rdd.reduceByKey(lambda a, b: a+ b)
# 1.3 按销售额聚合结果进行排序
result1_rdd = city_result_rdd.sortBy(lambda x: x[1],ascending=False,numPartitions=1)
print("需求1的结果是:",result1_rdd.collect())
# TODO 需求2:全部城市,有哪些商品类别在售卖
# 2.1 取出全部的商品类别并去重
category_rdd = dict_rdd.map(lambda x: x["category"]).distinct()
print("需求2的结果是:",category_rdd.collect())
# TODO 需求3:北京市有哪些商品类别在售卖
# 3.1 过滤北京市的数据
beijing_data_rdd = dict_rdd.filter(lambda x: x["areaName"] =="北京")
# 3.2 取出全部商品类别
result3_rdd = beijing_data_rdd.map(lambda x: x["category"]).distinct()
print("需求3的结果是:",result3_rdd.collect())
#输出结果:
需求1的结果是: [('北京', 91556), ('杭州', 28831), ('天津', 12260), ('上海', 1513), ('郑州', 1120)]
需求2的结果是: ['平板电脑', '家电', '书籍', '手机', '电脑', '家具', '食品', '服饰']
需求3的结果是: ['平板电脑', '家电', '书籍', '手机', '电脑', '家具', '食品', '服饰']
四、数据输出
1.RDD输出为Python对象
collect算子
功能:将RDD各个分区内的数据,统一收集到Driver中,形成一个List对象。
#用法
rdd.collect()
#返回值是一个list
reduce算子
功能:对RDD数据集按照你传入的逻辑进行聚合。
#语法:
rdd.reduce(func)
#func: (T, T) -> T
#2参数传入 1个返回值,返回值和参数要求类型一致
take算子
功能:取RDD的前N个元素,组合成list返回给你。
count算子
功能:计数RDD有多少条数据,返回值是一个数字
collect、reduce、take、count代码示例
#collect、reduce、take、count代码示例
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备一个RDD
rdd = sc.parallelize([1,2,3,4,5])
#collect算子,输出为List对象
rdd_list: list = rdd.collect()
print(rdd_list)
print(type(rdd_list))
#reduce算子,对RDD进行两两聚合
num = rdd.reduce(lambda a, b: a+ b)
print(num)
#take算子,取RDD的前N个元素,组成list返回
take_list = rdd.take(3)
print(take_list)
#count算子,计数RDD有多少条数据,返回值是一个数字
num_count = rdd.count()
print(f"rdd内有{num_count}个元素")
sc.stop()
#输出结果:
[1, 2, 3, 4, 5]
<class 'list'>
15
[1, 2, 3]
rdd内有5个元素
2.输出到文件中
saveAsTextFile算子
功能:将RDD的数据写入文本文件中。
支持本地写出,hdfs等文件系统。
调用保存文件的算子,需要配置Hadoop依赖:
- 下载Hadoop安装包
- http://archive.apache.org/dist/hadoop/common/hadoop-3.0.0/hadoop-3.0.0.tar.gz
- 解压到电脑任意位置
- 在Python代码中使用os模块配置:os.environ['HADOOP_HOME']='HADOOP解压文件夹路径’
- 下载winutils.exe,并放入Hadoop解压文件夹的bin目录内
- https://raw.githubusercontent.com/steveloughran/winutils/master/hadoop-3.0.0/bin/winutils.exe
- 下载hadoop.dll,并放入:C:/Windows/System32 文件夹内
- https://raw.githubusercontent.com/steveloughran/winutils/master/hadoop-3.0.0/bin/hadoop.dll
2.1 输出为多个分区
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
os.environ["HADOOP_HOME"] = "D:/develop/hadoop-3.0.0"
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
sc = SparkContext(conf=conf)
#准备RDD1
rdd1 = sc.parallelize([1,2,3,4,5])
#准备RDD2
rdd2 = sc.parallelize([("hello",55),("world",66),("hi",77)])
#准备RDD3
rdd3 = sc.parallelize([[1,3,5],[6,8,10],[11,13,15]])
#输出到文件中
rdd1.saveAsTextFile("D:/output1")
rdd2.saveAsTextFile("D:/output2")
rdd3.saveAsTextFile("D:/output3")
sc.stop()
文件输出有多个结果文件:
2.2 修改RDD分区为1个
方式1,SparkConf对象设置属性全局并行度为1:
conf = SparkConf().setMaster("local[*]").setAppName("test_spark") conf.set("spark.default.parallelism","1") sc = SparkContext(conf=conf)
方式2,创建RDD的时候设置(parallelize方法传入numSlices参数为1):
#准备RDD1 rdd1 = sc.parallelize([1,2,3,4,5], numSlices=1) #准备RDD2 rdd2 = sc.parallelize([("hello",55),("world",66),("hi",77)], numSlices=1) #准备RDD3 rdd3 = sc.parallelize([[1,3,5],[6,8,10],[11,13,15]], numSlices=1)
就只有一个part-00000结果文件了。
* 输出的结果是一个文件夹,文件放在文件夹里。
* 有几个分区就输出几个结果文件。
五、综合案例
要求:读取文件转换成RDD,并完成:
- 打印输出:热门搜索时间段(小时精度)Top3
- 打印输出:热门搜索词Top3
- 打印输出:统计黑马程序员关键字在哪个时段被搜索最多
- 将数据转换为JSON格式,写出为文件
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "D:/Python/Python31011/python.exe" #python解释器的位置
os.environ["HADOOP_HOME"] = "D:/develop/hadoop-3.0.0"
conf = SparkConf().setMaster("local[*]").setAppName("test_spark")
conf.set("spark.default.parallelism","1")
sc = SparkContext(conf=conf)
#读取文件转换成RDD
file_rdd = sc.textFile("D:/search_log.txt")
# TODO 需求1:打印输出:热门搜索时间段(小时精度)Top3
# 1.1 取出全部的时问并转换为小时
# 1.2 转换为(小时,1)的二元元组
# 1.3 Key分组聚合Value
# 1.4 排序(降序)
# 1.5 取前3
result1 = file_rdd.map(lambda x: (x.split("\t")[0][0:2],1)).\
reduceByKey(lambda a, b: a + b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(3)
print("需求1的结果:",result1)
# TODO 需求2:打印输出:热门搜索词Top3
# 2.1 取出全部的搜索词
# 2.2(词,1)二元元组
# 2.3 分组聚合
# 2.4 排序
# 2.5 Top3
result2 = file_rdd.map(lambda x: (x.split("\t")[2], 1)).\
reduceByKey(lambda a, b: a + b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(3)
print("需求2的结果:",result2)
# TODO 需求3:打印输出:统计黑马程序员关键字在哪个时段被搜索最多
# 3.1 过滤内容,只保留黑马程序员关键词
# 3.2 转换为(小时,1)的二元元组
# 3.3 Key分组聚合Value
# 3.4 排序(降序)
# 3.5 取前1
result3 = file_rdd.map(lambda x: x.split("\t")).\
filter(lambda x: x[2] == "黑马程序员").\
map(lambda x: (x[0][:2], 1)).\
reduceByKey(lambda a, b: a+b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(1)
print("需求3的结果:",result3)
# TODO 需求4:将数据转换为JSON格式,写出为文件
file_rdd.map(lambda x: x.split("\t")).\
map(lambda x: {"time":x[0],"user_id":x[1],"key_word":x[2],"rank1":x[3],"rank2":x[4],"url":x[5]}).\
saveAsTextFile("D:/output_json")
sc.stop()
#输出结果:
需求1的结果: [('20', 3479), ('23', 3087), ('21', 2989)]
需求2的结果: [('scala', 2310), ('hadoop', 2268), ('博学谷', 2002)]
需求3的结果: [('22', 245)]
数据转换为JSON格式,写出为文件:
六、分布式集群运行
PySPark代码在大数据集群上运行,该部分由于没学过大数据,所以仅了解。
from pyspark import SparkConf,SparkContext
import os
os.environ["PYSPARK_PYTHON"] = "export/server/anaconda3/bin/python"
os.environ["HADOOP_HOME"] = "/export/server/hadoop-3.3.1"
conf = SparkConf().setAppName("spark_cluster")
conf.set("spark.default.parallelism","24")
sc = SparkContext(conf=conf)
#读取文件转换成RDD
file_rdd = sc.textFile("hdfs://m1:8020/data/search_log.txt")
# TODO 需求1:打印输出:热门搜索时间段(小时精度)Top3
result1 = file_rdd.map(lambda x: (x.split("\t")[0][0:2],1)).\
reduceByKey(lambda a, b: a + b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(3)
print("需求1的结果:",result1)
# TODO 需求2:打印输出:热门搜索词Top3
result2 = file_rdd.map(lambda x: (x.split("\t")[2], 1)).\
reduceByKey(lambda a, b: a + b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(3)
print("需求2的结果:",result2)
# TODO 需求3:打印输出:统计黑马程序员关键字在哪个时段被搜索最多
result3 = file_rdd.map(lambda x: x.split("\t")).\
filter(lambda x: x[2] == "黑马程序员").\
map(lambda x: (x[0][:2], 1)).\
reduceByKey(lambda a, b: a+b).\
sortBy(lambda x: x[1],ascending=False,numPartitions=1).\
take(1)
print("需求3的结果:",result3)
# TODO 需求4:将数据转换为JSON格式,写出为文件
file_rdd.map(lambda x: x.split("\t")).\
map(lambda x: {"time":x[0],"user_id":x[1],"key_word":x[2],"rank1":x[3],"rank2":x[4],"url":x[5]}).\
saveAsTextFile("D:/output_json")
sc.stop()
在Linux虚拟机上:
vim demo.py #将上面的代码写进该文件
cd /export/server/
cd spark-3.1.2-bin-hadoop3.2/
bin/spark-submit --master yarn --num-executors 3 --queue root.teach --executor-cores 4 --executor-memory 4g /home/hadoop/demo.py #将demo.py文件提交到hadoop的yarn集群上,有3个excutor,每个excutor有4个CPU核心可用,每个excutor有4g内存可用
-- 第四阶段 --
-- Python高阶技巧 --
一、 闭包
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
优点,使用闭包可以让我们得到:
- 无需定义全局变量即可实现通过函数,持续的访问、修改某个值
- 闭包使用的变量的所用于在函数内,难以被错误的调用修改
缺点:
- 由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存
nonlocal 关键字:在闭包函数(内部函数中)想要修改外部函数的变量值,需要用nonlocal声明这个外部变量。
#简单闭包
def outer(logo):
def inner(msg):
print(f"<{logo}>{msg}<{logo}>")
return inner
fn1 = outer("欢迎")
fn1("大家好")
fn2 = outer("天天开心")
fn2("你好")
#输出结果:
<欢迎>大家好<欢迎>
<天天开心>你好<天天开心>
#使用nonlocal关键字修改外部函数的值
def outer(num1):
def inner(num2):
nonlocal num1
num1 += num2
print(num1)
return inner
fn = outer(10)
fn(10)
fn(10)
#输出结果:
20
30
#使用闭包实现ATM小案例
def account_create(inital_amount=0):
def atm(num,deposit=True):
nonlocal inital_amount
if deposit:
inital_amount += num
print(f"存款:+{num},账户余额:{inital_amount}")
else:
inital_amount -= num
print(f"取款:-{num},账户余额:{inital_amount}")
return atm
atm = account_create()
atm(100)
atm(200)
atm(100,deposit=False)
#输出结果:
存款:+100,账户余额:100
存款:+200,账户余额:300
取款:-100,账户余额:200
二、装饰器
装饰器其实也是一种闭包,其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。
#装饰器的一般写法(闭包)
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()
#输出结果:
我睡觉了
睡眠中……
我起床了
#装饰器的快捷写法(语法糖)
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.单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。
在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
- 定义:保证一个类只有一个实例,并提供一个访问它的全局访问点
- 适用场景:当一个类只能有一个实例,而客户可以从一个众所周知的访问点访问它时。
两个类对象,会得到两个不同的地址:
class StrTools:
pass
s1 = StrTools()
s2 = StrTools()
print(s1)
print(s2)
为了保证某些类只能出现一个实例,采用单例模式:
"""
str_tools_py.py
建一个类,获取唯一的实例对象,持续复用它
"""
class StrToos:
pass
str_tool = StrToos()
"""
test.py
在这里复用类实例对象str_tool
得到两个打印结果的id相同
"""
from str_tools_py import str_tool
s1 = str_tool
s2 = str_tool
print(id(s1))
print(id(s2))
2.工厂模式
当需要大量创建一个类的实例的时候,可以使用工厂模式
即,从原生的使用类的构造去创建对象的形式,迁移到,基于工厂提供的方法去创建对象的形式。
- 使用工厂类的get_person()方法去创建具体的类对象
- 优点:
- 大批量创建对象的时候有统一的入口,易于代码维护
- 当发生修改,仅修改工厂类的创建方法即可
- 符合现实世界的模式,即由工厂来制作产品(对象)
class Person:
pass
class Worker(Person):
pass
class Student(Person):
pass
class Teacher(Person):
pass
#创建一个额外的工厂类
class PersonFactory:
def get_person(self,p_type):
if p_type == "w":
return Worker()
elif p_type == "s":
return Student()
else:
return Teacher()
pf = PersonFactory()
worker = pf.get_person("w")
stu = pf.get_person("s")
teacher = pf.get_person("t")
四、多线程
1.进程、线程和并行执行
进程:就是一个程序,运行在系统之上,那么便称之这个程序为一个运行进程,并分配进程ID方便系统管理。
线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位。
并行执行:同一时间做不同的工作。
- 多个进程同时在运行,即不同的程序同时运行,称之为:多任务并行执行。
- 一个进程内的多个线程同时在运行,称之为:多线程并行执行
2.多线程编程
threading模块:实现多线程
#最简单的使用方法:
thread_obj = threading.Thread(target=func) 创建线程对象
thread_obj.start() 启动线程执行
#语法:
import threading
thread_obj = threading.Thread([grroup [, target [, name [, args [, kwargs]]]]])
- group:暂时无用,未来功能的预留参数
- target:执行的目标任务名
- args:以元组的方式给执行任务传参
- kwargs:以字典方式给执行任务传参
- name:线程名,一般不用设置
单线程示例:
import time
def sing():
while True:
print("我在唱歌,啦啦啦。。。")
time.sleep(1)
def dance():
while True:
print("我在跳舞,呱呱呱。。。")
time.sleep(1)
if __name__ == '__main__':
sing()
dance()
#输出结果;
我在唱歌,啦啦啦。。。
我在唱歌,啦啦啦。。。
我在唱歌,啦啦啦。。。
………………
多线程示例:
import time
import threading
def sing():
while True:
print("我在唱歌,啦啦啦。。。")
time.sleep(1)
def dance():
while True:
print("我在跳舞,呱呱呱。。。")
time.sleep(1)
if __name__ == '__main__':
#创建一个唱歌的线程
sing_thread = threading.Thread(target=sing) #注意不要写成target=sing()
#创建一个跳舞的线程
dance_thread = threading.Thread(target=dance)
#让线程去干活
sing_thread.start()
dance_thread.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)
if __name__ == '__main__':
#创建一个唱歌的线程
sing_thread = threading.Thread(target=sing,args=("我要唱歌,啦啦啦",))
#创建一个跳舞的线程
dance_thread = threading.Thread(target=dance, kwargs={"msg":"我在跳舞哦,啦啦啦"})
#让线程去干活
sing_thread.start()
dance_thread.start()
五、网络编程
1.服务端开发
Socket(简称 套接字)是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要socket。
Socket负责进程之间的网络数据传输,好比数据的搬运工。
2个进程之间通过Socket进行相互通讯,就必须有服务端和客户端
Socket服务端:等待其它进程的连接、可接受发来的消息、可以回复消息
Socket客户端: 主动连接服务端、可以发送消息、可以接收回复
Socket服务端编程主要分如下几个步骤:
import socket
#创建Socket对象
socket_server = socket.socket()
#绑定ip地址和端口
socket_server.bind(("localhost",8888))
#监听端口
socket_server.listen(1)
#listen方法接受一个整数传参数,表示接受的链接数量
#等待客户端链接
#result: tuple = socket_server.accept()
#conn = result[0] #客户端和服务端的链接对象
#adress = result[1] #客户端的地址信息
conn, address = socket_server.accept()
#accept方法返回的是二元元组
#可以通过 变量1, 变量2 = socket_server.accept()的形式,直接接受二元元组内的两个元素
#accept()方法,是阻塞的方法,等待客户端的链接,如果没有链接,就卡在这不向下执行了
print(f"接收到了客户端的链接,客户端的信息是:{address}")
#接受客户端信息,要使用客户端和服务端的本次链接对象,而非socket_server对象
data: str = conn.recv(1024).decode("UTF-8")
#recv接受的参数是缓冲区大小,一般给1024即可
#recv方法的返回值是一个字节数组也就是bytes对象,不是字符串,可以通过decode方法通过UTF-8编码,将字节数组转换为字符串对象
print(f"客户端发来的消息是:{data}")
#发送回复消息
msg = input("请输入你要和客户端回复的消息:").encode("UTF-8") #encode可以将字符串编码为字节数组对象
conn.send(msg)
#关闭链接
conn.close()
socket_server.close()
下载网络调试助手NetAssist:Releases · nicedayzhu/netAssist · GitHub
首先运行Python文件,然后连接客户端,点击“开始连接”后:
然后客户端发送信息到服务器,在服务器回复:
客户端接收到信息,结束:
要保证服务端和客户端持续的通信,可以优化一下代码,加入While循环
#服务端代码
import socket
#创建Socket对象
socket_server = socket.socket()
#绑定ip地址和端口
socket_server.bind(("localhost",8888))
#监听端口
socket_server.listen(1)
#listen方法接受一个整数传参数,表示接受的链接数量
#等待客户端链接
#result: tuple = socket_server.accept()
#conn = result[0] #客户端和服务端的链接对象
#adress = result[1] #客户端的地址信息
conn, address = socket_server.accept()
#accept方法返回的是二元元组
#可以通过 变量1, 变量2 = socket_server.accept()的形式,直接接受二元元组内的两个元素
#accept()方法,是阻塞的方法,等待客户端的链接,如果没有链接,就卡在这不向下执行了
print(f"接收到了客户端的链接,客户端的信息是:{address}")
#保证可以一直通信,直到exit结束
while True:
#接受客户端信息,要使用客户端和服务端的本次链接对象,而非socket_server对象
data: str = conn.recv(1024).decode("UTF-8")
#recv接受的参数是缓冲区大小,一般给1024即可
#recv方法的返回值是一个字节数组也就是bytes对象,不是字符串,可以通过decode方法通过UTF-8编码,将字节数组转换为字符串对象
print(f"客户端发来的消息是:{data}")
#发送回复消息
msg = input("请输入你要和客户端回复的消息:")
if msg == "exit":
break
conn.send(msg.encode("UTF-8"))
#关闭链接
conn.close()
socket_server.close()
2.客户端开发
#客户端代码
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)
print(f"服务端回复的消息是:{recv_data.decode('UTF-8')}")
#关闭链接
socket_client.close()
先启动服务端,再启动客户端:
六、正则表达式
正则表达式,又称规则表达式(Regular Expression),是使用单个字符串来描述、匹配某个句法规则的字符串,常被用来检索、替换那些符合某个模式(规则)的文本。
1.基础匹配
Python正则表达式,使用re模块,并基于re模块中三个基础方法来做正则匹配。
分别是:match、search、findall三个基础方法
- re.match(匹配规则,被匹配字符串)
- 从被匹配字符串开头进行匹配,匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空。
- re.search(匹配规则,被匹配字符串)
- 搜索整个字符串,找出匹配的。从前向后,找到第一个后,就停止,不会继续向后。
- re.findall(匹配规则,被匹配字符串)
- 匹配整个字符串,找出全部匹配项。
import re
s = "python spark hello myname hi python"
#match 从头匹配
result = re.match("python",s)
print(result)
#search 搜索匹配
result = re.search("hello",s)
print(result)
#findall 搜索全部匹配
result = re.findall("python",s)
print(result)
#输出结果:
<re.Match object; span=(0, 6), match='python'>
<re.Match object; span=(13, 18), match='hello'>
['python', 'python']
2.元字符匹配
字符串的r标记表示,字符串内转移字符无效,作为普通字符使用 。
import re
s = "python1 @35$56 hello 47&^8 ##it"
#搜索全部数字
result = re.findall(r"\d",s)
print(result)
#搜索全部特殊字符
result = re.findall(r"\W",s)
print(result)
#搜索全部英文字符
result = re.findall(r"[a-zA-Z]",s)
print(result)
#输出结果:
['1', '3', '5', '5', '6', '4', '7', '8']
[' ', '@', '$', ' ', ' ', '&', '^', ' ', '#', '#']
['p', 'y', 't', 'h', 'o', 'n', 'h', 'e', 'l', 'l', 'o', 'i', 't']
3.练习案例
要求:
- 匹配账号,只能由字母和数字组成,长度限制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-]+)*@(qql163|gmail)(\.[\w-]+)+&
import re
#匹配账号
r = '^[0-9a-zA-Z]{6,10}$'
s = '123456789'
result1 = re.findall(r,s)
print(result1)
#匹配QQ号
r = '^[1-9][0-9]{4,10}$'
s = '123456'
result2 = re.findall(r,s)
print(result2)
#匹配邮箱地址
r = r'(^[\w-]+(\.[\w-]+)*@(qq|163|gmail)(\.[\w-]+)+$)'
s = 'abcd@qq.com'
result3 = re.match(r,s)
print(result3)
#输出结果:
['123456789']
['123456']
<re.Match object; span=(0, 11), match='abcd@qq.com'>
七、递归
1.什么是递归
在满足条件的情况下,函数自己调用自己的一种特殊编程技巧。
2. 递归需要注意什么?
- 注意退出的条件,否则容易变成无限递归
- 注意返回值的传递,确保从最内层,层层传递到最外层
3.os模块的3个基础方法
- os.listdir #列出路径下的内容
- os.path.isdir #判断路径是否是文件夹,是返回True
- os.path.exists #判断路径是否存在,存在返回True
import os
def test_os():
'''演示os模块的3个基础方法'''
print(os.listdir("D:/develop")) #列出路径下的内容
print(os.path.isdir("D:/develop/hadoop-3.0.0")) #判断路径是否是文件夹
print(os.path.exists("D:/develop")) #判断路径是否存在
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:/develop"))
Python篇 完结 !