Python入门基础(9)——函数式编程入门

1、高阶函数

说白了就是复合函数(比如f(g(x+y))),再说高阶函数之前,你首先要明白,函数是可以作为一个变量进行赋值的,在Python里面,所有变量和函数都是一个object,都是一个类型,下面看实例代码吧:

abs(-10)  #这是一个函数,功能是取绝对值

f = abs  #将函数赋值给变量f,则f以后就表示绝对值函数了
print(f(-2))    #输出结果很显然是2

def add(x,y,f):      #变量可以作为参数,函数也可以作为参数
    return f(x)+f(y)

result = add(-5,5,abs)    #调用上面的函数,过程中将abs赋值给f
print(result)       #最后输出结果为10

这个过程中,你应该时刻明白,Python中不管你是什么,你都是一个object,都可以作为参数进行传递,多个函数相互组合在一起,自然就形成了高阶函数

2、匿名函数

python 使用 lambda 来创建匿名函数。

  • lambda只是一个表达式,函数体比def简单很多。

  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。

  • lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。

  • 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

  • 最重要的一点,Lambda表达式可以体现你的逼格。华尔街和伦敦银行高街最逼的一群人都是自诩用且只用函数式编程的。什么是函数式编程?就是类似于全篇程序都用python中lambda这样的一行代码来解决问题。为什么他们逼?因为数学家们学编程的时候,脑子还在数学公式这条线上;他们不会写面对对象编程,只会死想出一条条公式来解决问题;其实这是智商堪忧的体现;但是因为投行基金们很倾向于聘用这群数学家转型的半吊子程序员;他们的使用习惯于是成了圈内高逼的体现;恩,葡萄就是这么酸。:P。

(1)语法

Lambda函数的语法只包含一个语句,如下:

lambda [arg1 [,arg2,.....argn]]:expression

比如,我写个相加函数:

sum = lambda arg1, arg2: arg1 + arg2

sum(10, 20)

楼上这个,实际上就是一个函数:

def sum(arg1, arg2):
    return arg1 + arg2

除了Lambda本身,Python还提供了其他几个辅助工具,让你的函数式代码块更加牛逼:

reduce

Python中的reduce内建函数是一个二元操作函数,他用来将一个数据集合(列表,元组等)中的所有数据进行如下操作:传给reduce中的函数func() (必须是一个二元操作函数)先对集合中的第1,2个数据进行操作,得到的结果再与第三个数据用func()函数运算,最后得到一个结果。

顾名思义,reduce就是要把一个list给缩成一个值。所以你必须用二元操作函数。

from functools import reduce

l = [1,2,3,4,5]
print(reduce(lambda x,y: x+y, l))
# 这里代表着,把list中的值,一个个放进lamda的x,y中

# 如果你给出一个初始值,可以放在list后面
print(reduce(lambda x,y: x+y, l, 10))
# 这样,x开始的时候被赋值为10,然后依次
15
25

map

map函数应用于每一个可迭代的项,返回的是一个结果list。如果有其他的可迭代参数传进来,map函数则会把每一个参数都以相应的处理函数进行迭代处理。map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。

格式:map(func, seq1[, seq2...] )

Python函数式编程中的map()函数是将func作用于seq中的每一个元素,并用一个列表给出返回值。

map可以使用任何的lambda函数操作,本质上是把原有的list根据lambda法则变成另外一个list

l = [1,2,3]
new_list = list(map(lambda i: i+1, l))
print(new_list)
# Py3里,外面需要套个list:
# 这是为了让里面的值给显示出来,要不然你会得到这是个map函数
# 而不是里面的值。
# Py2的童鞋不虚

# 我们也可以把两个数组搞成一个单独的数组
l2 = [4,5,6]
new_list = list(map(lambda x,y: x+y, l, l2))
print(new_list)
[2, 3, 4]
[5, 7, 9]

filter

