* PyMySQL-SQL注入攻击问题
* PyMysql-操作事务
* 递归相关概述
* 递归相关案例
* 入门案例
* 求阶乘
* 斐波那契数列(不死神兔)
* 爬楼梯
* 打印文件夹(及其子级)
* 删除文件夹
* 拷贝文件夹
---
1.PyMySQL-演示SQL注入攻击
* SQL注入攻击介绍
* 概述
> 实际开发中, 如果我们的SQL语句中 部分内容是由用户键盘录入的, 此时如果用户录入一些非法值, 就很有可能改变了我们SQL语句的结构, 从而引发一系列的安全问题, 这些安全问题就是: SQL注入攻击.
* 例如: 模拟登陆
```sql
-- 模拟登陆, 语法格式如下
select * from users where username='用户录入的账号' and password='用户录入的密码';
-- 可能发生的情况
-- 场景1: 小白用户. 录入的账号: abc 录入的密码: xyz
select * from users where username='abc' and password='xyz'; -- 合法的SQL
-- 场景2: 懂点技术的用户. 录入的账号: abc 录入的密码: xyz ' or '1=1
select * from users where username='abc' and password='xyz ' or '1=1';
```
* Python代码-演示SQL注入攻击问题
* SQL代码-准备动作.
```sql
-- ------------------------- 我是华丽的分割线 -----------------------
-- 1. 建库, 切库, 查表.
create database if not exists day10;
use day10;
show tables;
-- 2. 建表, 添加表数据, 并查看数据.
create table users(
id int primary key auto_increment, # id, 主键自增
username varchar(20), # 账号(用户名)
password varchar(20) # 密码
);
insert into users values(null, 'admin01', 'pwd111'), (null, 'admin02', 'pwd222');
select * from users;
-- 场景1: 小白用户. 录入的账号: abc 录入的密码: xyz
select * from users where username='abc' and password='xyz'; -- 合法的SQL
-- 场景2: 懂点技术的用户. 录入的账号: abc 录入的密码: xyz ' or '1=1
select * from users where username='abc' and password='xyz ' or '1=1';
```
* Python代码
```python
"""
SQL注入攻击介绍:
概述:
实际开发中, 如果我们的SQL语句中 部分内容是由用户键盘录入的, 此时如果用户录入一些非法值,
就很有可能改变了我们SQL语句的结构, 从而引发一系列的安全问题, 这些安全问题就是: SQL注入攻击.
例如: 模拟登陆
-- 模拟登陆, 语法格式如下
select * from users where username='用户录入的账号' and password='用户录入的密码';
-- 可能发生的情况
-- 场景1: 小白用户. 录入的账号: abc 录入的密码: xyz
select * from users where username='abc' and password='xyz'; -- 合法的SQL
-- 场景2: 懂点技术的用户. 录入的账号: abc 录入的密码: xyz ' or '1=1
select * from users where username='abc' and password='xyz ' or '1=1';
"""
# 导包
import pymysql
# 1. 提示用户录入他/她的账号和密码, 并接收.
uname = input('请录入您的账号: ')
pwd = input('请录入您的密码: ')
# 2. 创建连接对象.
conn = pymysql.connect(host='localhost', port=3306, user='root', password='123456', database='day10', charset='utf8')
# 3. 创建游标对象.
cursor = conn.cursor()
# 4. 拼接 模拟登陆的 SQL语句.
# 写法1: 普通写法, 直接拼接即可.
# sql = "select * from users where username='"+ uname +"' and password='"+ pwd +"';"
# 写法2: 占位符思想
# sql = "select * from users where username='%s' and password='%s';" % (uname, pwd)
# 写法3: 格式化思想, 插值表达式.
sql = f"select * from users where username='{uname}' and password='{pwd}';"
# print(sql)
# 5. 执行(查询)SQL语句, 获取结果集.
cursor.execute(sql)
result = cursor.fetchall()
# 6. 处理(操作)结果集. 即: 结果集有数据就提示登录成功, 无数据则提示登录失败.
# 方式1: 根据 结果集的长度 判断.
# if.else 语句写法
# if len(result) > 0:
# print(f'登陆成功, 欢迎您, {uname}!')
# else:
# print('登陆失败!')
# 三元表达式写法.
# print(f'登陆成功, 欢迎您, {uname}!' if len(result) > 0 else '登陆失败!')
# 方式2: 根据 结果集的内容 判断.
print(f'登陆成功, 欢迎您, {uname}!' if result else '登陆失败!')
# 7. 释放资源.
cursor.close()
conn.close()
```
2.PyMySQL-解决SQL注入攻击
* 思路
采用**预编译思想(占位符)**来解决, 即: 预先对SQL语句进行编译, 之后无论传入什么, 都只会当做普通字符来处理.
* 示例代码
```python
"""
PyMysql解决SQL注入攻击的思想:
采用预编译思想(占位符)来解决, 即: 预先对SQL语句进行编译, 之后无论传入什么, 都只会当做普通字符来处理.
"""
# 导包
import pymysql
# 1. 提示用户录入他/她的账号和密码, 并接收.
uname = input('请录入您的账号: ')
pwd = input('请录入您的密码: ')
# 2. 创建连接对象.
conn = pymysql.connect(host='localhost', port=3306, user='root', password='123456', database='day10', charset='utf8')
# 3. 创建游标对象.
cursor = conn.cursor()
# 4. 拼接 模拟登陆的 SQL语句.
# 预编译思想 结合占位符, 即: 这一步已经把SQL语句的格式给确定了(预编译过了), 之后无论传入什么, 都只会当做普通字符来处理.
# sql = "select * from users where username='%s' and password='%s';" % (uname, pwd) # 这种是 普通的 占位符思想, 没有预编译, 不会解决SQL注入攻击问题.
# 预编译的时候, 用 %s 来占位, 在执行SQL语句的时候, 可以给这些占位符传值..
sql = "select * from users where username=%s and password=%s;" # 这种是 预编译思想 + 占位符, 能解决SQL注入攻击问题.
# print(sql)
# 5. 执行(查询)SQL语句, 获取结果集.
cursor.execute(sql, (uname, pwd)) # 在执行SQL语句的时候, 用容器类型(列表, 元组等)给 预编译时的占位符传值.
result = cursor.fetchall()
# 6. 处理(操作)结果集. 即: 结果集有数据就提示登录成功, 无数据则提示登录失败.
# 方式1: 根据 结果集的长度 判断.
# print(f'登陆成功, 欢迎您, {uname}!' if len(result) > 0 else '登陆失败!')
# 方式2: 根据 结果集的内容 判断.
print(f'登陆成功, 欢迎您, {uname}!' if result else '登陆失败!')
# 7. 释放资源.
cursor.close()
conn.close()
```
3.PyMysql-操作事务
* 事务介绍
* 概述
> 事务**(transaction)**指的是逻辑上的一组操作, 组成该操作的各个逻辑单元, 要么全部执行成功, 要么全部执行失败.
>
> 大白话记忆: 同生同死.
* 例如: 转账, 乔峰 => 虚竹 转 1000 元
* 动作1: 乔峰账号 - 1000元
* 动作2: 虚竹账号 + 1000元
> 上述的两个动作 = **2个逻辑单元**, 合起来, 就是完整的 转账动作.
* 特点: (ACID)
* 原子性
> 指的是: 组成事务的各个逻辑单元已经是最小单位, 不可分割.
* 一致性
> 指的是: 事务执行前后, 数据应该保持一致.
* 隔离性(Isolation)
> 指的是: 事务之间是相互隔离的, 一个事务执行期间不应该受到其它事务的干扰.
>
> 否则容易出现: 脏读, 不可重复读, 虚读(幻读)等情况...
>
> ```sql
> -- 其实考虑隔离性, 就是设置不同的隔离级别, 具体如下:
> -- 隔离级别, 安全性, 从低到高分别是:
> read uncommitted < read committed < repeatable-read < serializable
>
> -- 效率, 从高到低分别是:
> read uncommitted > read committed > repeatable-read > serializable
>
> -- MySQL数据库的默认隔离级别是: repeatable-read
> -- Oracle数据库的默认隔离级别是: read committed
>
> -- 四个隔离级别解释
> read uncommitted:
> 会发生: 脏读, 不可重复读, 虚读
> 已解决: 0
>
> read committed:
> 会发生: 不可重复读, 虚读
> 已解决: 脏读
>
> repeatable-read:
> 会发生: 虚读
> 已解决: 脏读, 不可重复读
>
> serializable:
> 会发生: 0
> 已解决: 脏读, 不可重复读, 虚读
> ```
* 持久性
> 指的是: 无论事务执行成功与否, 结果都会永久的写到数据表中.
* 涉及到的SQL语句
```sql
-- MySQL默认开启了事务的自动提交功能, 每个SQL语句都是一个单独的事务, 执行完都会自动提交.
-- 查看事务的自动提交功能.
show variables like '%commit%';
-- 查看事务的隔离级别
select @@transaction_isolation; -- 这个是: MySQL8.X的写法.
select @@tx_isolation; -- 这个是: MySQL5.X的写法.
-- 提交事务
commit -- 相当于: 把事务的执行结果给永久保存了.
-- 事务回滚
rollback -- 相当于: 撤销本次执行, 即: 把数据还原到事务没有开始执行前的状态.
-- 开启事务
begin 或者写 start transaction
-- 临时的设置事务的隔离级别为指定级别, 会话结束, 本次设置失效.
set session transaction isolation level 隔离级别;
```
* 扩展-演示脏读
```sql
-- 脏读: 也叫 读-未提交, 指的是1个事务读取到了另1个事务还没有提交的数据, 导致查询结果不一致.
具体动作:
1. 开启两个DOS窗口, 称之为: A, B窗口.
2. 设置A窗口的隔离级别为: read uncommitted(可能发生: 脏读, 不可重读, 虚读)
B窗口的隔离级别不变: 默认还是 repeatable-read
3. 在A, B窗口中分别开启事务.
4. 在A窗口中先查询一下 账务表(account)的数据.
5. 在B窗口中完成 乔峰 => 虚竹 转账1000的动作, 注意: 不要提交.
6. 在A窗口中再次查询一下 账务表(account)的数据, 发现和刚才查询的结果: 不一样.
7. 此时出现了: A窗口 读取到了 B窗口 未提交的数据, 这就是 => 脏读.
-- 不可重复读: 也叫 读-已提交(修改)
-- 虚读: 也叫 读-已提交(插入)
-- 串行化读法 => Serializable
```
* Python代码-模拟转账-**无事务**
```python
"""
需求: PyMysql演示 转账, 无事务版, 看看会发生什么问题.
遇到的问题:
账号A 扣钱 成功, 但是 账号B 收钱 失败.
产生原因:
没有把 账号A扣钱 和 账号B收钱 这两个操作放在一起, 所以它们是两个独立的动作, 各不相关.
解决方案:
把 账号A扣钱 和 账号B收钱 这两个动作绑定到一起, 要么全部成功, 要么全部失败.
这就可以通过 事务 来实现.
"""
# 导包
import pymysql
# 1. 获取连接对象.
conn = pymysql.connect(host='localhost', port=3306, user='root', password='123456', database='day10', charset='utf8')
# 2. 获取游标对象.
cursor = conn.cursor()
# 3. 执行SQL语句, 获取结果集.
# 3.1 账户A 扣钱
sql1 = "update account set money = money - 1000 where id = 1;"
row1 = cursor.execute(sql1) # 返回的是: 受影响的行数.
conn.commit() # 提交事务.
# 3.2 模拟程序出Bug.
print(10 / 0)
# 3.3 账户B 收钱
sql2 = "update account set money = money + 1000 where id = 2;"
row2 = cursor.execute(sql2) # 返回的是: 受影响的行数.
conn.commit() # 提交事务.
# 4. 操作结果集, 看: 转账是否成功, 并提示.
print('转账成功!' if row1 == 1 and row2 == 1 else '转账失败!')
# 5. 释放资源.
cursor.close()
conn.close()
```
* Python代码-模拟转账-**加入事务**
```python
"""
需求: PyMysql演示 转账, 无事务版, 看看会发生什么问题.
遇到的问题:
账号A 扣钱 成功, 但是 账号B 收钱 失败.
产生原因:
没有把 账号A扣钱 和 账号B收钱 这两个操作放在一起, 所以它们是两个独立的动作, 各不相关.
解决方案:
把 账号A扣钱 和 账号B收钱 这两个动作绑定到一起, 要么全部成功, 要么全部失败.
这就可以通过 事务 来实现.
"""
# 导包
import pymysql
# 1. 获取连接对象.
conn = pymysql.connect(host='localhost', port=3306, user='root', password='123456', database='day10', charset='utf8')
# 2. 获取游标对象.
cursor = conn.cursor()
try:
# 3. 执行SQL语句, 获取结果集.
# 3.0 开启事务, 即: 在提交或者回滚前, 所有的动作都是1个整体.
conn.begin() # 开启事务, 其实你不写, 也不影响, 但是按照规范写法, 要写上.
# 3.1 账户A 扣钱
sql1 = "update account set money = money - 1000 where id = 1;"
row1 = cursor.execute(sql1) # 返回的是: 受影响的行数.
# 3.2 模拟程序出Bug.
# print(10 / 0)
# 3.3 账户B 收钱
sql2 = "update account set money = money + 1000 where id = 2;"
row2 = cursor.execute(sql2) # 返回的是: 受影响的行数.
except:
# 3.4 走这里, 说明出现问题, 即: 先事务回滚, 然后提示 转账失败.
conn.rollback()
print('转账失败!')
else:
# 3.5 走这里, 说明无问题, 转账成功.
conn.commit() # 提交事务
print('转账成功!')
finally:
# 4. 释放资源.
cursor.close()
conn.close()
```
4.递归-入门
* 概述
> 在Python中, 我们把 函数自己调用自己的情况, 称之为: 递归.
* 注意事项:
1. 递归必须要有出口, 否则容易造成: 死递归.
2. 递归调用次数不能过多, 否则容易造成: 死递归.
3. 搞定递归很简单, 只要分析清楚 **出口** 和 **规律**即可.
* 示例代码
```python
"""
递归简介:
概述:
在Python中, 我们把 函数自己调用自己的情况, 称之为: 递归.
注意事项:
1. 递归必须要有出口, 否则容易造成: 死递归.
2. 递归调用次数不能过多, 否则容易造成: 死递归.
3. 搞定递归很简单, 只要分析清楚 出口 和 规律即可.
"""
# 案例: 递归代码演示
# 1. 定义fun01()函数, 实现自己调用自己.
# 计数器
count = 0
def fun01():
# 设置修改全局变量的值.
global count
count += 1
# 正常打印
print(f'我是 fun01 函数, {count}')
# 出口
if count >= 100:
return
# 函数自己调用自己 => 递归.
fun01()
# 2. 在main方法中测试.
if __name__ == '__main__':
# 调用 fun01()函数
fun01()
```
5.递归-求阶乘
* 代码演示
```python
"""
需求: 计算5的阶乘.
阶乘公式:
5! = 5 * 4 * 3 * 2 * 1 = 5 * 4! = 120
即:
n! = n * (n - 1)!
求5的阶乘, 思路分析:
5! = 5 * 4 * 3 * 2 * 1 -------
5 * 4! |
4 * 3! 规律
3 * 2! |
2 * 1! -------
1! = 1 出口
"""
# 1. 定义函数 factorial(n), 用来计算 n的阶乘.
def factorial(n):
# 出口, 1! = 1
if n == 1:
return 1
# 规律
return n * factorial(n - 1)
# 2. 在main方法中测试.
if __name__ == '__main__':
result = factorial(5)
print(f'result: {result}') # 120
```
* 图解
6.递归-不死神兔
* 代码演示
```python
"""
案例: 不死神兔, 斐波那契数列.
介绍:
传说在很久很久以前, 有一个意大利青年, 叫: 斐波那契, 有一天, 他提出来一个非常有意思的问题:
1. 假设1对小兔子, 一个月之后会长成1对大兔子.
2. 每对大兔子, 每个月都会生1对小兔子.
3. 问: 假设所有的兔子都不死的情况下, 1对小兔子, 1年(即: 12个月)之后会变成多少对兔子?
分析流程:
月份 大兔子对数 小兔子对数 当月兔子总对数
1月 0 1 1
2月 1 0 1
3月 1 1 2
4月 2 1 3
5月 3 2 5
6月 5 3 8
......
12月 * * 144
大白话解释斐波那契数列:
已知数列: 1, 1, 2, 3, 5, 8, 13, 21... 问: 第12个整数是多少?
结论:
出口: 前两个月(第1月, 第2月)的兔子对数 = 1, 即: m = 1 or m = 2时, 兔子对数 = 1
规律: 从第3个月开始, 每月兔子对数 = 上月兔子对数 + 上上月兔子对数, 即: m月兔子对数 = (m-1)月兔子对数 + (m-2)月兔子对数
"""
# 1. 定义函数, 计算 month 月的兔子对数.
def get_rabbit(m):
# 出口, 前两个月的兔子对数 = 1
# if m == 1 or m == 2:
# if m <= 2:
if m in [1, 2]:
return 1 # 兔子对数
# 规律: m月兔子对数 = (m-1)月兔子对数 + (m-2)月兔子对数
return get_rabbit(m - 1) + get_rabbit(m - 2)
# 2. 在main函数中调用
if __name__ == '__main__':
# 思路1: 递归版.
num = get_rabbit(12)
print(f'第12个月的兔子对数为: {num} 对!')
print('-' * 30)
# 思路2: 用 列表 实现, 已知列表 rabbit_list = [1, 1], 后续的每个值 = 前两个值之和, 问: 列表的第12个值是多少?
# 1. 定义列表, 记录 每月的兔子对数.
rabbit_list = []
# 2. 因为前两个月的兔子对数都是1, 我们手动添加即可.
rabbit_list.append(1)
rabbit_list.append(1)
# 3. 从第3个值(即: 第3月开始)有规律了, 当月 = 前两个值之和.
for i in range(2, 12): # i(索引)的值: 2 ~ 12, 包左不包右, 即: 2 ~ 11 分别表示 3月 ~ 12月
# 4. 规律: 当前值 = 前两个值之和.
rabbit_list.append(rabbit_list[i - 1] + rabbit_list[i - 2])
# 5. 打印结果.
print(rabbit_list)
print(f'第12个月的兔子对数为: {rabbit_list[11]} 对!')
print(f'第12个月的兔子对数为: {rabbit_list[len(rabbit_list) - 1]} 对!')
```
7.递归-爬楼梯
* 代码演示
```python
"""
需求:
假设需要爬n阶台阶才能到达顶层, 每次可以选择爬1阶或者2阶. 问: 共有几种方式可以到达顶层.
例如:
到达顶层的楼梯总台阶数 你可以选择的具体的爬楼梯的方案 方案总数
1 1 1
2 11,2 2
3 21,12,111 3
4 1111,112,121,211,22 5
5 11111,1112,1121,1211,2111,122,212,221 8
......
结论:
出口: n=1 => 1 n=2 => 2
规律: n >= 3, n = (n - 1)的方案总数 + (n - 2)的方案总数
"""
# 1. 定义函数, 递归实现 计算爬楼梯总方案数.'
def climb_stairs(n):
# 出口
if n == 1:
return 1 # 1个台阶, (爬楼梯)总方案数 = 1
elif n == 2:
return 2 # 2个台阶, (爬楼梯)总方案数 = 2
# 规律
return climb_stairs(n - 1) + climb_stairs(n - 2)
# 2. 在main函数中测试.
if __name__ == '__main__':
# 思路1: 递归版.
count = climb_stairs(6)
print(f'爬楼梯总方案数为: {count}')
# 思路2: for循环 + 列表版 实现.
```
8.递归-打印文件夹
* 需求介绍
> 键盘录入1个文件夹路径, 然后打印其所有的子级路径(包括子级的子级)
* 代码实现
```python
"""
需求: 键盘录入1个文件夹路径, 打印该文件夹下所有文件(包括子文件夹)
出口: 如果是文件, 就打印, 不递归.
规律: 如果是文件夹, 就递归.
"""
import os # Operating System, 系统模块, 里边定义的主要是和 文件, 文件夹(路径)相关的操作.
# 1. 定义函数 print_dir(path), 打印 path 文件夹路径下所有的子级.
def print_dir(path):
"""
自定义函数, 打印path路径下 所有的子级, 包括子级的子级
:param path: 要被打印的 文件夹路径
:return: 无
"""
# 1.1 判断path是否是 存在的 文件夹路径.
if os.path.isdir(path): # 假设: path = 'd:/src'
# 1.2 走到这里, 说明是 存在的 文件夹路径, 就: 获取其所有的子级.
child_names = os.listdir(path) # ['aa', 'ai30', '代码打字练习词库.txt', '面试题.py']
# 1.3 把上述的 子级文件(夹)的名字, 拼接成: 完整的 子级路径.
for child_name in child_names:
# 1.4 具体的拼接子级路径的动作, 即: 子级路径 = path + '/' + child_name
child_path = path + '/' + child_name
# 1.5 打印 子级路径
print(child_path)
# 1.6 判断当前的子级路径是否是 文件夹路径, 如果是文件夹, 就: 递归.
if os.path.isdir(child_path):
# 递归
print_dir(child_path)
else:
# 1.3 走到这里, 说明路径非法.
print('路径有误, 不是文件夹路径 或者 路径不存在, 程序结束!')
# 2. 在main方法中, 测试.
if __name__ == '__main__':
print_dir('d:/src')
print('-' * 30)
# 3. 回顾 os 模块的函数.
# 创建目录
# os.mkdir('d:/aaa')
# 改名
# os.rename('d:/aaa', 'd:/xyz')
# 删除目录, 必须是: 空目录
# os.rmdir('d:/dest')
# 查看目录下, 所有的子级(不包括子级的子级), 只能获取: 子级的名字, 而不是子级的目录.
# list_dir = os.listdir('d:/src')
# print(type(list_dir)) # <class 'list'>
# print(list_dir) # ['aa', 'ai30', '代码打字练习词库.txt', '面试题.py']
# os.path.isdir(路径): 判断是否是 存在的 文件夹.
print(os.path.isdir('d:/src/aa')) # 存在, 且是文件夹. True
print(os.path.isdir('d:/src/bb')) # 不存在. False
print(os.path.isdir('d:/src/面试题.py')) # 存在的, 但是是文件. False
print('-' * 30)
# os.path.isfile(路径): 判断是否是 存在的 文件
print(os.path.isfile('d:/src/面试题.py')) # True, 存在, 且是文件.
print(os.path.isfile('d:/src/abc.py')) # False, 不存在, 是文件.
print(os.path.isfile('d:/src/aa')) # False, 存在, 是文件夹.
print('-' * 30)
# os.path.exists(路径): 判断是否是 存在的文件 或者文件夹.
print(os.path.exists('d:/src/面试题.py')) # True, 存在的 文件
print(os.path.exists('d:/src/aa')) # True, 存在的 文件夹.
print(os.path.exists('d:/src/bb')) # False, 不存在的.
print('-' * 30)
# os.path.basename(路径):
print(os.path.basename('d:/src/面试题.py')) # 面试题.py
print(os.path.basename('d:/src/1.py')) # 1.py
print(os.path.basename('d:/src/aa')) # aa
print(os.path.basename('d:/src/bb')) # bb
print(os.path.basename('d:/ai30/base_class/hg/xyz')) # xyz
```