Python 函数基础

函数式编程其中的一个特点就是允许把函数本身作为参数传递给另一个函数,也允许返回一个函数,Python支持函数式编程的这一特点。由于Python允许使用变量,因此Python不是纯函数式编程语言。

先了解函数作为变量是什么意思,以abs函数为例,执行print(abs(-10))的输出结果是10,这里输出的是abs(-10)的返回值;如果执行print(abs)输出结果则是<built-in function abs>,这里输出的则是abs函数。其实abs这个函数名也是变量,同样可以进行传递

f = abs
print(f(-10))

输出结果同样是10,和直接调用其实是一样的,如果这里直接print(f)的输出结果同样也是<built-in function abs>,这说明f函数已经指向了abs函数,这就是函数的传递。这样子,函数就可以作为参数传递

def addAbs(x,y,f):
    return f(x) + f(y)

sum = addAbs(-10,2,abs)
print(sum)

#输出结果为:12

函数可以作为变量传递,如果函数名在代码中使用了,并且赋了其他的值,这时候去调用这个变量将使用的是后面赋予的值,而不是原本的函数。实际上一般也不允许这么写

map

map函数接收两个参数,一个函数,一个Iterablemap将对传入的Iterable中的元素执行传入的函数,最后返回新的Iterable

def f(x):
    return x + 10

r = map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))
#输出结果为11~19

将函数f传入map迭代生成listmap作为高阶函数,将函数的规则运算抽象,不仅仅可以计算x+10还可以计算更加复杂的函数。

reduce

reduce函数同样也是接收函数和一个Iterablereduce函数的作用就是将传入函数的结果与Iterable的下一个元素累积计算,效果上感觉有点类似递归。

from functools import reduce

def add(x, y):
    return x + y

print(reduce(add, [1, 2, 3, 4, 5]))
#输出结果为15

可以看出来是1~5的累加,这么看reduce函数并没有什么特别的地方,但是给出了一个mapreduce结合使用的例子,将str转换成int,很简单,但是很好地使用了map函数和reduce函数

from functools import reduce

