1-ETL基础认知(了解)
问题1: 如何将零散的数据,集中输入到数据仓库???
ETL
E: 数据抽取-- 抽取的是其他数据源中的数据
T: 数据转换-- 将数据转换为统一的格式,消除异常值,缺失值,对于错误的逻辑进行修改
L: 数据加载-- 将不同数据源的数据处理后加载到数仓或者输出到指定位置
问题2: ETL主要解决了数据分析中的什么???
数据孤岛问题
问题3: 什么是数据仓库???
存储数据的仓库,其实我们这里说的数据仓库就是存储ETL转换完成数据的数据平台.
在开发中我们有很多种资源但是一般提到节省系统资源,浪费资源 说的是内存, cpu 和 带宽
2-常见的数据存储形式(能够分辨常见的数据形式即可)
结构化数据: 每一个数据都可以使用行索引和列索引标记,同时使用行和列索引可以标记一个唯一的值
- 数据库中的表
- Excel
- CSV数据格式
- TSV数据格式(其实就是CSV格式的数据,但是分隔符用的是制表位
\t
)
**半结构化数据:**数据中的一部分数据是结构化数据,或者可以转换为结构化数据
注意: JSON数据和XML数据在任何情况下,都可以互相转换. 半结构化数据,通常会转换为结构化数据后,再做分析.
非结构化数据:
- markdown文本
- word文档
- mp3
- avi、mp4
注意: 非结构化数据,一般我们不做分析,可以提取其中的一部分,或者其中的规律,保存为结构化数据后再做分析.
3-Kettle的使用(了解)
ETL工具很多我们知道有这么个东西就行了
4-ETL实战案例需求分析(重点理解)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGWATkxu-1688340889073)(day01-知识复习.assets/1.png)]
问题1: 这是一个什么行业的业务数据?
零售行业的数据
问题2: 这个案例中有哪几种数据源? 所以采集需求有3个
- 订单数据-JSON
- 商品库数据-MySQL
- 后台日志数据-后台服务log日志文件
问题3: 这个案例中有哪几种数据去向?
-
数据仓库: 此处使用mysql数据库模拟数据仓库,在真实开发场景下,不会使用该服务作为数仓(数据吞吐量不足)
当下一个阶段Hadoop+Hive学习完成后,我们会搭建一个真实的数据仓库
-
CSV文件备份: 数据内容和数据仓库完全一致,主要为了做数据备份,恢复数据时使用.(真实业务开发中可能使用各种格式存储)
订单业务逻辑
核心思想: 获取全部的JSON数据文件,与数据库中存储的已采集数据文件进行对比,得到未采集的数据文件进行操作
功能:实现订单数据采集的程序
思路:
① 获取订单文件夹下面有哪些订单JSON文件
② 查询元数据库表中已经被采集的订单JSON文件,来对比确定要采集新的订单JSON文件
③ 针对待采集的新订单JSON文件,进行数据采集(ETL操作->mysql->csv)
④ 将本次采集的订单JSON文件,记录到元数据库的表中
商品业务逻辑
核心思想: 采集数据并记录采集的位置(id,行号,索引…),下一次采集先获取上一次采集的位置,然后继续操作
功能:数据源库中商品数据采集的主程序
思路:
① 查询元数据库表,获取上一次采集商品数据中 updateAt 的最大值
② 根据上一次采集商品数据中 updateAt 的最大值,查询数据源库商品表,获取继上一次采集之后,新增和更新的商品数据
③ 针对新增和更新的商品数据,进行数据采集(ETL->mysql->csv)
④ 将本次采集商品数据中的 updateAt 的最大值,保存到元数据库表中
日志业务逻辑
核心思想: 获取全部的日志数据文件,与数据库中存储的已采集数据文件进行对比,得到未采集的数据文件进行操作
功能:实现后台访问日志数据采集的程序
思路:
① 获取后台访问日志文件夹下面有哪些日志文件
② 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
③ 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
④ 将本次采集的访问日志文件,记录到元数据库的表中
总结:所有的需求都要做到采集过的数据不重复采集
5-项目结构搭建(操作)
(project name)工程名称: python_etl
工程名称可以随意定义,但是要符合标识符的命名规则:
- 不能使用关键字
- 只能使用字母数字和下划线
- 不能以数字开头
- 严格区分大小写
标识符: 程序员自己定义的,具有特殊功能或含义的字符组合
关键字: 系统或者解释器定义的,具有特殊功能或者含义的字符组合
5个目录:
- config:保存整个ETL工程的配置信息
- model:保存项目的数据模型文件
- test:保存单元测试的代码文件
- util:保存项目的工具文件
- learn:保存项目开发过程中的一些基础知识讲解练习文件(实际不需要)
问题1: 什么时候创建package 什么时候创建directory???
如果你当前目录中的文件可能被其他文件引用就是用package
如果你当前文件目录中的文件不会被任何文件引用就使用directory 例如: logs learn
注意:目录命名时,如果该目录时package,则文件名称必须为标识符, 如果是directory那么只需要没有中文即可.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GgdaRF6L-1688340889074)(day01-知识复习.assets/2.png)]
6-前置知识学习
6.1 日志模块(理解)
import logging
核心理解:
- 导入模块 (自己的文件名不要和内置模块名称重名,否则内置模块无法使用)
- 日志管理对象: 负责日志的收集工作 -
相当于执法记录仪
- 日志处理器: 负责日志的输出形式管理(终端/文件) -
相当于将记录仪连接的打印机
- 日志格式: 负责日志的输出格式管理 -
相当于打印机中的墨盒
日志格式:
'%(asctime)s - [%(levelname)s] - %(filename)s[%(lineno)d]:%(message)s'
logging日志模块的基本使用
"""
logging模块: 主要是为了记录程序运行期间产生的日志信息
日志器对象的创建和配置
1. 日志器对象的创建
2. 日志处理器的创建
3. 将日志处理器绑定到日志器对象上
日志的输出
1. 设置日志输出级别
2. 输出不同级别的日志信息
"""
# 0.导入模块
import logging
####################### 日志器对象的创建和配置 #####################
# 1. 日志器对象的创建
logger = logging.getLogger()
# 2. 日志处理器的创建
stream_handler = logging.StreamHandler()
# 3. 将日志处理器绑定到日志器对象上
logger.addHandler(stream_handler)
############################ 日志的输出 #######################
# 1. 设置日志输出级别 (默认warning及warning级别以上的日志信息会被输出)
# 忘记日志自己别的数据值,因为如果使用10 20 30 就会做不到见名知意,降低代码的可读性
logger.setLevel(logging.INFO)
# 2. 输出不同级别的日志信息
"""
日志级别 5个:
debug: 代码调试时输出的日志信息
info: 代码正常运行过程中输出的日志信息
warning: 代码的运行结果可能和预期结果发生偏差时输出的日志信息
error: 代码出现异常时输出的日志信息
critical: 一般我们遇不到,遇到了解决不了的问题 例如: 磁盘或者内存空间已满等.
"""
logger.debug('这是一个debug级别的日志信息')
logger.info('这是一个info级别的日志信息')
logger.warning('这是一个warning级别的日志信息')
logger.error('这是一个error级别的日志信息')
logger.critical('这是一个critical级别的日志信息')
logging日志模块设置日志输出格式
"""
日志器对象的创建和配置
1. 日志器对象的创建
2. 日志处理器的创建
3. 将日志处理器绑定到日志器对象上
4. 创建一个格式对象
5. 将日志格式对象绑定到日志处理器上
日志的输出
1. 设置日志输出级别
2. 输出不同级别的日志信息
"""
# 导包
import logging
##################### 日志器对象的创建和配置 #####################
# 1. 日志器对象的创建
logger = logging.getLogger()
# 2. 日志处理器的创建
stream_handler = logging.StreamHandler()
# 3. 将日志处理器绑定到日志器对象上
logger.addHandler(stream_handler)
# TODO: 4. 创建一个格式对象
fmt = logging.Formatter('%(asctime)s - [%(levelname)s] - %(filename)s[%(lineno)d]:%(message)s')
# TODO: 5. 将日志格式对象绑定到日志处理器上
stream_handler.setFormatter(fmt)
########################## 日志的输出 ##########################
# 1. 设置日志输出级别
logger.setLevel(logging.INFO)
# 2. 输出不同级别的日志信息
logger.debug('这是一个debug级别的日志信息')
logger.info('这是一个info级别的日志信息')
logger.warning('这是一个warning级别的日志信息')
logger.error('这是一个error级别的日志信息')
logger.critical('这是一个critical级别的日志信息')
logging模块将日志输出到文件中
本质就是更换日志处理器类型
StreamHandler: 将日志信息输出到终端上
FileHandler: 将日志信息输出到文件中
"""
日志器对象的创建和配置
1. 日志器对象的创建
2. 创建换一个文件类型的日志处理器
3. 将日志处理器绑定到日志器对象上
4. 创建一个格式对象
5. 将日志格式对象绑定到日志处理器上
日志的输出
1. 设置日志输出级别
2. 输出不同级别的日志信息
常用的日志处理器有两种
StreamHandler: 流式日志处理器,将日志信息输出到终端中
FileHandler: 文件日志处理器,将日志信息输出到文件中
"""
# 导入logging模块
import logging
#################### 日志器对象的创建和配置 ####################
# 1. 日志器对象的创建
logger = logging.getLogger()
# TODO: 2. 创建换一个文件类型的日志处理器
# 注意: filename要传入一个log文件的路径,可以使用绝对路径也可以使用相对路径,但是文件目录一定要存在,文件可以不存在
# 举例: ../logs/test.log 路径中 logs目录必须存在, test.log可以不存在
file_handler = logging.FileHandler(
filename='../logs/test.log',
mode='a',
encoding='utf8'
)
# 3. 将日志处理器绑定到日志器对象上
logger.addHandler(file_handler)
# 4. 创建一个格式对象
fmt = logging.Formatter('%(asctime)s - [%(levelname)s] - %(filename)s[%(lineno)d]:%(message)s')
# 5. 将日志格式对象绑定到日志处理器上
file_handler.setFormatter(fmt)
######################### 日志的输出 #######################
# 1. 设置日志输出级别
logger.setLevel(logging.INFO)
# 2. 输出不同级别的日志信息
logger.debug('这是一个debug级别的日志信息')
logger.info('这是一个info级别的日志信息')
logger.warning('这是一个warning级别的日志信息')
logger.error('这是一个error级别的日志信息')
logger.critical('这是一个critical级别的日志信息')
6.2 time模块(理解)
理解大于记忆
time模块的基本使用:
- time.sleep(数字):程序休眠几秒钟
- time.time():获取当前时间的时间戳(以秒为单位)
- time.time_ns():获取当前时间的时间戳(以纳秒为单位)
- time.localtime():获取当前的本地时间,结果是一个 struct_time 类的对象
- time.localtime(时间戳-秒):返回时间戳对应的本地时间,结果是一个 struct_time 类的对象
- time.mktime(struct_time对象):返回时间对应的时间戳(秒为单位)
- time.strftime(格式化字符串, 日期):将日期数据(struct_time对象)格式化一个字符串
- time.strptime(日期字符串,格式化字符串):将日期字符串转换为一个日期数据(struct_time对象)
%Y:4位数字的年份
%m:2位数字的月份
%d:2位数字的日期
%H:24小时制的小时
%M:2位数字的分钟
%S:2位数字的秒
"""
**time模块的基本使用:**
- time.sleep(数字):程序休眠几秒钟
- time.time():获取当前时间的时间戳(以秒为单位)
- time.time_ns():获取当前时间的时间戳(以纳秒为单位)
- time.localtime():获取当前的本地时间,结果是一个 struct_time 类的对象
- time.localtime(时间戳-秒):返回时间戳对应的本地时间,结果是一个 struct_time 类的对象
- time.mktime(struct_time对象):返回时间对应的时间戳(秒为单位)
- time.strftime(格式化字符串, 日期):将日期数据(struct_time对象)格式化一个字符串
- time.strptime(日期字符串,格式化字符串):将日期字符串转换为一个日期数据(struct_time对象)
"""
# 导入模块
import time
# time.sleep(数字):程序休眠几秒钟
print('hello')
time.sleep(1)
print('world')
# time.time():获取当前时间的时间戳(以秒为单位)
date_float1 = time.time()
print(date_float1) # 1672285269.896592
# time.time_ns():获取当前时间的时间戳(以纳秒为单位)
date_float2 = time.time_ns()
print(date_float2) # 1672285402027404100
# time.localtime():获取当前的本地时间,结果是一个 struct_time 类的对象
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=29, tm_hour=11, tm_min=44, tm_sec=52, tm_wday=3, tm_yday=363, tm_isdst=0)
date1 = time.localtime()
print(date1)
print(date1.tm_year)
print(date1.tm_mon)
...
# time.localtime(时间戳-秒):返回时间戳对应的本地时间,结果是一个 struct_time 类的对象
# 将一个数值型的时间戳,转换为struct_time时间类型
date2 = time.localtime(1672285269.896592)
print(date2)
# time.mktime(struct_time对象):返回时间对应的时间戳(秒为单位)
# 将一个时间从struct_time时间类型转换为数值型的时间戳
date_float3 = time.mktime(date2)
print(date_float3) # 这种方式的转换有时间损失,以秒为单位进行了四舍五入
# time.strftime(格式化字符串, 日期):将日期数据(struct_time对象)格式化一个字符串
# 将时间类型的数据按照一定的格式转换为字符串类型的数据
"""
%Y:4位数字的年份
%m:2位数字的月份
%d:2位数字的日期
%H:24小时制的小时
%M:2位数字的分钟
%S:2位数字的秒
"""
date_str1 = time.strftime('%Y年%m月%d日 %H时%M分%S秒', date2)
print(date_str1)
# time.strptime(日期字符串,格式化字符串):将日期字符串转换为一个日期数据(struct_time对象)
# 将字符串类型的时间数据,转换为struct_time类型数据
# 重点: 时间字符串和时间格式要完全统一,否则无法正常转换
date3 = time.strptime('2022年11月11日 08时02分24秒', '%Y年%m月%d日 %H时%M分%S秒')
print(date3)
6.3 unittest模块(掌握)
问题1:什么叫单元测试?
对于软件中最小的可测试单元进行检测或检查的方式就是单元测试
问题2: 单元测试在企业开发中一般是谁来写??
如果企业中测试工程师能力极强,可以由他来写,但是通常由开发人员来写,测试人员点击运行查看结果即可.
from unittest import TestCase
class 自定义单元测试类(TestCase):
def setUp(self) -> None:
"""该方法会在每个单元测试方法执行之前自动执行一次,可以做一些单元测试前的初始工作"""
pass
# 注意:所有的单元测试方法名必须以 test 开头,否则不能被 unittest 识别
def test_单元测试方法1(self):
pass
def test_单元测试方法2(self):
pass
...
运行单元测试代码,出现如下情况的原因是你的文件名称不符合标识符的命名规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJ59T3Bi-1688340889075)(day01-知识复习.assets/1672287171197.png)]
unittest模块的使用
"""
unittest模块主要是用于做单元测试
1. 创建一个测试类
2. 书写setup方法 在测试方法执行前执行
3. 书写tearDown方法,在测试方法执行后执行
4. 书写测试方法
"""
# 导入unittest模块
from unittest import TestCase
class TestDemo(TestCase):
def setUp(self) -> None:
"""在测试方法执行前执行"""
print('setUp方法被执行...')
def tearDown(self) -> None:
"""在测试方法执行后执行"""
print('tearDown方法被执行...')
# 所有的测试方法,必须以test开头,否则无法进行单元测试
def test_func1(self):
print('测试方法1被执行....')
def test_func2(self):
print('测试方法2被执行....')
注意事项:
- 测试文件名称要遵守标识符的命名规则,无论是否被调用
- 测试类,必须继承自TestCase
- 测试方法必须方法名以test_开头,否则无法识别
- -> None提示该方法没有返回值,但是是一个软性限制**(如果不遵循则报警告不会崩溃**)
6.4 OS模块(重要)
os模块的使用:
- os.getcwd():获取当前程序的运行工作路径
- os.listdir(目录):获取指定目录下的内容,返回一个list
os.path模块的使用:
- os.path.abspath(路径):返回指定路径的绝对路径
- os.path.dirname(路径):返回指定路径的上一级路径
- os.path.basename(路径):返回指定路径的最后一部分
- os.path.join(路径1, 路径2):将两个路径进行拼接
- os.path.isfile(路径):判断指定路径是不是一个文件,是返回True,否则返回False
- os.path.isdir(路径):判断指定路径是不是一个文件夹,是返回True,否则返回False
"""
os模块的使用:
- os.getcwd():获取当前程序的运行工作路径
- os.listdir(目录):获取指定目录下的内容,返回一个list
os.path模块的使用:
- os.path.abspath(路径):返回指定路径的绝对路径
- os.path.dirname(路径):返回指定路径的上一级路径
- os.path.basename(路径):返回指定路径的最后一部分
- os.path.join(路径1, 路径2):将两个路径进行拼接
- os.path.isfile(路径):判断指定路径是不是一个文件,是返回True,否则返回False
- os.path.isdir(路径):判断指定路径是不是一个文件夹,是返回True,否则返回False
"""
# __file__ 系统内置变量,用来表示当前文件所在位置的绝对路径
print(__file__)
# 导入os 模块
import os
# os.getcwd():获取当前程序的运行工作路径 : 当前文件所在的目录的绝对路径
print(os.getcwd())
# os.listdir(目录):获取指定目录下的内容,返回一个list
# 如果括号内什么也不写,获取的就是当前文件所在目录中所有文件的名称
print(os.listdir())
# 如果在括号内填写目录的路径,则获取的就是指定目录中的文件名称列表
# 注意我们使用的目录层级符号,如果是\要注意转译或者改为/
print(os.listdir('C:\\Users\\admin\\Desktop\\深圳36期-ETL阶段'))
print(os.listdir('C:/Users/admin/Desktop/深圳36期-ETL阶段'))
# os.path.abspath(路径):返回指定路径的绝对路径
print(os.path.abspath('../'))
# os.path.dirname(路径):返回指定路径的上一级路径
# 返回当前路径文件所在目录的路径
print(os.path.dirname(__file__))
# print(os.path.dirname('../')) # 如果给他的是相对路径,那么他返回的也是相对路径,一般获取dirname不适用相对路径
print(os.path.dirname(os.path.abspath('../'))) # 使用的时候转换为绝对路径
# os.path.basename(路径):返回指定路径的最后一部分
# 返回当前路径的文件名称
print(os.path.basename(__file__))
# print(os.path.basename('../')) # 在开发中basename也会使用绝对路径
print(os.path.basename('C:/Users/admin/Desktop/深圳36期-ETL阶段/python_etl'))
# os.path.join(路径1, 路径2):将两个路径进行拼接
# 一般为了防止出现拼接错误,我们会在路径1的末尾添加/,在路径2的开头删除斜杠
print(os.path.join('C:/Users/admin/Desktop/深圳36期-ETL阶段/', 'python_etl/learn(实际开发中不需要)'))
# os.path.isfile(路径):判断指定路径是不是一个文件,是返回True,否则返回False
print(os.path.isfile(__file__))
# os.path.isdir(路径):判断指定路径是不是一个文件夹,是返回True,否则返回False
print(os.path.isdir(__file__))
6.5 pymysql模块(重要)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSpUdQtp-1688340889075)(day01-知识复习.assets/image-20181006231547917.png)]
操作步骤:
- 导入模块
- 创建连接
- 创建游标
- 执行sql/获取查询集
- 关闭游标
- 关闭连接
分类:
- 建表建库语句
- 直接使用execute进行执行,不需要commit提交
- 非查询语句(增删改)
- 直接使用excute进行执行,需要使用commit提交
- 查询语句
- 直接使用excute进行执行,需要使用cursor.fetchall/fetchone/fetchmany进行结果集查询
数据库和数据表的创建
"""
操作步骤:
1. 导入模块
2. 创建连接
3. 创建游标
4. 执行sql/获取查询集
5. 关闭游标
6. 关闭连接
"""
############################ 数据库的创建 ########################
# # 1. 导入模块
# import pymysql
# # 2. 创建连接
# conn = pymysql.connect(
# host='127.0.0.1',
# port=3306,
# user='root',
# password='123456',
# charset='utf8'
# )
# # 3. 创建游标
# cursor = conn.cursor()
# # 4. 执行sql/获取查询集
# sql = 'create database python charset="utf8";'
# row_num = cursor.execute(sql)
# if row_num > 0:
# print('数据库创建成功...')
#
# # 5. 关闭游标
# cursor.close()
# # 6. 关闭连接
# conn.close()
############################ 数据表的创建 ########################
# 1. 导入模块
import pymysql
# 2. 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
charset='utf8',
database='python'
)
# 3. 创建游标
cursor = conn.cursor()
# 4. 执行sql/获取查询集
sql = 'create table person(id int, name varchar(100));'
row_num = cursor.execute(sql)
if row_num > 0:
print('数据表创建成功...')
# 5. 关闭游标
cursor.close()
# 6. 关闭连接
conn.close()
数据记录的增删改操作
"""
操作步骤:
1. 导入模块
2. 创建连接
3. 创建游标
4. 执行sql/获取查询集
在增删改数据时,要注意数据提交
5. 关闭游标
6. 关闭连接
"""
###################### 增 ####################
# 1. 导入模块
import pymysql
# 2. 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
charset='utf8',
database='python'
)
# 3. 创建游标
cursor = conn.cursor()
# 4. 执行sql/获取查询集
sql = 'insert into person values(1, "小明"),(2,"小芳");'
# 执行sql语句
row_num = cursor.execute(sql)
if row_num > 0:
print('数据记录插入成功....')
# 只要是事务型操作(增删改),就必须提交后才能生效(sql数据的执行都是在内存中的,只有commit之后才能落盘)
conn.commit()
# 5. 关闭游标
cursor.close()
# 6. 关闭连接
conn.close()
###################### 删 ####################
# 1. 导入模块
import pymysql
# 2. 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
charset='utf8',
database='python'
)
# 3. 创建游标
cursor = conn.cursor()
# 4. 执行sql/获取查询集
sql = 'delete from person where id=1;'
# 执行sql语句
row_num = cursor.execute(sql)
if row_num > 0:
print('数据记录删除成功....')
# 只要是事务型操作(增删改),就必须提交后才能生效(sql数据的执行都是在内存中的,只有commit之后才能落盘)
conn.commit()
# 5. 关闭游标
cursor.close()
# 6. 关闭连接
conn.close()
###################### 该 ####################
# 1. 导入模块
import pymysql
# 2. 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
charset='utf8',
database='python'
)
# 3. 创建游标
cursor = conn.cursor()
# 4. 执行sql/获取查询集
sql = 'update person set name="小华" where id = 2;'
# 执行sql语句
row_num = cursor.execute(sql)
if row_num > 0:
print('数据记录修改成功....')
# 只要是事务型操作(增删改),就必须提交后才能生效(sql数据的执行都是在内存中的,只有commit之后才能落盘)
conn.commit()
# 5. 关闭游标
cursor.close()
# 6. 关闭连接
conn.close()
数据记录的查询操作
"""
操作步骤:
1. 导入模块
2. 创建连接
3. 创建游标
4. 执行sql/获取查询集
查询操作不需要数据提交,但是要进行结果集的处理
5. 关闭游标
6. 关闭连接
"""
# 1. 导入模块
import pymysql
# 2. 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
charset='utf8',
database='python'
)
# 3. 创建游标
cursor = conn.cursor()
# 4. 执行sql
sql = 'select * from person where id > 1;'
cursor.execute(sql)
# 获取查询集
# query_set = cursor.fetchall() # 获取全部的数据
# query_set = cursor.fetchone() # 获取一条数据
query_set = cursor.fetchmany(3) # 获取3条数据
print(query_set)
# 5. 关闭游标
cursor.close()
# 6. 关闭连接
conn.close()
扩展: 大数据主要解决了企业穷的问题???
如果企业没钱还想计算和存储海量数据,就要使用大数据解决方案,如果有钱直接上超级计算机
大数据解决方案一般是性能,工期和成本之间的博弈,但是一般妥协的都是前两者.
6.6 递归[了解]
递归时数学中的说法,其实就是一个函数内部调用了函数本身,那么这个就是递归
合理递归的要求:
- 在函数体内部调用函数本身
- 要有明确的递归出口(递归跳出条件)
- 不能超出最大调用深度(python默认函数最多嵌套1000层)
# 递归是一种算法
# 什么是算法??? 将数学知识以代码的形式呈现出来的一种{思想}
# # 递归就是在函数体内部调用函数本身的一种代码书写形式
# def func1():
# func1()
# print('123')
#
# # RecursionError: maximum recursion depth exceeded
# func1()
# 上述内容就是递归,但是不是一个合理的递归,合理的递归有如下三个条件
"""
1. 在函数体内部调用函数本身
2. 要有明确的递归出口(递归跳出条件)
3. 不能超出最大调用深度(python默认函数最多嵌套1000层)
"""
# 举例: 计算1-n的累加和
"""
递归第一步要找规律
f(1) = 1 = 1
f(2) = 1 + 2 = f(1) + 2
f(3) = 1 + 2 + 3 = f(2) + 3
f(4) = 1 + 2 + 3 + 4 = f(3) + 4
....
f(n) = 1 + 2 + 3 ... + n = f(n-1) + n
结论:
计算1-n的累加和的规律就是 f(n-1) + n
"""
# 递归第二步.将数学规律转换为代码逻辑
# def sum_1_to_n(n):
# return sum_1_to_n(n-1) + n
#
# sum_1_to_n(100)
# 递归第三步. 找到递归地跳出条件
# 根据上边的规律,f(1)函数中,没有调用函数本身,则此时就是f函数的递归出口
def sum_1_to_n(n):
if n == 1:
return 1
return sum_1_to_n(n - 1) + n
print(sum_1_to_n(100)) # 5050
# RecursionError: maximum recursion depth exceeded in comparison
print(sum_1_to_n(1000)) # 超出最大的调用深度,所以报错
递归的缺点:
- 递归的性能消耗极大
- 如果递归的调用深度或者递归的出口没有限制完全,在使用时极易造成程序崩溃
- 递归的使用场景不容易辨别
递归的优点:
- 可以将一个复杂的问题简单化,拆分为多个嵌套的简单问题
- 使用递归可以减少代码量
递归逻辑图解