1.高阶函数(map/reduce/filter)

高阶函数是指函数的参数可以是函数

这篇总结几个常用的高阶函数:map/reduce/filter

map函数、reduce函数、filter函数都是Python中的内建函数。

map函数

map函数的作用是将一个函数作用于一个序列的每一个元素,一行代码即可完成,不需要用我们平常喜欢用的循环。map将运算进行了抽象,我们能一眼就看出这个函数是对一个序列的每个元素进行了同样的一个操作。map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

注意第一个参数只需要传函数名,而这个函数参数就是后面的序列的每一个元素传入。由于map的结果是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

def mul(x):
    return x*x
    
r = list(map(mul, [1,2,3,4,5,6,7,8,9]))
#或简化:
r = list(map(lambda x:x*x, [1,2,3,4,5,6,7,8,9]))

reduce函数

reduce也是把一个函数作用在一个序列上,它必须接收两个参数,把前两个参数计算的结果作为第一个参数继续和序列的下一个元素进行累积计算,即:

reduce(f, [x1, x2, x3, x4,x5]) = f(f(f(f(x1, x2), x3), x4),x5)

例1:

#将list转化成一个整数
from functools import reduce
L=[1,2,5,8]
print(reduce(lambda x,y: x*10+y, L))#1258

注意:

reduce()要从functools中import

lambda函数是匿名函数,我们可以用lambda函数进行一些简单进行,省去定义函数。lambda函数的参数可以0个或多个,参数用逗号分隔。参数后跟冒号,再跟表达式,返回的是计算的结果。

例2:

#将字符串转化成整数
from functools import reduce
def str2int(s):
    def str2num(x):
        DIGITS={'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
        return DIGITS[x]
    return reduce(lambda y,z:y*10+z, map(str2num,s))
print('9748')#9748

#将字符串转化成浮点数
from functools import reduce
def str2float(s):
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}    
    def str2num(a):
        return DIGITS[a]
    return reduce(lambda x,y:x*10+y, map(str2num, s.split('.')[0])) +reduce(lambda x,y:x*10+y, map(str2num, s.split('.')[1]))*10**(-len(s.split('.')[1]))

filter函数

filter函数和map函数类似,接收两个参数,一个是函数,第二个是一个序列。filter函数把传入的函数依次作用于序列的每一个元素,根据函数的返回值是True还是False决定保留或删除该元素。

如我们想找出一个序列中的所有偶数,等等。

#删除序列中元素的所有空格
def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

#返回一个范围内的所有回数(从左至右和从右至左,是同一个数字)
def is_palindrome(n):
    s=str(n)
    return s[::1] == s[::-1]

# 测试:
output = filter(is_palindrome, range(1, 200))
print(list(output))
#结果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]

2.函数嵌套

函数嵌套分为函数嵌套定义和函数嵌套调用。

函数嵌套定义即在一个函数定义的函数体内定义另外一个函数:

#函数嵌套定义示例
def f():
    print("f")
    def f1():
        print("f1")
        def f2():
            print("f2")
        f2()
    f1()

f()
#结果:f
#       f1
#       f2

还可以将嵌套定义的函数作为return的参数(这种用法在下面的闭包中详细介讲解),下面的代码跟上面的代码输出结果一致。

def f():
    print("f")
    def f1():
        print("f1")
        def f2():
            print("f2")
        return f2
    return f1

t1 = f()#t1就是f1
t2 = t1()#即调用f1(),t2就是f2
t3 = t2()#即调用f2()

函数嵌套调用就是在一个函数体里面调用另外一个函数。

例:在func2中调用了func1函数

#函数嵌套调用示例
def func1(x,y):
    return x*y
    
def func2(a,b,c):
    r1 = func1(a,b)
    r2 = func1(a,c)
    r3 = func1(b,c)
    print(r1,r2,r3)
    
func2(2,5,9)
#结果:10 18 45

3.名称空间及变量作用域

名称空间就是存储名字的地方,Python中变量与值的绑定关系的地方。分为三种:内置名称空间、全局名称空间、局部名称空间。

内置名称空间:Python解释器启动时首先加载的