DIGTIS = {'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(c):
        return DIGTIS[c]
    return reduce(fn, map(char2num, s))

或者使用lambda表达式

from functools import reduce

DIGTIS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(c):
    return DIGTIS[c]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

有时候觉得大神的想法真多,一个简单地东西,还能思考多种写法。反思一下,其实学习的过程中很多东西看懂了,却没有比较深入的使用,需要适当的做一些扩展的思考来练习知识的使用,不然知识始终只是知识而已。

对于mapreduce两个方法,Google很久以前写过一篇论文MapReduce: Simplified Data Processing on Large Clusters,看了能更好地理解map/reduce

filter

filter函数,用于过滤,与map相似接收一个函数和一个Iterablefilter函数将传入的函数以此作用于传入的序列,函数返回TrueFalse表示该元素是否保留。

def check_empty(s):
    return s and s.strip()

l = list(filter(check_empty, ['X', '', 'Y', None, 'Z', '   ']))
print(l)
#输出结果:['X', 'Y', 'Z']

sorted

Python内置的sorted函数,可以对list进行排序,不仅仅是int,字符串类型也可以进行排序,字符串排序则是按照ASCII的大小进行比较的。

l = [86, 16, -64, 0, -3]

print(sorted(l))
#输出结果:[-64, -3, 0, 16, 86]

这是对int正常大小的排序,sorted还可以接收一个key函数来实现自定义排序,比如:按绝对值排序

l = [86, 16, -64, 0, -3]

print(sorted(l, key=abs))
#输出结果:[0, -3, 16, -64, 86]

接下来看看字符串的排序

l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']

print(sorted(l))
#输出结果:['C.C.', 'MrTrying', 'bruce', 'jack wharton', 'nothing']

ASCII中,大写字符的的ASCII值是小于小写字符的,所以默认情况下,大写字母开头的会排在前面;当然通过key可以忽略大小写的比较

l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']

print(sorted(l, key=str.lower))
#输出结果:['bruce', 'C.C.', 'jack wharton', 'MrTrying', 'nothing']

sorted函数还有第三个参数reversereverse=True可以将排序反序

l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']

print(sorted(l, key=str.lower, reverse=True))
#输出结果:['nothing', 'MrTrying', 'jack wharton', 'C.C.', 'bruce']

基本可以看出sorted函数可以将排序算法抽象的功能,提升了排序的扩展性,同时代码还是相当简洁的。sorted函数对list进行排序,就可以对dictkey或者value进行排序。

返回(一个)函数

Pyhton支持函数式编程,函数可以作为参数传递,也可以作为返回值使用。

这是一个求和函数

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

可以将这个函数作为返回值return出去,被赋值的函数使用方式是一样的。

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

f = lazy_sum()
print(f)
#输出结果:<function lazy_sum.<locals>.calc_sum at 0x1047a02f0>

print(f(1,2,3,4,5))
#输出结果:15

每次调用lazy_sum()都返回一个新函数,即使传参是一样的

闭包

闭包,简单一点理解就是:能够读取其他函数内部变量的函数。

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

f1, f2, f3 = count()

上面的count函数返回一个函数的list,函数是循环生成的,其中f()使用了count()中的局部变量ii的引用并保存在返回的函数,这种形式称之为闭包

可能觉得返回值应该是149,但是分别调用f1()f2()f3()输出结果全部都是9。原因在于引用了ii在循环中递增,随着f1()f2()f3()三个函数的生成i变成了3,导致最后输出都是9所以,使用返回闭包时,最好不要引用可变变量。

使用java会知道,java不支持函数式编程,所以传递函数的替代方式是传递一个实现的接口,而在实例化这个接口的代码块中,如果接口的方法想要使用自身作用于意外的变量,这个变量就必须使用final修饰。

上面的代码稍作修改,使用g()返回f()f()所使用的n在调用g()时传入不会在发生改变,进而f()所保存的n不会发生改变,再调用f1()f2()f3()就可以正常输出149

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

lambda表达式

函数传递时,可以使用匿名函数,简化代码

l = list(map(lambda x: x * x, [1, 2, 3, 4, 5]))
print(l)
#输出结果:[1, 4, 9, 16, 25]

lambda后跟的就是函数的参数,:后是函数返回值得表达式。

上面代码就可以看到,使用lambda x: x * x代替了

def f(x):
    return x * x

同样,lambda表达式也可以作为函数返回

def f(x):
    return lambda: x * x

Decorator(装饰器)

Decorator是高阶函数,可以在不修改原有还是的基础上,动态的增加函数功能。

先定义一个打印日志的Decorator,可以看到log函数传入一个函数,返回在内部定义的wapper函数。wapper函数调用log传入的函数func,并在调用func函数前打印了函数名

import functools

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

log中返回的wrapper函数的参数(*args,**kw)可以接受任意参数的调用。

Decorator可以借助pyhton@语法放在定义函数的代码处。

@log
def test():
    print('MrtTying')

test()
#输出结果:
#call test():
#MrtTying

这里看代码的写法,decorator相当于执行了test = log(test),只是在原有的test函数本身做了一个代理。如果需要自定义日志部分的分本呢?可以给之前的log函数在套一层,log函数传入自定义文本,decorator传入func参数,具体如下:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('call')
def test():
    print('MrtTying')

而函数的实际调用相当于test = log('call')(test)

到这里已经有两种decorator写法了,而wrapper函数都有一个@functools.wraps(func)decorator。这里分析一波,log最终返回的是wrapper函数,虽然wrapper返回了传入函数的返回值,当外部调用test函数时,实际上使用的是wrapper函数,只是test函数重新指向了wrapper函数。

这里就会出现一个问题,函数是一个对象,拥有__name__等属性,而wrapper函数的属性与传入的func并不相同,所以我们需要将原有函数func的属性传递给wrapper函数,否则有些依赖函数签名的代码会出错。

Pyhton的内置函数functools.wraps可以帮助我们完成函数属性的复制。用法如上两段代码

偏函数

偏函數是对可以传入默认值的函数的一种扩展,可以重新创建一个函数设定这个传入函数的默认值,同样也是可以重新设置的。

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

print(int2('01000000'))
#输出结果:64

这是我们自己的实现方式,重新定义了int2函数来转化2进制数。pythonfunctools.partial可以帮助我们创建一个偏函数

import functools

int2 = functools.partial(int, base=2)

print(int2('01000000'))
#输出结果:64

上述代码中的int2函数,同样可以添加base参数,例如:int2('01000000',base=10)得到的结果将是'01000000'的10进制结果,也就是1000000

创建偏函数时,实际可以接收函数对象、*args和***kwint中只是固定了关键字参数是base

偏函数也就通过固定原函数的部分参数来简化函数调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351