四、函数式编程

  

  python代码运行的时候遇到函数时如何处理的:

  从python解释器开始执行之后,就在内存中开辟了一个空间

  每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来

  但是,当遇到函数定义的时候解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心

  等执行到函数调用的时候,python解释器会再开辟一块内存来存储这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内

存中,函数中的变量只有在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

  我们给这个‘存放名字和值的关系’的空间叫做-------命名空间

  代码在运行开始,创建的存储‘变量名与值的关系’的空间叫做全局命名空间,在函数的运行中开辟的临时空间叫做局部命名空间,存放内置的叫做内置命名空间

  三种命名空间之间的加载和取值顺序:

  加载顺序:内置命名空间(程序运行前加载)>全局命名空间(程序运行中:从上到下加载)>局部命名空间(程序运行中:调用时才加载)

  取值:在局部调用:局部命名空间----->全局命名空间----->内置命名空间

       在全局调用:全局命名空间----->内置命名空间

  作用域

  作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域

  全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效

  局部作用域:局部名称空间,只能在局部范围内生效

  global关键字:内部作用域想要对外部作用域的变量进行修改,global关键字在一个函数中来修改全局变量,在一个函数外使用global关键字没有效果,因为该变量本身就是

全局变量

a = 10
def func():
    global a #global 必须在赋值之前
    a = 20
print(a)
func()
print(a)
10
20
a = 10
def func():
    a = 11
print(a)
func()
print(a)
10
10

  global是对整个环境下的变量起作用,而不是对函数内的变量起作用

x = 0
def outer():
    x = 1
    def inner():
        global x 
        x = 2
        print('inner:',x)
    inner()
    print('outer:',x)
outer()
print('global:',x)
inner: 2
outer: 1
global: 2

  函数的嵌套定义:内层嵌套函数时,说明是从上到下的执行顺序

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

  函数的作用域链

def f1():
    a = 1
    def f2():
        print(a)
    f2()
f1()
1
def f1():
    a = 1
    def f2():
        a = 2
    f2()
    print('a in f1 : ',a)
f1()
a in f1 :  1

  nonlocal关键字

  global适用于函数内部修改全局变量的值,所以只能改变全局同变量,并不能改变嵌套的里面的同变量,但是本内部的变量还是会改变的,即输出内部的是global的值

  nonlocal适用于嵌套函数中内部函数修改外部变量的值

  1)外部必须有这个变量

  2)在内部函数声明nonlocal变量之前不能再出现同名变量

  3)内部修改这个变量如果想在外部有这个变量的第一层函数中生效

x = 0
def outer():
    x = 1
    def inner():
        nonlocal x 
        x = 2
        print('inner:',x)
    inner()
    print('outer:',x)
outer()
print('global:',x)
inner: 2
outer: 2
global: 0

  当使用nonlocal时,就声明了该变量不止是嵌套函数inner()里面才有效,而是在整个大函数里面都有效,但是并未改变全局变量

def f1():
    a = 1
    def f2():
        nonlocal a 
        a = 2
    f2()
    print('a in f1:',a)
f1()
a in f1: 2

 

函数式编程

 

  1.高阶函数

  高阶函数英文叫Higher-order function

  变量可以指向函数

  可见,abs(-10)是函数调用,而abs是函数本身

  f = abs

  函数本身也可以赋值给变量,即:变量可以指向函数

  f(-10)

  说明变量f现在已经指向了abs函数本身,直接调用abs()函数和调用变量f()完全相同

  函数名也是变量

  abs=10

  abs(-19) #不能执行了,因为abs = 10

  把abs指向10后,就无法通过abs(-10)调用该函数了,因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

  当然实际代码绝对不能这么写,这里是为了说明函数名也是变量,要恢复abs函数,请重启python交互环境

  传入函数

  既然变量可以指向函数,函数的参数能接受变量,那么一个函数就可以接受另一个函数作为参数,这种函数就称之为高阶函数

  一个函数可以接受另一个函数作为参数,这种函数为高阶函数

  一个简单的高阶函数:

def add(x,y,f):
    return f(x)+f(y)