filter()函数可以对序列做过滤处理,就是说可以使用一个自定的函数过滤一个序列,把序列的每一项传到自定义的过滤函数里处理,并返回结果做过滤。最终一次性返回过滤后的结果。 和map()类似,filter()也接收一个函数和一个序列。和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

语法

filter(func, seq)

l = [100, 20, 24, 50, 110]
new = list(filter(lambda x: x<50, l))
# 同理,py3得套个list来转化成list函数,便于打印出来
print(new)
[20, 24]

熟练运用以上三个玩意儿,你就可以一行写出几乎所有的复杂计算了。

3、装饰器

装饰器就是函数的『包装』:

我们来看一个代码:

def hello(fn):
    def wrapper():
        print("hello, %s" % fn.__name__)
        fn()
        print("goodby, %s" % fn.__name__)
    return wrapper
 
@hello
def foo():
    print("i am foo")
 
foo()
hello, foo
i am foo
goodby, foo

你可以看到如下的东西:

1)函数foo前面有个@hello的“注解”,hello就是我们前面定义的函数hello

2)在hello函数中,其需要一个fn的参数(这就用来做回调的函数)

3)hello函数中返回了一个inner函数wrapper,这个wrapper函数回调了传进来的fn,并在回调前后加了两条语句。

所以,本质上来讲,用@decorator来装饰某个函数时,其实是做了下面这件事儿:

@decorator
def func():
    pass

变成 =====》

func = decorator(func)

再简单点说,就是把一个函数传到另外一个函数中,再调回给自己。

所以:

hello(foo)返回了wrapper()函数,所以,foo其实变成了wrapper的一个变量,而后面的foo()执行其实变成了wrapper()

同理,我们也可以搞多个decorator:

@decorator_one
@decorator_two
def func():
    pass

相当于:

func = decorator_one(decorator_two(func))

你还可以给这个decorator带个参数:

@decorator(arg1, arg2)
def func():
    pass

相当于:

func = decorator(arg1,arg2)(func)

好了,讲这么多比较复杂,我们来看个网页编程的case:

def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                     if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    return real_decorator
 
@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"
 
print(hello())
<b class='bold_css'><i class='italic_css'>hello world</i></b>

在上面这个例子中,我们可以看到:makeHtmlTag有两个参数。所以,为了让 hello = makeHtmlTag(arg1, arg2)(hello) 成功,makeHtmlTag 必需返回一个decorator(这就是为什么我们在makeHtmlTag中加入了real_decorator()的原因),这样一来,我们就可以进入到 decorator 的逻辑中去了—— decorator得返回一个wrapper,wrapper里回调hello。

这里插一个知识,我们看到parameters里面有个`(*args, **kwargds)`,指的是:
一个星星,指的是这里可以随便放多少个参数,内部提及的时候,当做一个list看。
两个星星指的也是随便多少个参数,但是这里可以带上参数的名字,比如(x='1', y='2'),内部提及的时候,当做一个dict看。

Decorator这个东西,也可以写成class样式:

class myDecorator(object):
 
    def __init__(self, fn):
        print("inside myDecorator.__init__()")
        self.fn = fn
 
    def __call__(self):
        self.fn()
        print("inside myDecorator.__call__()")
 
@myDecorator
def aFunction():
    print("inside aFunction()")
 
print("Finished decorating aFunction()")
 
aFunction()
inside myDecorator.__init__()
Finished decorating aFunction()
inside aFunction()
inside myDecorator.__call__()

这个class样式的看起来比函数样式看着清楚点儿,这样我们再把刚刚的网页编程那段改一下,就得到:

class makeHtmlTagClass(object):
 
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class !="" else ""
 
    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped
 
@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)
 
print(hello("Baby"))
<b class='bold_css'><i class='italic_css'>Hello, Baby</i></b>

装饰器的副作用:

因为decorator的因素,我们原本的函数其实已经变成了一个叫wrapper函数。

比如,你再调用__name__的时候,他会告诉你,这是 wrapper, 而不是 foo 或者 hello。

当然,虽然功能效果不变,但是有些处女座的童鞋会觉得很不爽。

