Python入门(3/3)第三、四阶段:PySpark案例实战和Python高阶技巧

 Python入门(1/3):Python入门(1/3)——第一阶段_python最新的解释器-优快云博客
Python入门(2/3):Python入门(2):第二阶段——面向对象-优快云博客
Python入门(3/3):Python入门(3)第三、四阶段:PySpark案例实战和Python高阶技巧-优快云博客


目录

-- 第三阶段 --

-- PySpark案例实战 --

一、 基础准备

1.什么是PySpark

2.pyspark库的安装

3.构建PySpark执行环境入口对象 

4.Pyspark的编程模型

二、数据输入

1.RDD对象

2.Python数据容器转RDD对象

3.读取文件转RDD对象

三、数据计算

1.map方法

2.flatMap方法

3.reduceByKey方法

4.练习案例:单词计数

5.filter方法

6.distinct方法

7.sortBy方法

8.练习案例

四、数据输出

1.RDD输出为Python对象

2.输出到文件中

2.1 输出为多个分区

 2.2 修改RDD分区为1个

五、综合案例

六、分布式集群运行

-- 第四阶段 --

-- Python高阶技巧 --

一、 闭包

二、装饰器

三、设计模式

1.单例模式

2.工厂模式

四、多线程

1.进程、线程和并行执行

2.多线程编程

五、网络编程

1.服务端开发

2.客户端开发

六、正则表达式 

1.基础匹配

 2.元字符匹配

 3.练习案例

 七、递归

1.什么是递归

2. 递归需要注意什么?

3.os模块的3个基础方法


-- 第三阶段 --

-- 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篇 完结 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值