week3 day4函数收尾
一. 三元表达式
三元表达式是python为我们提供的一种简化代码的解决方案。对于我们平时所写的行数较少的代码,可以把他改写成三元表达式的形式。
x=11
y=12
if x>y:
print(x)
eles:
print(y)
可以用一行解决。
res=x if x>y else y
print(res)
即条件一元,条件成立的返回值一元,条件不成立的返回值一元。语法如下:
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
二. 生成式
2.1 列表生成式
案例一:
需求:生成一个有10个值的列表。
原写法:
list1=[]
for i in range(10):
list1.append(i)
print(list1)----->[0,1,2,3,4,5,6,7,8,9]
列表生成式写法:
list1=[i for i in range(10)]
print(list1)----->[0,1,2,3,4,5,6,7,8,9]
案例二:
需求:只把需求一里面大于5的值添加到列表里面。
原写法:
list1=[]
for i in range(10):
if i >5:
list1.append(i)
print(list1)----->[6,7,8,9]
列表生成式写法:
list1=[i for i in range(10) if i > 5]----->[6,7,8,9]
案例三:
需求:完成单个元素的修改。
names=['lxx','hxx','wxx','lll']
names1=[i+'_sb' for i in names]
print(names1)----->['lxx_sb','hxx_sb','wxx_sb','lll_sb']
案例四:
需求:把没有sb的元素拿出来。
names=['lxx_sb','hxx_sb','wxx_sb','lll_sb']
names1=[i for i in names if '_sb' not in i]
print(names1)----->['lxx','hxx','wxx','lll']
2.2 字典生成式
dict={i:None for i in range(5)}
print(dict)----->{0:None,1:None,2:None,3:None,4:None}
items=[('k1',111),('k2',222),('k3',333)]
dict={k:v for k,v in items}
print(dict)----->{'k1':111,'k2':222,'k3':333}
2.3 生成器表达式
res=(i for i in range(5))
print(res) # --->generator object得到了一只老母鸡,拍(next)老母鸡才能下蛋
print(next(res))----->0
print(next(res))----->1
print(next(res))----->2
print(next(res))----->3
print(next(res))----->4
如果想计算文件里面的字数:
with open(文件路径,mode='rt',encoding='utf-8') as f:
res1=sum([len(line) for line in f]) # 把每行的字数添加到一个列表中,然后对列表元素求和
res2=sum((len(line) for line in f)) # 拿到一只老母鸡,sum执行了不断拍老母鸡的操作
res2=sum(len(line) for line in f) # 两个res2功能相同,第二个是pycharm的优化,只写一个括号就可以了
三. 函数递归
函数不仅可以嵌套定义,还可以嵌套调用,即在调用一个函数的过程中,函数内部又调用了另一个函数,而函数的递归调用指的是调用一个函数的过程中又直接或间接地调用了该函数本身。
def f1():
print('from f1')
f1()
f1()
在调用f1的过程中,又调用f2,而在调用f2的过程中又调用f1,这就是间接调用函数f1本身。
def f1():
print('from f1')
f2()
def f2():
print('from f2')
f1()
f1()
从上图可以看出,两种情况下的递归调用都是一个无限循环的过程,但在python对函数的递归调用的深度做了限制,因而并不会像大家所想的那样进入循环,会抛出异常,要避免出现这种情况,就必须让递归调用在满足某个特定条件下终止。
大前提:递归调用一定要在某一层结束。
递归的两个阶段:回溯(过去)和递推(回来)。
age(5)=age(4)+10
age(4)=age(3)+10
age(3)=age(2)+10
age(2)=age(1)+10
age(1)=5
def age(n):
if n==1:
return 10
n-=1
return age(n)+10
age(5)
案例:套娃取值
nums=[1,[2,[3,[4,[5,[6]]]]]]
def find_item(container):
for i in container:
if type(i) is list:
return find_item(i)
else:
print(i)
find_nums(nums)
二分法:
nums=[-3,1,3,5,7,9,11,13,15,17,19,21]
def find_num(list1,setnum):
if len(list1)==0:
print('您要找的数不在列表中')
mid_index=len(list1)//2
if list1[mid_index]>setnum:
print('猜大了')
list1=[0:mid_index]
return find_num(list1,setnum)
elif list1[mid_index]<setnum:
print('猜小了')
list1=[mid_index+1:]
return find_num(list1,setnum)
else:
print('正确!')
四. 编程范式
很多初学者在了解了一门编程语言的基本语法和使用之后,面对一个“开发需求”时仍然会觉得无从下手、没有思路/套路,本节主题“编程范式”正是为了解决该问题,那到底什么是编程范式呢?
编程范式指的就是编程的套路,打个比方,如果把编程的过程比喻为练习武功,那编程范式指的就是武林中的各种流派,而在编程的世界里常见的流派有:面向过程(流水线)、函数式、面向对象(类)等,本节我们主要介绍前两者。
在正式介绍前,我们需要强调:“功夫的流派没有高低之分,只有习武的人才有高低之分“,在编程世界里更是这样,各种编程范式在不同的场景下都各有优劣,谁好谁坏不能一概而论,下面就让我们来一一解读它们。
五. 面向过程
“面向过程”核心是“过程”二字,“过程”指的是解决问题的步骤,即先干什么后干什么。基于面向过程开发程序就好比在设计一条流水线,是一种机械式的思维方式,这正好契合计算的运行原理:任何程序的执行最终都需要转换成CPU的指令流水按过程调度执行,即无论采用什么语言、无论依据何种编程范式设计出的程序,最终的执行都是过程式的。
详细的,若程序一开始是要着手解决一个大的问题,按照过程式的思路就是把这个大的问题分解成很多个小问题或子过程去实现,然后依次调用即可,这极大地降低了小恒虚的复杂度。举例如下:
写一个数据远程备份程序,分三步:本地数据打包,上传至云服务器,检测备份文件的可用性。
import os,time
# 一:基于本章所学,我们可以用函数去实现这一个个的步骤
# 1、本地数据打包
def data_backup(folder):
print("找到备份目录: %s" %folder)
print('正在备份...')
zip_file='/tmp/backup_%s.zip' %time.strftime('%Y%m%d')
print('备份成功,备份文件为: %s' %zip_file)
return zip_file
#2、上传至云服务器
def cloud_upload(file):
print("\nconnecting cloud storage center...")
print("cloud storage connected")
print("upload [%s] to cloud..." %file)
link='https://www.xxx.com/bak/%s' %os.path.basename(file)
print('close connection')
return link
#3、检测备份文件可用性
def data_backup_check(link):
print("\n下载文件: %s , 验证文件是否无损..." %link)
#二:依次调用
# 步骤一:本地数据打包
zip_file = data_backup(r"/Users/egon/欧美100G高清无码")
# 步骤二:上传至云服务器
link=cloud_upload(zip_file)
# 步骤三:检测备份文件的可用性
data_backup_check(link)
优点:复杂的问题流程化,进而简单化。
缺点:程序的可扩展性差,因为一条流水线就是用来解决一个问题的,就好比生产汽车的流水线无法生产汽水。
应用场景:
面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景,如果你只是写一些简单的脚本,做一些一次性的任务,用面向过程去实现是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护,那还是用面向对象最为方便。
六. 函数式
函数式编程并非用函数编程那么简单,而是将计算机的运算视为数学意义上的运算,比起面向过程,函数式更加注重的是执行结果而非执行的过程,代表语言有:Haskell、Erlang。而python并不是一门函数式的编程语言,但是仍为我们提供了很多函数式编程好的特性,如lambda
、map
、filter
、reduce
。
6.1 匿名函数与lambda
我们通过def定义的函数都是有名函数,而无名函数是不用def所定义的,即没有名字的函数。隐藏了return。特点:临时用一次。
lambda 参数1,参数2...:expression
# 1、定义
lambda x,y,z:x+y+z
#等同于
def func(x,y,z):
return x+y+z
# 2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)
# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)
匿名函数与有名函数具有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用,我们以下述字典为例来介绍它:
案例:找到薪水最高的人的名字。
salaries={
'egon':3000,
'tom':100000,
'zzz':1000
}
内置max和min都支持迭代器协议,工作原理都是迭代字典,取得的是字典的键,因而比较的是键的最大和最小值,而我们想要的是比较值(工资)的最大值与最小值。
print(max([11,22,33]))----->33
print(max(salaries))----->'zzz'
但实际上max有两只手,第一只手抓产生的结果,第二只手抓排序的依据,当我们不设定key时,默认左右手抓的东西一样。
原本解决问题的方法:
def func(k):
return salaries[k]
print(max(salaries,key=func))
匿名函数解法:
print(max(salaries,key=lambda k:salaries[k]))
同理,最小值和排序也都可以通过匿名函数用一行来完成。
print(min(salaries,key=lambda k:salaries[k]))
print(sorted(salaries,key=lambda k:salaries[k])) # 由小到大
print(sorted(salaries,key=lambda k:salaries[k],reverse=True)) # 由大到小
6.2 map、filter、reduce、zip
函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法:
array=[1,2,3,4,5]
要求一:对array的每个元素做平方处理,可以使用map函数
map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下:
>>> res=map(lambda x:x**2,array)
>>> res
<map object at 0x1033f45f8>
解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。
>>> list(res) #使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]
要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数
reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值
reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce
>>> res=reduce(lambda x,y:x+y,array)
>>> res
15
解析:
1 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3
2 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15
也可以为reduce指定初始值
>>> res=reduce(lambda x,y:x+y,array,100)
>>> res
115
要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素
>>> res=filter(lambda x:x>3,array)
解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
>>> list(res)
[4, 5]
要求四:对array进行组合操作,这就用到了zip函数,比如将两个子元素个数相同的列表拼起来
l1=[111,222,333]
l2=['嘿嘿','哈哈','嘻嘻']
>>> res=zip(l1,l2) # 得到的仍然是一个迭代器
>print(list(res)) # [(111,'嘿嘿'),(222,'哈哈')(333,'嘻嘻')]
zip就像一条拉链一样,可以将多个数据组合在一起,而组合后结果的长度,和子元素最短的长度一样。
l1=[111,222,333]
l2=['嘿嘿','哈哈','谢谢','户嚯']
l3=[12,23,34,45,56]
res=zip(l1,l2,l3)
print(list(res)) # [(111, '嘿嘿', 12), (222, '哈哈', 23), (333, '谢谢', 34)]
提示:我们介绍map、filter、reduce、zip只是为了带大家了解函数式编程的大致思想,在实际开发中,我们完全可以用列表生成式或者生成器表达式来实现三者的功能。