add(1,-2,abs) #3

  map/reduce

  python 内建了 map()和reduce()函数

  map()函数接受了两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为Iterator返回

  要把函数作用在list:[1,2,3,4,5,6,7,8,9]上,就可以用map()实现

def f(x):
    return x*x
r = map(f, [1,2,3,4,5,6,7,8,9])
list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]  

  map()传入的第一个参数是f。即函数对象本身,由于结果r是一个Iterator,Iterator是惰性序列,因此,通过list()函数让他把整个序列都计算出来并返回一个list

def f(x):
    return x*x
L = []
for n in [1,2,3,4,5,6,7,8,9]:
    L.append(f(n))
print(L)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

  这样也可以,但是你能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list吗”

  所以,map()作为高阶函数,事实上他把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2方,还可以计算任意复杂的函数

  比如,把这个list所有数字转为字符串,只需要一行代码,其中str是函数

list(map(str,[1,2,3,4,5,6,7,8,9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

  reduce

  把一个函数作用在一个序列[x1,x2,x3,;;;],这个函数必须接收两个参数,第一个参数是函数,第二个参数是可迭代对象,reduce把结果继续和序列的下一个元素做累积计算,

最终的结果不一定是迭代器

  其效果就是:

reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4) #第一个和第二个作用,结果:a
                                               #结果a和第三个作用,结果:b
                                               #结果b和第四个作用,结果:c,,,以此类推

  比方说对一个序列求和,就可以用reduce实现:

from functools import reduce    #1+3
def add(x,y):                   #4(1+3)+5
    return x+y                  #9(4+5)...
reduce(add,[1,3,5,7,9])         #29       

  当然求和运算可以直接用python内建函数sum(),没必要动用reduce,但是如果要把序列[1,3,5,7,9]变换成整数13579,reduce就可以派上用场:

from functools import reduce #x=1,y=3
def fn(x,y):                 #x=13,y=5
    return x*10 + y          #x=135,y=7
reduce(fn,[1,3,5,7,9])       #x=1357,y=9
reduce(fn,(1,3,5,7,9))
#结果:13579

  这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换成int的函数

from functools import reduce
def fn(x,y):
    return x*10+y
def char2num(s):
    digits = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
    return digits[s]
a = map(char2num,'13579')#因为map()是惰性的,所以一个一个放进去 
a #结果:<map at 0x5ddab90>
list(a) #[1, 3, 5, 7, 9]
reduce(fn,list(map(char2num,'13579'))) #等价于reduce(fn,[1, 3, 5, 7, 9])

  整理在一起

DIGITS = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
def str2int(s):
    def fn(x,y):
        return x*10+y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn,map(char2num,s))#可能是因为map是惰性的,所以运行不出来,另外为什么不加list,是因为reduce里面可以接受元组;map第二个数作为前面函数的参数
str2int('12345') 
12345 

  filter

  python内建的filter()函数用于过滤序列

  filter()函数,是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断

结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。

  和map()类似,filter()也接受一个函数和一个序列、和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素

  例如,一个list中,删掉偶数,只保留奇数,可以这么写:

def is_odd(n):
    return n%2 == 1 # ==了就是True
list(filter(is_odd,[1,2,4,5,6,9,10,15]))
[1, 5, 9, 15]  

  把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
    return s and s.strip() #strip()参数为空时为删除空白符;.strip()方法作用是把字符串头尾的指定字符去掉,比如“000xxx000”,s.strip('0')为“xxx”
#None类型没有strip方法,执行会报错。前面加s,当s为None时,执行到and前面已经可以判定为False,and后的部分就不会被执行了。
#变量A and 变量B 返回的一定是个bool值,如果 x 为 False,x and y 返回 False,否则它返回 y 的运算值。并且 None没有strip()方法。
list(filter(not_empty,['A','','B',None,'c','']))
['A', 'B', 'c']

  可见用filter()这个高阶函数,关键在于正确实现一个’筛选‘函数

  注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

  用filter求素数

  '''计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:

  首先,列出从2开始的所有自然数,构造一个序列:

  2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

  取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:新序列的第一个数5,然后用

5把序列的5的倍数筛掉:不断筛下去,就可以得到所有的素数。

  用python来实现这个算法,可以先构造一个从3开始的奇数序列:

def _odd_iter():
    n = 1
    while True:
        n = n+2
        yield n #注意这是一个生成器,并且是一个无限序列
#while True语句中一定要有结束该循环的break语句,否则会一直循环下去    

  然后定义一个筛选函数
def _not_divisible(n):
return lambda x:x%n>0
#最后,定义一个生成器,不断返回下一个素数
def primes():
yield2
it = _odd_iter()#初始序列
while True:
n = next(it) #返回序列的第一个数
yield n 
it = filter(_not_divisible(n),it)#构造新序列,把不能被整除的挑出来
#由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:
#难道说filter以及map、reduce里面的第二部分可以是iterator吗??????????
#打印1000以内的素数
for n in primes():
if n<1000:
print(n)
else:
break
#注意到Iterator是惰性计算的序列,所以我们可以用python表示‘全体自然数’,‘全体素数’这样的序列,而代码非常简洁

  map结果是迭代器,格式为map(f,[]),一个函数和一个可迭代对象;reduce结果不一定是迭代器,做累计运算,可以和map组合,reduce(f,map(f,[]))或reduce(f,);filter结果是迭代器,filter(f,[]),其函数是判断的函数,如果是迭代器就要用list来接收

  sorted排序算法

  如果是数字,可以直接比较,但是如果是字符串或者是两个dict呢,直接比较数字上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来

  python内置的sorted()函数就可以对list进行排序

sorted([36,5,-12,9,-21])
[-21, -12, 5, 9, 36]

  此外,sorted函数也是一个高阶函数,他还可以接受一个key函数来实现自定义的排序,例如按照sin函数大小排序

import math
sorted([36,5,-12,9,-21],key=math.sin)
[36, 5, -21, 9, -12]

  key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序,对比原始的list和经过key=math.sin处理过的list

lis = [36,5,-12,9,-21]  #这个是sorted([36,5,-12,9,-21])的结果
keys = [36,5,-21,9,-12] #这个是输出sorted([36,5,-12,9,-21],key=math.sin)的结果,返回的还是原始序列的数

  我们在看一个字符串排序的例子

sorted(['bob','about','Zoo','Credit'])
['Credit', 'Zoo', 'about', 'bob']

  默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z'<'a',结果,大写字母Z会排在小写字母a的前面

  注意是,大写排在小写的前面

  反向排序

sorted(['bob','about','Zoo','Credit'],key = str.lower, reverse = True)
['Zoo', 'Credit', 'bob', 'about']

  从上面例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁

 

  2.返回函数

  返回函数,顾名思义是将函数作为返回值

  高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

  我们来实现一个可变参数的求和

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax+n
    return ax

  但是不需要立刻求和,而是在后面的代码中,根据需要在计算怎么办?可以不返回求和的结果,而是返回求和的函数

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n 
        return ax
    return sum #sum是函数

  当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数(可能是函数是sum(),返回的是sum吧)

f = lazy_sum(1,3,5,7,9)
f
<function __main__.lazy_sum.<locals>.sum()>

  调用函数f时,才真正计算求和的结果

def lazy_sum(*args):
    def sum(): #相当于在原来的部分上加了def sum():    return sum
        ax = 0
        for n in args:
            ax = ax + n 
        return ax
    return sum #sum是函数
f = lazy_sum(1,3,5,7,9)
f()
25

  在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量

  当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为‘闭包(closure)’的程序结构拥有极大的威力

  请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数

f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
f1 == f2
False

  f1 和 f2的调用结果互不影响

  不可以是

def all_sum(*args):   
    ax = 0  #变量放在内函数外时,会报错,不报错的话将ax = 0 放在要改变ax的sum函数里面,作为局部变量;因为ax变动了所以必须ax在内部函数里面,如果ax不变动的话可以放在这里
    def sum():
        for i in args:
            ax = ax + i
        return ax
    return sum
x = all_sum(1,2,3,4)
x()

 

  闭包

  函数内在包含子函数,并最终返回子函数

  闭包函数最大的价值在于:我们可以在函数的外部(返回了内嵌函数),直接读取该函数的局部变量

  闭包条件:

  1)在一个外函数中定义了一个内函数

  2)在一个内函数中运用了外函数的临时变量(非全局变量,即非全局的局部自由变量)

  3)外函数的返回值是内函数的引用

  就所观察的,通俗来讲就是内部函数里面print(a)了一个在外部函数定义的变量a(可能有偏颇)

  一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失,但是闭包是一种特殊情况,如果外函数在结束的

时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束

#闭包函数的实例
#outer是外部函数,a和b都是外函数的临时变量
def outer(a):
    b = 10
    def inner():  #inner是内函数
        print(a+b) #在内函数中,用到了外函数的临时变量
    return inner   #外函数的返回值是内函数的引用

 

def func():
    name = 'eva'
    def inner():
        print(name)

  闭包函数:内部函数包含对外部作用域而非全局作用域名字的引用,该内部函数(函数内部定义的函数)称为闭包函数

def greeting_conf(prefix):
    def greeting(name):
        print(prefix,name)
    return greeting
mGreeting = greeting_conf('Good Morning')
mGreeting('Wilber')
(<cell at 0x04F3DF90: str object at 0x04F3FE80>,)
Good Morning Wilber
def FuncX(x):
    def FuncY(y):
        return x*y
    return FuncY

  greeting函数访问了非本地变量prefix,根据对namespace的理解,这也是可以的,所以是闭包

  变量prefix并没有随着greeting_conf函数的退出而销毁,反而是生命周期得到了延长

  对于FuncY函数来说,对在FuncX函数的整个作用域(FuncY函数的非全局作用域的外部作用)的变量x进行应用,自此就可以说FuncY函数就是所谓

的闭包  

  我们知道函数内的变量要想在函数外部用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数,自然是直接把这个函数名字返

回就好了

def func():
    name = 'eva'
    def inner():
        print(name) #内部函数inner对外部name的引用
    return inner
f = func()
f()
eva

  判断闭包函数的方法

#输出的__colsure__有cell元素:是闭包函数
def func():
    name = 'eva'
    def inner():
        print(name)
    print(inner.__closure__)
    return inner
f = func()
f() 
print('-------------------------------------')
#输出的__closure__为None:不是闭包函数
name = 'egon'
def func2():
    def inner():
        print(name) #因为这里没有引用外部函数的临时变量
    print(inner.__closure__)
    return inner
f2 = func2()
f2()
(<cell at 0x0497D3F0: str object at 0x04938D60>,)
eva
-------------------------------------
None
egon

  闭包嵌套

def wrapper():
    money = 1000
    def func():
        name = 'eva'
        def inner():
            print(name,money)
        return inner
    return func
f = wrapper()
i = f()
i()
eva 1000

  命名空间:

  一共有三种命名空间从大范围到小范围顺序:内置命名空间,全局命名空间,局部命名空间

  作用域:小范围可以用大范围的,但是大范围不能用小范围的

  注意到返回的函数在其定义内部引用了局部变量(在函数内定义的量)args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单实现起来可不容易

  注意:

#此类情况会报错,local variable 'ax' referenced before assignment
#因为ax = 0 放在了函数定义外面
#如果一个全局变量在函数内被重新定义过,再在函数内使用变量则默认为局部变量;如果在函数内没有定义,
#但是使用的话就变成了全部变量,如果这个值要改变,就会报错,如果只是打印的话,就不会报错
def lazy_sum(*args):
    ax = 0
    def sum():  
        for n in args:
            ax = ax + n 
        return ax
    return sum
f = lazy_sum(1,3,5,7,9)
f()
#例如:
def f():
    a = 1
    a = a + 1
    return  a 
f()  
#结果是2
a = 1
def f():
    a = a + 1
    return  a 
f()  
#local variable 'a' referenced before assignment
a = 5
def f():
    a = 10
    a = a+1
    return a 
f()
#11

  我的理解是,若内部函数的变量会变时,全局变量在内部函数中引用未声明,那么就会报错

  另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用f()才执行,我们来看一个例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i*i
        fs.append(f)
    return fs 

f1, f2, f3 = count()
f1()
f2()
f3()

  在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了

  你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是都为9

9
9
9 

  返回的fs的列表为[f,f,f],当执行f1,f2,f3=count()的时候,f1=f,f2=f,f3=f,这个时候变量i的值已经变成了3,所以最后结果都为9

  原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i

  如果一定要引用循环变量怎么办,方法是在创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i*i
        fs.append(f())
    return fs
f1, f2, f3 = count()   

  或

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):                    
        fs.append(f(i))
    return fs 

f1, f2, f3 = count()
f1()
f2()
f3()
1
4
9

 

  3.匿名函数

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

  在python中,对匿名函数提供了有限支持,还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)外,还可以传入匿名函数

list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

  通过对比可以看出,匿名函数lambda x:x * x实际上就是:

def f(x):
    return x*x
list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

  关键字lambda表示匿名函数,冒号前面的x表示函数参数

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

  用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突,此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变函数变量,再利用变量来调用该函数

f = lambda x:x*x
f(5)
25

  同样,也可以把匿名函数作为返回值返回,比如

def build(x,y):
    return lambda :x*x +y*y
build(1,5)
<function __main__.build.<locals>.<lambda>()>
def build(x,y):
    return lambda :x*x +y*y
build(1,5)()
26
f = lambda a,b,c:a+b+c #冒号之前的字符表示他们是这个函数的参数
f(1,2,3) #匿名函数不需要return来返回值,表达式本身结果就是返回值
6

  无参数匿名函数

t = lambda: True
t()
s = 'this is\na\ttest'
print(s.split()) #split函数默认分割:空格,换行符,TAB
['this', 'is', 'a', 'test']
' '.join(s.split())
#等价于
(lambda s: ' '.join(s.split()))('this is\na\ttest')
'this is a test'

  有参数匿名函数的调用

a = lambda x, y, z: x*y*z
a(2,3,4)
24
b = lambda x, y=2: x+y
b(10)
12
c = lambda *z: z #返回的是一个元组
c('testing1','testing2')
('testing1','testing2')
d = lambda **y: y #y返回的是一个字典
d()
{}

  后面传递参数

(lambda x,y: x if x>y else y)(101,102)
02
(lambda x:x**2)(3)
9

  lambda返回的值,结合map,filter,reduce使用

list(filter((lambda x:x%3==0),([1,2,3,4,5,6])))
[3,6]

  lambda嵌套到普通函数中,lambda函数本身作为return的值

def increment(n):
    return lambda x: x+n
f = increment(4)
f(2)
6
def say():
    title = 'sir,'
    action = lambda x: title + x
    return action #相当于return lambda x: title + x
t = say()
t('hello')
'sir,hello'

  大量例子

  字符串联合,有默认值,也可以x =(lambda...)这种格式

x = (lambda x='Boo',y='Too',z='Zoo':x+y+z)
x('Foo')
#'FooTooZoo'
x()
#'BooTooZoo'
x('aoo','coo')
#'aoocooZoo'

  和列表联合使用

L = [lambda x:x**2,\
    lambda x:x**3,\
    lambda x:x**4]
for f in L:
    print(f(2))
4
8
16
print(L[0](3))
9

  和字典结合使用

key = 'B'
dic = {'A':lambda:2*2,\
      'B':lambda:2*4,\
      'C':lambda:2*8}
print(dic[key]())
8

  求最小值

lower = lambda x,y:x if x<y else y
lower('aa','bb')
'aa'

  和map及list联合使用

import sys
showall = lambda x:list(map(sys.stdout.write,x))
showall(['Jerry','Sherry','Alice'])
JerrySherryAlice

   lambda和map联合使用

out = lambda *x:sys.stdout.write(" ".join(map(str,x)))
out('this','is','a','book!\n')
This is a book!

  判断字符串是否以某个字母开头

print((lambda c: c.startswith('B'))('Bob'))
#或者
x = lambda c: c.startswith('B')
x('Bob')
True

  lambda 和 map 联合使用

squares = map(lambda x:x**2,range(10))
filters = filter(lambda x:x>5 and x<50,squares)
list(filters)
[9, 16, 25, 36, 49]

  lambda和sorted联合使用

#按death名单里面,按年龄来排序
#匿名函数的值返回key,进行排序
death = [('James',32),('Alies,20'),('Wendy',25)]
sorted(death,key = lambda age:age[0])
#按第二个元素,索引为0排序
#['Alies,20', ('James', 32), ('Wendy', 25)]

  lambda和reduce联合使用

from functools import reduce
L = [1,2,3,4]
sum = reduce(lambda x,y:x+y,L)
sum
10

  求2-50之间的素数(只能被1或被自己整除的数)

nums = range(2,50)
for i in nums:
    nums = filter(lambda x:x==i or x%i,nums)
next(nums)
2

  求两个列表元素的和

a = [1,2,3,4]
b = [5,6,7,8]
c = map(lambda x,y:x+y,a,b)
list(c)
[6, 8, 10, 12]

  求字符串每个单词的长度

sentence = 'Welcome to beijing'
words = sentence.split()
lengths = map(lambda x:len(x),words)
list(lengths)
[7,2,7]

 

  4.装饰器

  公司要用函数编写的时间来论绩效,此时,你编写了一个函数

import time
def func1(): #这是你编写的函数,这是一个闭包函数,因为引用了外部函数中的非本局变量func
    print('in func1')
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time()-start)
    return inner
func1 = timer(func1)
func1()

  什么是装饰器?

  python装饰器(functional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能,这个函数的特殊之处

在于它的返回值也是一个函数,这个函数是内嵌‘原’函数的函数

一般而言,我们要想拓展原来函数代码,最直接的方法就是侵入代码里面修改,但是实际工作中,有些时候核心代码并不可以直接去改,所以在不该动原代码的情况下,我们可

以再定义一个函数(但是生效需要再次执行函数)

import time
def deoo(funo):
    start_time = time.time()
    f()
    end_time = time.time()
    execution_time = (end_time - start_time)*1000
    print('time is %d ms' %execution_time)
def f():
    print('hello')
    time.sleep(1)
    print('world')
if __name__ == '__main__':
    deoo(f)
    print('f.__name__is',f.__name__)

  装饰器的本质:一个闭包函数

  装饰器的功能:在不修改原函数及调用方式的情况下对原函数进行扩展:例如如何计算现有的代码函数的时间,即不改动代码的基础上计算时间

  函数装饰器要满足以下条件:

  1).不能改变原来函数的代码                 2).为函数添加新的功能                     3).不能改变函数的调用方式

  装饰器装饰带参数的函数

def timer(func):  
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner
@timer #==>func1 = timer(func1)
def func1(a):
    print(a)
func1(1)

  装饰器装饰有两个函数,需要传递的参数不一样,并且函数是有返回值的

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner
@timer #timer(func1)
def func1(a,b):
    print('in func1')
@timer #timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'func2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))
in func1
0.0
in func2 and get a:aaaaaa
0.0
func2 over

  开放封闭原则

  1)对扩展是开放的

  任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改,所以必须允许代码扩展,增加新功能

  2)对修改是封闭的

  因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响到正在使用该函数的用户

  由于函数也是一个对象,而且函数对象可以赋值给变量,所以,通过变量也能调用该函数

  装饰器的主要功能

  在不改变函数调用方式的基础上再函数的前后添加功能

  装饰器的固定格式

  简单来说:1).外层函数参数作为内层函数中的函数或参数,不为空;

       2)内层函数参数与内层函数中的函数的参数一致,若无则为空;

       3)返回内层函数名,不加括号;

       4)调用为外层函数加内层函数中的函数名,不加括号,再在后面加括号,有参数写参数;

       5)自定义的函数参数无限制;

       6)装饰器的调用为@外函数名,写在自定义函数之前,最后自定义函数加括号

          7)返回内函数中的函数

       8)外置函数的参数类型要与内函数的参数类型一致

def timer(func):
    def inner(*args,**kwargs):
        #执行函数前需要做的
        re = func(*args,**kwargs)
        #执行函数后需要做的
        return re
    return inner
from functools import wraps
def deco(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

  多个装饰器装饰同一个函数

def wrapper1(func):
    def inner():
        print('wrapper1,before func')
        func()
        print('wrapper1,after func')
    return inner
def wrapper2(func):
    def inner():
        print('wrapper2,before func')
        func()
        print('wrapper2,after func')
    return inner
@wrapper2 #===>f = warpper2(f)
@wrapper1
def f():
    print('in f')
f()
wrapper2,before func
wrapper1,before func
in f
wrapper1,after func
wrapper2,after func
def now():
    print('2015-3-25')
f = now
f() #或f = now() 
#2015-3-25

  函数对象有一个__name__属性,可以拿到函数的名字:

def now():
    print('2015-3-25')
f = now
f() #或f = now() 
now.__name__ 
f.__name__
now
now

  现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种代码运行期间动态增加功能的方式,叫做‘装饰器

(Decorator)

  本质上,decorator就是一个返回函数的高阶函数,所以,我们要定义一个能打印日志的decorator,可以定义如下

def log(func):
    def wrapper(*args,**kw):
        print('call ^%s():' %func.__name__)
        return func(*args,**kw)
    return wrapper

  观察上面的log,因为他是一个decorator,所以接受一个函数作为参数,我们要借助python的@语法,把decorator置于函数的定义处now

@log
def now():
    print('2015-3-25')

  调用now()函数,不仅会允许now()函数本身,还会在运行now()函数前打印一行日志

now()
call ^now():
2015-3-25

  把@log放在now()函数的定义处,相当于执行了语句

now = log(now)

  由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返

回的wrapper()函数

  wrapper()函数的参数定义是(*args,**kw),因此,wrapper函数可以接受任意参数的调用(即参数可有可无),在wrapper()函数内,首先打印日志,再紧接着调用原始函数

  一个python文件有两种使用方法,第一是直接作为脚本执行,第二是import到其他的python脚本中被调用(模块重用)执行。因此if__name__=='main':的作用是控制这两种

情况执行代码的过程
  在if__name__ == 'main':下的代码只有在第一种情况下(即文件作为脚本直接执行)
  简而言之,__name__就是当前模块名,当模块被直接运行时候模块名称为__main__,当模块被直接执行的时候,代码被执行,当模块被导入的时候,代码不运行
  举个例子:在test.py中写入如下代码:

print('i am the first')
if __name__ == '__main__':
    print('i am the second')
i am the first
i am the second

  即,if__name__=='__main__':语句之前和之后的代码都被执行

  然后在用一文件夹新建名称为import_test.py的脚本,只输入如下代码

import test
#执行import_test.py脚本,输出结果如下:
print('i am the first')
if __name__ == '__main__':
    print('i am the second')

  虽然此处的输出结果两个都输出了,但是如果是在创建了test.py的话,第二个是不会执行的

 

  5.偏函数

  python中的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function),要注意,这里的偏函数和数学意义上的偏函数不一样

  在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度,而偏偏函数也可以做到这一点,举例如下:

  int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

int('12345')

  但int()函数还提供额外的base函数,默认值为10,如果传入base函数,就可以做N进制转换

int('12345',base=8) #5349
int('12345',base=16) #74565

  假设要转换大量的二进制字符串,每次都传入int(x,base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去

def int2(x,base=2):
    return int(x,base)
int2('1000000')
int2('1010101')

  functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

import functools
int2 = functools.partial(int,base=2)
int2('1010101')

  所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数就会更简单

  注意到上面的新的int2函数,仅仅是把base参数重写设定默认值为2,但也可以在函数调用时传入其他值

int2('1000000',base=10)

  最后,创建偏函数时,实际上可以接受函数对象,#args和**kw这3个函数,当传入:int2 = functools.partial(int,base=2),实际上固定了int()函数的关键字参数base,也就是:

int2('10000')

  相当于:

kw = {'base':2}
int('10010',**kw)

  当传入:

max2 = functools.partial(max,10)

  实际上会把10作为*args的一部分自动加到左边,也就是:max2(5,6,7)

  相当于:

args = (10,5,6,7)
max2(*args)

  总结:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单

posted @ 2019-10-27 22:42  瞧我这个笨脑袋  阅读(211)  评论(0)    收藏  举报