所以,Python的functool包中提供了一个叫wrap的decorator来消除这样的副作用:

from functools import wraps
def hello(fn):
    @wraps(fn)
    def wrapper():
        print("hello, %s" % fn.__name__)
        fn()
        print("goodby, %s" % fn.__name__)
    return wrapper
 
@hello
def foo():
    '''foo help doc'''
    print("i am foo")
    pass
 
foo()
print(foo.__name__)
print(foo.__doc__)
hello, foo
i am foo
goodby, foo
foo
foo help doc

来个经典例子:

斐波那契额数列递归法:

from functools import wraps
def memo(fn):
    cache = {}
    miss = object()
 
    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
            result = fn(*args)
            cache[args] = result
        return result
 
    return wrapper
 
@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。

而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

4、偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

偏函数又可以翻译成部分函数,大概意思就是说,只设置一部分参数。

举个例子,我们知道int()可以把字符串变成十进制数字:

int('12345')
12345

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


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

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

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

这样,我们转换二进制就非常方便了:

int2('1000000')
64

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

import functools

int2 = functools.partial(int, base=2)
int2('1000000')
64

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

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

int2('1000000', base=10)
1000000

最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:

int2 = functools.partial(int, base=2)
int2('10010')
18

实际上固定了int()函数的关键字参数base。

同理:我们刚刚说的**可以当成一个dict带入

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

继续同理,我们可以用*把一个list塞进来

max2 = functools.partial(max, 10)

max2(5, 6, 7)
10

相当于:

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

注意,10在最左边。

5、实战

今天的课程实践会结合上节课给你们的代码内容。

上节课我们讲了如果写个Dataset类来帮我们下载和处理数据。

这节课我们学习了文本的读入,那我们就来做做更加复杂的内容:

本节课的压缩包里有一个数据文件,是我们今天实战的数据。

这是美国亚利桑那州Pima印第安女人患有糖尿病状况的数据集(因为他们的得病率很高)。

这个数据是一个txt文件(其实是个csv),每一行就是一个数据条,长这样:

6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

其中,前面N-1个数据,分别是一些身体的指标,比如:血压,血糖,身高,是否怀孕等等。 最后第N个数据点是记录她是否患有糖尿病,它只有0或者1两种可能。这也就是我们说的数据标签。

所以,这里我们脑海中应该浮现的数据结构如下:

x = [
        [6,148,72,35,0,33.6,0.627,50],
        [1,85,66,29,0,26.6,0.351,31],
        [8,183,64,0,0,23.3,0.672,32],
        ...
    ]

y = [1,0,1,0,1,1,1,0,...]

所以,这是一个二元分类问题。

参照上一堂课的内容,我们要做如下的修改:

  1. Dataset这个类中的download_data函数要被修改,改成我们从外部读入数据的过程。并且要把我们的x和y分开储存,并返回。

  2. 我们这次依旧是以0.7的比率分开训练集和测试集。当我们得到区分开的 x_train, x_test, y_train, y_test以后,我们把这些个数据分别用Json或者Pickle的方法序列化在我们文件夹内。然后我们新建一个程序,从中反序列化我们处理好的数据。并进行之后的Machine Learning过程。

  3. 当我们把一个model训练好以后,我们代入全部的x_test数据,并得到我们预测出来的分类值y_preds。我们把这个值(list)与我们的y_test相互比较,用一些正确率统计的方法计算我们model的准确率,并用lambda函数来实现

预测数据的直接准确率怎么计算?

准确率 = (对的数据数 / 全部数据数) * 100%

高级一点,

我们可以再实现一点其他准确率计算方式:

AUC,MSE,...

详情可见附录的cheat sheet,或者自行百度。

OK!

自己动手尝试一下吧!


好吧,这一节写的有点偷懒(主要是暂时用不到,以后用到可能会再整理一下),2小节以后直接粘贴复制的,详细可以参看:资源整理





猜你喜欢

转载自blog.csdn.net/yuangan1529/article/details/80756068
今日推荐