二分法 面向过程 函数式编程 模块
1 二分查找法
算法是高效解决问题的方法。
二分查找法 Binary Search
前提:列表中的元素按照升序排列
具体步骤:
- 如果带查找的列表为空,则说明列表中不存在目标元素;
- 将列表中间位置的元素与目标值进行比较,如果两者相等,则查找成功;
- 否则根据这个中间元素将列表分为前后两个子列表;
- 根据中间位置的元素与目标值的大小关系确定带查找的子列表,如果中间位置的元素大于目标值,则对后面的子列表进行进一步查找,否则对前面的子列表进行进一步查找;
- 重复上面的查找过程,直到找到目标元素,或者待查找的子列表为空,即表示列表中不存在目标元素。
def binary_search(lst, target_ele):
if len(lst) == 0:
print('目标元素不存在。')
return False
mid_index = len(lst) // 2
mid_ele = lst[mid_index]
if target_ele == mid_ele:
print('目标元素存在。')
return True
elif target_ele > mid_ele:
return binary_search(lst[mid_index + 1: ], target_ele)
else:
return binary_search(lst[: mid_index], target_ele)
lst = [0, 1, 2, 3, 4, 5]
print(binary_search(lst, 2)) # 目标元素存在。True
print(binary_search(lst, 20)) # 目标元素不存在。False
2 面向过程的编程思想
2.1 介绍
编程思想,也可以称为编程范式。
面向过程(Procedure Oriented)的编程思想是以过程为核心的编程思想,其中过程指的是做事的步骤流程。
其它编程思想本质上都是基于面向过程的编程思想的,只是在此面向过程的基础上进行了进一步封装。
优点:将复杂的问题流程化处理,变得简单。
缺点:扩展性比较差。面向过程的编程思想中,所有的流程都是联系在一起的,互相影响,修改某个部分很可能会影响整体,因此不便于维护和扩展。
2.2 应用场景解析:
- 不是所有的软件程序都需要频繁更迭;
例如脚本程序 或者 进行数据分析的相关操作 - 即便软件程序需要频繁更迭,但这并不代表代码的所有组成部分都需要一起更迭。
3 函数式编程思想
3.1 介绍
函数式编程思想是将计算机的运算视为数学运算,即将数学上对函数的定义搬到了程序中。整体上,函数式编程与python的设计思想相悖。python提供了一些具有函数式编程思想的语法,它们可以将代码精简又不丧失可读性与间接性。
3.2 lambda 匿名函数
3.2.1 定义匿名函数
lambda [arg1 [,arg2,.....argn]]: expression
表达式只能是单行
对比:
定义有名函数
def func(x, y):
return x + y
print(func) # <function func at 0x地址>
定义匿名函数
lambda x, y: x + y
print(lambda x, y: x + y) # <function <lambda> at 0x地址>
3.2.2 调用匿名函数
方式1:内存地址加括号
res = (lambda x, y: x + y)(1, 2)
print(res) # 3
方式2:通过赋值,使用有名函数的方式调用
func = lambda x, y: x + y
print(func(1, 2)) # 3
匿名函数的核心是不需要函数名,因此上面的独立地调用匿名函数的方式不推荐使用。
方式3:
匿名函数一般用于需要临时调用一次的场景。
之后由于没有函数名,即执行后无法再次通过函数名来访问函数体代码所在的内存空间,因此会被视为垃圾而回收掉。
更多的使用场景是将匿名函数与其它函数进行配合使用,即调用某个函数使临时为这个函数服务一次。
3.2.3 常用使用场景
max(iterable, key=None)
取出传入的可迭代对象中的最大值,可选参数key为比较的基准。
max函数的工作原理是将可迭代对象转换为迭代器对象进行迭代,返回的是迭代器产生的元素中的一个。
如果指定key,max函数会将每一个迭代器产生的元素当作参数传入key指定的函数中,将函数的返回值作为比较依据,根据返回值的大小取出最大的返回值对应的元素。
lst = [1, -2]
print(max(lst)) # 1
def func(x):
return x ** 2
print(max(lst, key=func)) # -2
print(max(lst, key=lambda x: x ** 2)) # -2
dic = {1: 30, 2: 20, 3: 10}
# 默认比较的是字典的键
print(max(dic)) # 3
# 指定了key,此时比较的是字典的值
print(max(dic, key=lambda each_key: dic[each_key])) # 1
min函数使用方法相同。
sorted(iterable, key=None, reverse=False)
排序规则,reverse = True 降序,reverse = False 升序(默认)
dic = {1: 30, 2: 20, 3: 10}
res1 = sorted(dic)
print(res1) # [1, 2, 3]
res2 = sorted(dic, key=lambda each_key: dic[each_key])
print(res2) # [3, 2, 1]
filter(function, iterable)
filter() 函数用于过滤掉不符合条件的元素,Python 3 中返回迭代器对象。
第一个参数 function 函数会接受迭代器产生的每一个元素,经过判断后根据返回的真/假结果来判断这个元素是否保留/去除。
dic = {1: 30, 2: 20, 3: 10}
res = filter(lambda each_key: dic[each_key] > 10, dic)
print(res) # <filter object at 0x地址>
print(list(res)) # [1, 2]
reduce(function, sequence [, initial] )
python 3 中reduce函数已不是内置函数。
reduce函数会依次从sequence中取一个元素,将这个元素和上一次调用function的返回值作为参数传入function函数,并再次调用function函数。
from functools import reduce
res = reduce(lambda x, y: x * y, [1, 2, 3], 10)
print(res) # 60
map(function, iterable, …)
根据提供的函数 function 作为处理规则来对序列做映射操作。
序列的每一个元素作为参数传入 function 函数,返回迭代器,其生成的值是每次调用 function 函数的返回值。
res = map(lambda x: x ** 3, list(range(5)))
print(res) # <map object at 0x地址>
print(list(res)) # [0, 1, 8, 27, 64]
4 模块
4.1 什么是模块
模块是一系列功能的集合体。
分为三类:内置模块,第三方模块,自定义模块
模块分为四种形式:
- 使用python语言编写的后缀名为.py的文件;
一个python文件可以存放很多功能,其本身就是一个模块。
文件名:func.py
模块名:func - 已被编译成共享库或DLL的C或C++扩展;
- 存放一系列模块的文件夹(文件夹下存在名为__init__.py的文件),该文件夹也可以称为包;
- 使用C语言编写的链接到python解释器的内置模块。
4.2 为什么使用模块
- 内置模块与第三方模块可以拿来就用,无需定义,这种拿来主义可以极大地提升开发效率;
- 自定义模块
可以将程序的各部分功能提取出来存放到一个公共的模块中,供大家来共享使用。这样做可以减少代码冗余,使组织结构更加清晰。
4.3 如何使用模块
4.3.1 导入
import func
func 是一个名字,属于本文件的全局名称空间。
首次导入模块会发生:
- 执行 func.py;
- 产生 func.py 的名称空间,将 func.py 执行过程中产生的名字存放到此名称空间中;
- 在当前文件中产生一个名字 func,指向2中产生的名称空间。
之后导入相同的模块都会直接引用首次导入时产生的名称空间,不会重复执行相同的模块。
import 模块名 as 模块别名
如果原模块名比较长,可以为其起一个简短的别名,方便使用。
4.3.2 引用
模块名.名字
- 指名道姓地向模块索要名字对应的值,不会与当前名称空间中的名字产生冲突;
- 无论是查看还是修改,操作的都是模块本身,与调用位置无关。
导入模块规范
- 可以以逗号为分隔符,在同一行中导入多个模块,但不推荐;
- 按顺序分类导入
python内置模块
第三方模块
自定义模块
4.3.3 补充
- 函数是第一类对象,即函数可以当作变量来使用,包括用于赋值,作为函数的参数以及返回值,作为容器类型数据的元素等。
模块也是第一类对象。 - 自定义模块的命名风格:纯小写字母+下划线
- 可以在函数内导入模块,导入的模块名字处于函数的局部名称空间。
5 练习
5.1
文件内容如下
egon male 18 3000
alex male 38 30000
wupeiqi female 28 20000
yuanhao female 28 10000
要求:
- 从文件中取出每一条记录放入列表中,
每个元素形式为{‘name’:‘egon’,‘sex’:‘male’,‘age’:18,‘salary’:3000}; - 取出薪资最高的人的信息;
- 取出最年轻的人的信息。
with open(r'./test.txt', mode='rt', encoding='utf-8') as f:
key_list = ['name', 'sex', 'age', 'salary']
info_dict = [
{
key_list[0]: each_list[0],
key_list[1]: each_list[1],
key_list[2]: each_list[2],
key_list[3]: each_list[3]
} for each_list in (each_line.strip().split() for each_line in f)
]
print(info_dict)
# 取出薪资最高的人的信息
max_salary_info = max(info_dict, key=lambda each_dict: int(each_dict['salary']))
print(max_salary_info) # {'name': 'alex', 'sex': 'male', 'age': '38', 'salary': '30000'}
# 取出最年轻的人的信息
min_age_info = min(info_dict, key=lambda each_dict: int(each_dict['age']))
print(min_age_info) # {'name': 'egon', 'sex': 'male', 'age': '18', 'salary': '3000'}
5.2
将names=[‘egon’,‘alex_sb’,‘wupeiqi’,‘yuanhao’]中的名字全部变大写。
names = ['egon', 'alex_sb', 'wupeiqi', 'yuanhao']
upper_list = [each_name.upper() for each_name in names]
print(upper_list) # ['EGON', 'ALEX_SB', 'WUPEIQI', 'YUANHAO']
5.3
将names=[‘egon’,‘alex_sb’,‘wupeiqi’,‘yuanhao’]中以sb结尾的名字过滤掉,然后保存剩下的名字长度。
from functools import reduce
names = ['egon', 'alex_sb', 'wupeiqi', 'yuanhao']
no_sb_list = filter(lambda each_name: not each_name.endswith('sb'), names) # ['egon', 'wupeiqi', 'yuanhao']
len_list = [len(each_ele) for each_ele in no_sb_list]
print(len_list) # [4, 7, 7]
total_len = reduce(lambda x, y: x + y, len_list)
print(total_len) # 18
5.4
求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
with open(r'./a.txt', mode='rt', encoding='utf-8') as f:
max_len_line = max(len(each_line) for each_line in f)
print(max_len_line)
5.5
求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
with open(r'./a.txt', mode='rt', encoding='utf-8') as f:
sum_len = sum(len(each_line) for each_line in f)
print(sum_len)
sum_len1 = sum(len(each_line) for each_line in f)
print(sum_len1) # 0
第一次读取文件后指针指向了文件末尾,所以再次读取文件时读到的内容为空。
5.6
with open('a.txt') as f:
g = (len(line) for line in f)
print(sum(g)) # 为何报错?
- g是一个生成器,本身不存储值,只有在需要时才会执行并生成值;
- with open语句会在其内部代码块执行完毕后关闭文件;
- 结合以上两点,当调用sum(g)时,生成器会执行并产生值,但此时文件已经关闭,无法读取文件,因此报错。
5.7
文件shopping.txt内容如下
mac,20000,3
lenovo,3000,10
tesla,1000000,10
chicken,200,1
- 求总共花了多少钱?
- 打印出所有商品的信息,格式为[{‘name’:‘xxx’,‘price’:333,‘count’:3},…]
- 求单价大于10000的商品信息,格式同上
with open(r'./shopping.txt', mode='rt', encoding='utf-8') as f:
key_list = ['name', 'price', 'count']
shop_dict = [
{
key_list[0]: each_list[0],
key_list[1]: each_list[1],
key_list[2]: each_list[2]
} for each_list in (each_line.strip().split(',') for each_line in f)
]
print(shop_dict)
# 总共消费
total_consume = sum(int(each_dict['price']) * int(each_dict['count']) for each_dict in shop_dict)
print(total_consume) # 10090200
# 求单价大于10000的商品信息
target_list = [each_dict for each_dict in shop_dict if int(each_dict['price']) > 10000]
print(target_list)
5.8
题目1:
- 应该将程序所有功能都扔到一个模块中,然后通过导入模块的方式引用它们;
答:不应该,应该看情况处理,对于一些文件独有的功能,无需将其共享到模块中。 - 应该只将程序各部分组件共享的那一部分功能扔到一个模块中,然后通过导入模块的方式引用它们。
答:可以
题目2:
- 运行python文件与导入python文件的区别是什么?
运行python文件仅仅生成本文件的名称空间,导入时还会生成导入文件的名称空间。 - 运行的python文件产生的名称空间何时回收,为什么?
该文件运行完毕时回收名称空间。因为文件运行过程中产生的名字存放于全局名称空间中,全局名称空间在文件运行结束时销毁。 - 导入的python文件产生的名称空间何时回收,为什么?
所有导入这个文件的文件全部运行完毕时回收导入文件的名称空间。因为导入时会产生导入文件的名称空间,并在当前文件中产生一个名字去指向导入文件的名称空间,该名字处于源文件的全局名称空间中,在原文件运行结束时随着全局名称空间一起销毁。