多层装饰器
- 语法糖功能:会自动将下面紧挨着的函数名当做参数传递给@符号后面的函数名(加括号调用)
- 多个语法糖装饰一个函数名:从下往上执行最后一个语法糖才会做重命名操作
def outter1(func1): # func1 = wrapper2函数名
print('加载了outter1')
def wrapper1(*args, **kwargs):
print('执行了wrapper1')
res1 = func1(*args, **kwargs)
return res1
return wrapper1
def outter2(func2): # func2 = wrapper3函数名
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs)
return res2
return wrapper2
def outter3(func3): # func3 = 真正的index函数名
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs)
return res3
return wrapper3
# 语法糖功能:会自动将下面紧挨着的函数名当做参数传递给@符号后面的函数名(加括号调用)
@outter1 # index = outter1(wapper2) index是wrapper1
@outter2 # wrapper2 = outter2(wrapper3)
@outter3 # wrapper3 = outter3(真正的index函数名)
def index():
print('from index')
index()
# 加载了outter3
# 加载了outter2
# 加载了outter1
# 执行了wrapper1
# 执行了wrapper2
# 执行了wrapper3
# from index
有参装饰器
- 有参装饰器:编写一个装饰器时,当这个装饰器内部需要外界传入额外的数据来控制代码的分支,就需要给原来的装饰器模板再套用一层,加语法糖时就直接添加额外的数据
- 目的:为了给装饰器代码传递更多额外的数据
def outer(condition,type_user): # 给装饰器传参数(有几个参数就传几个参数)
def login_auth(func_name): # 这里不能再填写其他形参
def inner(*args, **kwargs): # 这里不能再填写非被装饰对象所需的参数
username = input('username>>>:').strip()
password = input('password>>>:').strip()
# 应该根据用户的需求执行不同的代码
if type_user =='jason':print('VIP')
if condition == '列表':
print('使用列表作为数据来源 比对用户数据')
elif condition == '字典':
print('使用字典作为数据来源 比对用户数据')
elif condition == '文件':
print('使用文件作为数据来源 比对用户数据')
else:
print('目前只有上面几种方式')
return inner
return login_auth
@outer('文件','jason') # 函数名加括号 优先级最高
def index():
print('from index')
index()
递归函数
概念
- 概念:在函数内部调用自己的函数被称之为递归
# 递归调用:直接调用
def index():
print('from index')
index()
index()
# 递归调用:间接调用
def index():
print('from index')
func()
def func():
print('from func')
index()
func()
- python中允许函数最大递归调用次数
- 官方给出的限制是1000,代码验证可能会有偏差(996 997 998...都有可能)
count = 0
def index():
print('from index')
global count
count += 1 # count = count + 1
print(count)
index()
index()
import sys
print(sys.getrecursionlimit()) # 1000 获取递归最大次数
sys.setrecursionlimit(2000) # 自定义最大次数
print(sys.getrecursionlimit())
函数递归应用场景
-
递推:层层往下寻找答案(每一次都是基于上一次进行下一次的执行)
-
回溯:遇到终止条件,则从最后往回返一级一级的把值返回来
-
递归特点
- 必须要有一个明确的结束条件
- 每次进入更深一层递归时,问题规模(计算量)相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
l1 = [1, [2, [3, [4, [5, [6, [7, [8, [9, [10,]]]]]]]]]]
# 1.for循环l1里面所有的数据值
# 2.判断当前数据值是否是数字 如果是则打印
# 3.如果不是则继续for循环里面所有数据值
# 4.判断当前数据值是否是数字 如果是则打印
# 5.如果不是则继续for循环里面所有数据值
# 6.判断当前数据值是否是数字 如果是则打印
# ...
def get_num(l1):
for i in l1: # for循环自带结束条件,不用手动加结束条件
if isinstance(i, int): # 判断i绑定的数据值是不是整型
print(i)
else:
get_num(i)
get_num(l1)
递归的优缺点
- 递归的优点:
- 递归函数让代码看着更干净整洁
- 使用递归可以将复杂的任务分解成更简单的子问题
- 和使用嵌套相比,使用递归更容易生成序列
- 递归的缺点:
- 有时递归背后的逻辑很难遵循
- 占用大量内存和时间,递归调用效率低
- 递归函数很难调试
算法之二分法
- 算法:解决问题的方法
- 算法永远都在精进,但是很少有最完美的算法
- 二分法:所有算法里最简单的算法
- 二分法使用前提:数据集必须有顺序(升序/降序)
- 二分法原理:
- 获取数据集中间的元素 比对大小
- 如果中间的元素大于目标数据,那么保留数据集的左边一半
- 如果中间的元素小于目标数据,那么保留数据集的右边一半
- 数据 那么保留数据集的右边一半
- 如果中间的元素大于目标数据,那么保留数据集的左边一半
- 如果中间的元素小于目标数据,那么保留数据集的右边一半
- 获取数据集中间的元素 比对大小
l1 = [11, 23, 32, 45, 65, 78, 90, 123, 432, 467, 567, 687, 765, 876, 999, 1131, 1232]
def get_num(l1, target_num):
# 添加递归函数的结束条件
if len(l1) == 0:
print('不好意思 找不到')
return
# 1.先获取数据集中间那个数
middle_index = len(l1) // 2
middle_value = l1[middle_index]
# 2.判断中间的数据值与目标数据值孰大孰小
if target_num > middle_value:
# 3.说明要查找的数在数据集右半边 如何截取右半边
right_l1 = l1[middle_index + 1:]
# 3.1.获取右半边中间那个数
# 3.2.与目标数据值对比
# 3.3.根据大小切割数据集
# 经过分析得知 应该使用递归函数
print(right_l1)
get_num(right_l1, target_num)
elif target_num < middle_value:
# 4.说明要查找的数在数据集左半边 如何截取左半边
left_l1 = l1[:middle_index]
# 4.1.获取左半边中间那个数
# 4.2.与目标数据值对比
# 4.3.根据大小切割数据集
# 经过分析得知 应该使用递归函数
print(left_l1)
get_num(left_l1, target_num)
else:
print('找到了', target_num)
# get_num(l1, 999)
get_num(l1, 1000)
- 二分法缺陷:
- 数据集必须是有序的
- 查找的数如果在开头或结尾而分发效率更低
作业
# 1.尝试编写有参函数将多种用户验证方式整合到其中
# 直接获取用户数据比对
# 数据来源于列表
# 数据来源于文件
def outer(data):
def login_auth(func_name):
def inner(*args, **kwargs):
if data == '直接获取':
username = input('username>>>:').strip()
password = input('password>>>:').strip()
if username == 'jason' and password == '123':
res = func_name(*args, **kwargs)
print('用户登录成功')
return res
else:
print('用户名或密码错误,验证失败')
elif data == '列表':
print("用户登录成功")
res = func_name(*args, **kwargs)
return res
elif data == '文件':
print('用户登录成功')
res = func_name(*args, **kwargs)
return res
else:
print('没有这个验证方式')
return inner
return login_auth
@outer('直接获取')
def user_data():
pass
@outer('列表')
def user_list():
pass
@outer('文件')
def user_file():
pass
user_data()
user_list()
user_file()
# 2.尝试编写递归函数
# 推导指定某个人的正确年龄
# eg: A B C D E 已知E是18 求A是多少
def age(n):
if n == 5:
return 18
else:
res = age(n+1) + 2
return res
res = age(1)
print(res)
冒泡排序
- 冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
def bubbleSort(arr):
n = len(arr)
# 遍历所有数组元素
for i in range(n):
# Last i elements are already in place
for j in range(0, n-i-1):
if arr[j] > arr[j+1] :
arr[j], arr[j+1] = arr[j+1], arr[j]
arr = [64, 34, 25, 12, 22, 11, 90]
bubbleSort(arr)
print ("排序后的数组:")
for i in range(len(arr)):
print ("%d" %arr[i])
# 结果:
# 排序后的数组:
# 11
# 12
# 22
# 25
# 34
# 64
# 90
插入排序
- 插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
def insertionSort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i-1
while j >=0 and key < arr[j] :
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
arr = [12, 11, 13, 5, 6]
insertionSort(arr)
print ("排序后的数组:")
for i in range(len(arr)):
print ("%d" %arr[i])
# 结果
# 排序后的数组:
# 5
# 6
# 11
# 12
# 13
快速排序
简介
-
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来
-
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
-
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
-
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
- 快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
算法步骤
- 从数列中挑出一个元素,称为 "基准"(pivot)
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序
def quickSort(arr, left=None, right=None):
left = 0 if not isinstance(left,(int, float)) else left
right = len(arr)-1 if not isinstance(right,(int, float)) else right
if left < right:
partitionIndex = partition(arr, left, right)
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
def partition(arr, left, right):
pivot = left
index = pivot+1
i = index
while i <= right:
if arr[i] < arr[pivot]:
swap(arr, i, index)
index+=1
i+=1
swap(arr,pivot,index-1)
return index-1
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
posted on
浙公网安备 33010602011771号