Python编程-函数进阶二

一、生成器补充

1.什么是生成器?
可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象。

2.生成器分类
(1)生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。
(2)生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。

3.为何使用生成器之生成器的优点
Python使用生成器对延迟操作提供了支持。
所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

4.生成器函数

def lay_eggs(num):
    for egg in range(num):
        res='蛋%s' %egg
        yield res
        print('下完一个蛋')

laomuji=lay_eggs(10)
print(laomuji)               #打印生成器在内存中的地址
print(laomuji.__next__())    #执行生成器函数
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
运行结果:
<generator object lay_eggs at 0x0000000000A320F8>
蛋0
下完一个蛋
蛋1
下完一个蛋
蛋2
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
['蛋3', '蛋4', '蛋5', '蛋6', '蛋7', '蛋8', '蛋9']

5.生成器表达式和列表解析

egg_list=['鸡蛋%s' %i for i in range(10)]   #列表解析
laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))
运行结果:
<generator object <genexpr> at 0x0000000000D820A0>
鸡蛋0
鸡蛋1
鸡蛋2
总结:

(1)把列表解析的[]换成()得到的就是生成器表达式
(2)列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
(3)Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。
例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

sum(x ** 2 for x in xrange(4))

6.注意
人口信息.txt文件的内容
{'name':'北京','population':10}
{'name':'南京','population':100000}
{'name':'山东','population':10000}
{'name':'山西','population':19999}

def get_provice_population(filename):
    with open(filename) as f:
        for line in f:
            p=eval(line)              #将字符串转换成字典
            yield p['population']
gen=get_provice_population('人口信息')

all_population=sum(gen)
print(all_population)
for p in gen:
    print(p/all_population)
运行结果:
130009
为什么只有1个运行结果呢?

执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。

示例:

def test():
    for i in range(4):
        yield i

g=test()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
运行结果:
[0, 1, 2, 3]
[]
7.协程函数

(1)协程函数就是使用了yield表达式形式的生成器

def eater(name):
    print("%s eat food" %name)
    while True:
        food = yield
    print("done")

g = eater("gangdan")
print(g)
<generator object eater at 0x00000000011B80A0>

(2)协程函数赋值过程
要先运行next(),让函数初始化并停在yield,相当于初始化函数,然后再send(),send会给yield传一个值

next()和send()都是让函数在上次暂停的位置继续运行,

next是让函数初始化

send在触发下一次代码的执行时,会给yield赋值

def eater(name):
    print('%s start to eat food' %name)
    food_list=[]
    while True:
        food=yield food_list
        print('%s get %s ,to start eat' %(name,food))
        food_list.append(food)