全局名称空间:执行脚本时,以脚本内容为基础,加载全局名称空间

局部名称空间:在运行脚本的过程中,如果调用函数,就会临时产生局部名称空间

内置名称空间和全局名称空间对应的作用域是全局的,而局部名称空间对应的作用域是局部的。

因此我们在脚本里应该尽可能少地定义全局有效的变量,这样耗内存且拖慢运行速度。 

变量的作用域是定义的时候就固定了的,对于函数中的参数变量,是在函数定义阶段固定的,跟函数的调用位置不相关。

在函数内定义的变量一般只在函数内部起作用,不能再函数外部使用。

a = 99
print(a)#99
def addn():
    a = 100#这里定义的a只在本函数内部有作用
    print(a)
    a += 1
    print(a)
addn()#100 101
print(a)#99
a = 200
print(a)#200

4.闭包

上面讲到的函数嵌套定义中的第二部分示例其实就是闭包。闭包返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

闭包中内层的函数通过nonlocal关键字可以使得其可以使用外层函数中定义的变量。

下面的例子中,如果n不指定为nonlocal,程序会报错

#实现一个计数器
def counter():
    n=0
    def incr():
        nonlocal n
        x=n
        n+=1
        return x
    return incr

c=counter()
print(c())#1
print(c())#2
print(c())#3

使用闭包可以延迟计算,先将函数包起来,需要用的时候再调用。

 5.装饰器

我们在写Python程序的时候往往需要输出一些日志信息,但是不希望去改变函数内部的代码,那么给函数动态增加如输出日志的功能,可以用装饰器实现。

装饰器有两种:不带参数和带参数的装饰器。

#不带参数的装饰器
import functools
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

#带参数的装饰器
#text是需要传入的参数
import functools
def log1(text):
    def decorator(func):
@functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))#func.__name__返回变量func对应的函数名
            return func(*args, **kw)
        return wrapper
    return decorator

装饰器的输入是函数,返回值也是函数。将装饰器放在要定义的函数的前面(使用@),即可实现在调用定义的函数的时候会运行一次装饰器。

@log
def now():
    print('2019-08-10')

@log1('tt')
def now():
    print('2019-08-10')

那么对于上述不带参数的装饰器,调用now()函数的时候的输出如下:相当于执行了now = log(now)

>>>now()
call now():
2019-08-10

对于上述带参数的装饰器,调用now()函数的时候的输出如下:相当于执行了now = log('tt')(now)

>>>now()
tt now():
2019-08-10

多个装饰器可以同时放在函数定义的上面

@log1
@log2
@log3
def now():
    pass

#相当于:now = log1(log2(log3(now)))

最后放一个装饰器的例子。

用一个装饰器为多个函数加上认证功能,登录成功一次后在超时时间内进行其他操作(运行下一个函数)无需重复登录,超过了超时时间,则必须重新登录。

#装饰器为多个函数加上认证功能,登录成功一次后在超时时间内进行其他操作(运行下一个函数)无需重复登录,超过了超时时间,则必须重新登录
import time,random

user = {'name':None, 'login_time':False, 'timeout':0.0000002}

def log(func):
    def wrapper(*args,**kwargs):
        if user['name']:
            timeout=time.time()-user['login_time']
            if timeout < user['timeout']:
                return func(*args,**kwargs)
            else:
                print("登录超时,请重新登录!")
        name=input('name: ').strip()
        password=input('pwd: ').strip()
        if name == 'tt' and password == '123':
            user['name']=name
            user['login_time']=time.time()
            res=func(*args,**kwargs)
            return res
    return wrapper
        
        
@log
def first():
    print("welcome to login!")
    
            
@log
def home():
    print("welcome to home!")
    
first()
home()

当我们在首次登录后会执行first函数里面的代码,再执行home函数时已经超过超时时间,这时的输出如下,需要再次输入用户名和密码。

name: tt
pwd: 123
welcome to login!
登录超时,请重新登录!
name:

而如果未超过超时时间,home函数能够直接执行,输出:

name: tt
pwd: 123
welcome to login!
welcome to home!