e=eater('钢蛋')  
print(e)
print(next(e))     # 现在是运行函数,让函数初始化
print(e.send('包子'))  #
print(e.send('韭菜馅包子'))
print(e.send('大蒜包子'))
运行结果:
<generator object eater at 0x00000000011C8150>
钢蛋 start to eat food
[]
钢蛋 get 包子 ,to start eat
['包子']
钢蛋 get 韭菜馅包子 ,to start eat
['包子', '韭菜馅包子']
钢蛋 get 大蒜包子 ,to start eat
['包子', '韭菜馅包子', '大蒜包子']
用装饰器修饰协程函数
def deco(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        next(res)
        return res
    return wrapper

@deco    #g=deco(g)
def eater(name):
    print('%s ready to eat' %name)
    food_list=[]
    while True:
        food=yield food_list
        food_list.append(food)
        print('%s start to eat %s' %(name,food))

g=eater('alex')     #wrapper('alex')
print(g)
next(g) #等同于 g.send(None)

g.send('手指头')
g.send('脚指头')
g.send('别人的手指头')
g.send('别人的脚指头')

print(g)
print(g.send('脚趾头1'))
print(g.send('脚趾头2'))
print(g.send('脚趾头3'))
运行结果:
alex ready to eat
<generator object eater at 0x0000000000A12258>
alex start to eat None
alex start to eat 手指头
alex start to eat 脚指头
alex start to eat 别人的手指头
alex start to eat 别人的脚指头
<generator object eater at 0x0000000000A12258>
alex start to eat 脚趾头1
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1']
alex start to eat 脚趾头2
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2']
alex start to eat 脚趾头3
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2', '脚趾头3']
应用场景

grep -rl 'python' /root

import os

def send_none(func):
    '''
    装饰器函数:初始化表达式yield的生成器函数(send一个None)
    '''
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        res.send(None)    # next(res)
        return res
    return wrapper

@send_none
def get_abs_path(open_file):
    '''
    获取目录下所有文件的绝对路径函数,并send给open_file 
    '''
    while True:
        walk_path = yield
        w = os.walk(walk_path)
        for path,_,files in w:
            for file in files:
                file_abs_path = r'%s\%s' % (path, file)
                open_file.send(file_abs_path)    # 将得到的绝对路径通过send方式传给open_file这个生成器函数

@send_none
def open_file(match_line):
    '''
    获得文件句柄函数,并将文件句柄和文件路径以元祖的形式send给match_line
    '''
    while True:
        file_abs_path = yield
        with open(file_abs_path,encoding='utf-8') as f:
            match_line.send((file_abs_path,f))    # send可以将多个参数打包成元祖传递给生成器函数

@send_none
def match_line(word):
    '''
    遍历通过yield获取到的文件里的每一行,找到关键字是否存在于改行,存在关键字的文件,打印输出
    (使用标签位位,避免重复打印)
    '''
    while True:
        file_abs_path,f = yield
        Flag = False    # 定义一个标志位
        for line in f:
            if Flag:    # 如果标志位被更改为True则跳出循环(为了防止一个文件有多行关键字,print时存在重复)
                break
            if word in line :
                Flag = True    # 当匹配到关键字所在的行,将标志位改为Ture
                print(file_abs_path)

walk_path = r'E:\s17\day05\a'
g = get_abs_path(open_file(match_line('python')))    # 获得get_abs_path()生成器对象
g.send(walk_path)
运行结果:
E:\s17\day05\a\a1
E:\s17\day05\a\b\c\c1
E:\s17\day05\a\b\c\d\d1
总结:

面向过程的程序设计:是一种流水线式的编程思路,是机械式
优点:
程序的结构清晰,可以把复杂的问题简单

缺点:
扩展性差

应用场景:
linux内核,git,httpd

二、递归

1.递归调用
在函数调用过程中,直接或间接地调用了函数本身,这就是函数的递归调用。

def f1():
    print('f1')
    f2()

def f2():
    f1()

f1()
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

使用以下方式查询:

import sys
print(sys.getrecursionlimit())
运行结果:
1000

2.尾递归
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化。
3.应用场景:二分法

逻辑是将列表取中间值做比较,然后取左边或右边再比较

l = [1, 2, 10,33,53,71,73,75,77,85,101,201,202,999,11111]

def search(find_num,seq):
    if len(seq) == 0:
        print('not exists')
        return
    mid_index=len(seq)//2
    mid_num=seq[mid_index]
    print(seq,mid_num)
    if find_num > mid_num:
        #in the right
        seq=seq[mid_index+1:]
        search(find_num,seq)
    elif find_num < mid_num:
        #in the left
        seq=seq[:mid_index]
        search(find_num,seq)
    else:
        print('find it')

search(77,l)
search(72,l)
search(-100000,l)
运行结果:
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[77, 85, 101, 201, 202, 999, 11111] 201
[77, 85, 101] 85
[77] 77
find it
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[1, 2, 10, 33, 53, 71, 73] 33
[53, 71, 73] 71
[73] 73
not exists
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[1, 2, 10, 33, 53, 71, 73] 33
[1, 2, 10] 2
[1] 1
not exists
三、匿名函数

1.lambda
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

f=lambda x,y:x+y
print(f)

print(f(1,2))
运行结果:
<function <lambda> at 0x0000000000B40268>
3
map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
运行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81]

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

2.lambda用法
(1)把匿名函数赋值给一个变量,再利用变量来调用该函数

f = lambda x: x * x
print(f)
f(5)
运行结果:
<function <lambda> at 0x10453d7d0>
25

(2)匿名函数作为返回值返回

def build(x, y):
    return lambda: x * x + y * y
t=build(4,3)
print(t())

(3)max,min,zip,sorted的用法

salaries={
'egon':3000,
'alex':100000000,
'wupeiqi':10000,
'yuanhao':2000
}

print(max(salaries))    #只根据字典的key进行比较
res=zip(salaries.values(),salaries.keys())  #接收任意个参数,返回一个元组列表
print(res)
print(list(res))
#匿名函数用法
print(max(salaries,key=lambda k:salaries[k]))
print(min(salaries,key=lambda k:salaries[k]))
print(sorted(salaries))      #默认的排序结果是从小到到
print(sorted(salaries,key=lambda x:salaries[x]))        #默认的排序结果是从小到大
print(sorted(salaries,key=lambda x:salaries[x],reverse=True))     #默认的排序结果是从小到大
运行结果:
yuanhao
<zip object at 0x0000000000A17A88>
[(3000, 'egon'), (100000000, 'alex'), (2000, 'yuanhao'), (10000, 'wupeiqi')]

alex
yuanhao
['alex', 'egon', 'wupeiqi', 'yuanhao']
['yuanhao', 'egon', 'wupeiqi', 'alex']
['alex', 'wupeiqi', 'egon', 'yuanhao']

3.global用法
如果你想要为一个定义在函数外的变量赋值,那么你就得告诉Python这个变量名不是局部的,而是全局的。
我们使用global语句完成这一功能。没有global语句,是不可能为定义在函数外的变量赋值的。

注意:应该尽量避免这样做,因为这使得程序的读者会不清楚这个变量是在哪里定义的。
x=1000
def f1():
    x=0     #只在局部生效

f1()
print(x)    #打印的是全局变量x
运行结果:
1000
x=1000
def f1():
    global x   #调用全局变量x
    x=0

f1()
print(x)
运行结果:
0

4.内置函数

  • map函数
    Map接受一个方法和一个集合作为参数。它创建一个新的空集合,以每一个集合中的元素作为参数调用这个传入的方法,然后把返回值插入到新创建的集合中。
l=['alex','wupeiqi','yuanhao']
res=map(lambda x:x+'_SB',l)
print(res)
print(list(res))
运行结果:
<map object at 0x00000000007FB9B0>
['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
nums=(2,4,9,10)
res1=map(lambda x:x**2,nums)
print(list(res1))
运行结果:
[4, 16, 81, 100]
  • reduce函数
    reduce()传入的函数必须接收两个参数,reduce()对list的每个元素反复调用函数,并返回最终结果值。
from functools import reduce

l=[1,2,3,4,5]
print(reduce(lambda x,y:x+y,l,10))    #reduce(f,[1,2,3,4],10) #1+2+3+4+10
运行结果:
25
  • filter函数
    对每个元素进行判断,返回True或False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
l=['alex_SB','wupeiqi_SB','yuanhao_SB','egon']

res=filter(lambda x:x.endswith('SB'),l)
print(res)
print(list(res))
运行结果:
<filter object at 0x000000000108BA58>
['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
四、面向过程编程与函数编程

1.概念
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

2.总结
面向过程解释:
函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的函数,一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。

例如:
用户登录流程:前端接收处理用户请求-》将用户信息传给逻辑层,逻辑词处理用户信息-》将用户信息写入数据库
验证用户登录流程:数据库查询/处理用户信息-》交给逻辑层,逻辑层处理用户信息-》用户信息交给前端,前端显示用户信息

3.高阶函数

  • map函数应用
array=[1,3,4,71,2]

ret=[]
for i in array:
    ret.append(i**2)
print(ret)

#如果我们有一万个列表,那么你只能把上面的逻辑定义成函数
def map_test(array):
    ret=[]
    for i in array:
        ret.append(i**2)
    return ret

print(map_test(array))

#如果我们的需求变了,不是把列表中每个元素都平方,还有加1,减一,那么可以这样
def add_num(x):
    return x+1
def map_test(func,array):
    ret=[]
    for i in array:
        ret.append(func(i))
    return ret

print(map_test(add_num,array))
#可以使用匿名函数
print(map_test(lambda x:x-1,array))

#上面就是map函数的功能,map得到的结果是可迭代对象
print(map(lambda x:x-1,range(5)))
运行结果:
[1, 9, 16, 5041, 4]
[1, 9, 16, 5041, 4]
[2, 4, 5, 72, 3]
[0, 2, 3, 70, 1]
<map object at 0x0000000001503A20>
  • reduce函数应用
from functools import reduce
#合并,得一个合并的结果
array_test=[1,2,3,4,5,6,7]
array=range(100)

#报错啊,res没有指定初始值
def reduce_test(func,array):
    l=list(array)
    for i in l:
        res=func(res,i)
    return res

# print(reduce_test(lambda x,y:x+y,array))

#可以从列表左边弹出第一个值
def reduce_test(func,array):
    l=list(array)
    res=l.pop(0)
    for i in l:
        res=func(res,i)
    return res

print(reduce_test(lambda x,y:x+y,array))

#我们应该支持用户自己传入初始值
def reduce_test(func,array,init=None):
    l=list(array)
    if init is None:
        res=l.pop(0)
    else:
        res=init
    for i in l:
        res=func(res,i)
    return res

# print(reduce_test(lambda x,y:x+y,array))
print(reduce_test(lambda x,y:x+y,array,50))
运行结果:
4950
5000
  • filter函数应用
movie_people=['alex','wupeiqi','yuanhao','sb_alex','sb_wupeiqi','sb_yuanhao']

def tell_sb(x):
    return x.startswith('sb')

def filter_test(func,array):
    ret=[]
    for i in array:
        if func(i):
            ret.append(i)
    return ret

print(filter_test(tell_sb,movie_people))

#函数filter,返回可迭代对象
print(filter(lambda x:x.startswith('sb'),movie_people))
运行结果:
['sb_alex', 'sb_wupeiqi', 'sb_yuanhao']
<filter object at 0x0000000001487C18>

转载于:https://www.cnblogs.com/tongxiaoda/p/7595847.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值