python函数式编程学习笔记

python函数式编程学习笔记

函数的不定长参数

当函数的参数不确定时,可以使用*args**kwargs
*args是可变的positional arguments列表(表示任何多个无名参数,它是一个tuple),**kwargs是可变的keyword arguments列表(表示关键字参数,它是一个dict)。并且,*args必须位于**kwargs之前,因为positional arguments必须位于keyword arguments之前。

def print_everything(*args):
    for count,thing in enumerate(args):
        print("%d. %s" %(count, thing))

print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage
def table_things(**kwargs):
    for name,value in kwargs.items():
        print(name, "=", value)

table_things(apple = 'fruit', cabbage = 'vegetable')
apple = fruit
cabbage = vegetable
def foo(*args,**kwargs):
    print('args=',args)
    print('kwargs=',kwargs)
    print(' ')
    
if __name__=='__main__':
    foo(1,2,3,4)
    foo(a=1,b=2,c=3)
    foo(1,2,3,4,a=1,b=2,c=3)
    foo('a',1,None,a=1,b='2',c=3)
args= (1, 2, 3, 4)
kwargs= {}
 
args= ()
kwargs= {'a': 1, 'b': 2, 'c': 3}
 
args= (1, 2, 3, 4)
kwargs= {'a': 1, 'b': 2, 'c': 3}
 
args= ('a', 1, None)
kwargs= {'a': 1, 'b': '2', 'c': 3}
 

python可以自动将参数解析后再与调用的函数匹配。

def print_three_things(a, b, c):
    print("a =", a, "& b =", b, "& c =", c)

mylist = ['aardvark', 'baboon', 'cat']
print_three_things(*mylist)
a = aardvark & b = baboon & c = cat
# 还有一个很漂亮的用法,就是创建字典:
def kw_dict(**kwargs):
    return kwargs
    
print(kw_dict(a=1,b=2,c=3))
{'a': 1, 'b': 2, 'c': 3}

递归(程序调用自身的编程技巧)

递归通常把一个大型复杂的问题,转化为一个与原问题相似的,且规模较小的问题来求解。
递归策略只需少量的程序就可以描述出解题的所需的多次重复计算,大大减少了程序的代码量。

递归函数一般包括:

  • 当函数直接结束是时有基本的返回值。
  • 递归调用函数自身:包括一个或多个对自身函数的调用。
def Factorial(a):
    result=a
    for i in range(1,a):
        result*=i
    return result
# 调用函数
print(Factorial(10))
3628800
 #递归函数
def Recursion(a):
    if a==1:
        return 1
    else:
        return a*Recursion(a-1)
Recursion(10)
3628800

函数式编程(Functional programming)

参考资料:

什么是函数式编程?

函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。
在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purely functional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。
可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。

为什么使用函数式编程?

函数式的风格通常被认为有如下优点:

  • 逻辑可证:
    这是一个学术上的优点:没有边界效应使得更容易从逻辑上证明程序是正确的(而不是通过测试)。
  • 模块化:
    函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块。小的函数更易于阅读和检查错误。
  • 组件化:
    小的函数更容易加以组合形成新的功能。
  • 易于调试:
    细化的、定义清晰的函数使得调试更加简单。当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方。
  • 易于测试:
    不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易。
  • 更高的生产率:
    函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护。

如何辨认函数式风格?

支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:

  • 函数是一等公民:
    函数能作为参数传递,或者是作为返回值返回。这个特性使得模板方法模式非常易于编写,这也促使了这个模式被更频繁地使用。
    以一个简单的集合排序为例,假设lst是一个数集,并拥有一个排序方法sort需要将如何确定顺序作为参数。
    如果函数不能作为参数,那么lst的sort方法只能接受普通对象作为参数。这样一来我们需要首先定义一个接口,然后定义一个实现该接口的类,最后将该类的一个实例传给sort方法,由sort调用这个实例的compare方法,就像这样:

函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。
理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。

1 范畴的概念

"范畴就是使用箭头连接的物体。"彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。
箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

2 数学模型

既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。

  • 所有成员是一个集合
  • 变形关系是函数

也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"
理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。

3 范畴与容器
我们可以把"范畴"想象成是一个容器,里面包含两样东西。

  • 值(value)
  • 值的变形关系,也就是函数。
本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

函数的合成与柯里化

函数式编程有两个最基本的运算:合成和柯里化。

  • 函数的合成:

    如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
    函数合成就是将那些管道(pipe)连了起来,让数据一口气从多个管道中穿过。

def f(x):
    def g():
        return x+1
    return g()*5

f(3)
20
  • 柯里化:

\(f(x)\)\(g(x)\)合成为\(f(g(x))\),有一个隐藏的前提,就是\(f\)\(g\)都只能接受一个参数。如果可以接受多个参数,比如\(f(x, y)\)\(g(a, b, c)\),函数合成就非常麻烦。
这时就需要函数柯里化了。
所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

函子

函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

map & filter & reduce

filter用于过滤列表对象,返回真值的原像,类似于逆映射

def f(x):
    return x**2

def even(x):
    return x%2==0

list(map(even,range(10)))
[True, False, True, False, True, False, True, False, True, False]
list(filter(even,range(15)))
[0, 2, 4, 6, 8, 10, 12, 14]
list(filter(f,range(10)))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
list(map(f,range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
list(map(lambda x:x**2,range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

函数柯里化

一个函数有多个参数,我们希望能固定其中几个参数的值。

from functools import partial
def foo(a,b,c):
    return a+b+c

foo2 = partial(foo, b=2)
foo2(a=1, c=3)
6

看上去这跟提供参数默认值的情况类似。但默认值只能固定
为单个值,而柯里化能通过泛化出很多个函数,每个函数用
不同的固定值,来看一下 应用场景

from functools import partial
bin2dec = partial(int, base=2)
hex2dec = partial(int, base=16)

原int方法的定义为:int( x[, base]),base参数的默认为10
经过柯里化之后,可以用如下方式调用:

int('15') #=>15  using default base 10
print(bin2dec('01011')) #=>11
print(hex2dec('67')) #=>103
11
103

反柯里化(Uncurrying)

顾名思义,柯里化的逆过程。
将多个只含单个参数的函数模拟成一个多参数函数。
你可以像这样调用:foo(1)(4)或(foo(1))(4),都能得到正确的结果5

def foo(a):
    def bar(b):
        return a+b
    return bar
foo(1)(4)
5

map()函数是python内置的高阶函数,对传入的list的每一个元素进行映射,返回一个新的映射之后的list

map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

例如,对于list [1, 2, 3, 4, 5, 6, 7, 8, 9]

如果希望把list的每个元素都作平方,就可以用map()函数:
因此,我们只需要传入函数f(x)=x*x,就可以利用map()函数完成这个计算:

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]
注意:map()函数不改变原有的 list,而是返回一个新的 list。
利用map()函数,可以把一个 list 转换为另一个 list,只需要传入转换函数。
由于list包含的元素可以是任何类型,因此,map() 不仅仅可以处理只包含数值的 list,事实上它可以处理包含任意类型的 list,只要传入的函数f可以处理这种数据类型。
任务
假设用户输入的英文名字不规范,没有按照首字母大写,后续字母小写的规则,请利用map()函数,把一个list(包含若干不规范的英文名字)变成一个包含规范英文名字的list:
输入:['adam', 'LISA', 'barT']
输出:['Adam', 'Lisa', 'Bart']
list(map( lambda x, y: x * y, [1, 2, 3], [4, 5, 6]))
[4, 10, 18]
def format_name(s):
    s1=s[0:1].upper()+s[1:].lower();
    return s1;

list(map(format_name, ['adam', 'LISA', 'barT']))
['Adam', 'Lisa', 'Bart']
from functools import reduce
list(map( lambda x, y: x * y, [1, 2, 3], [4, 5, 6] ))
[4, 10, 18]
list(map( lambda x, y: ( x * y, x + y), [1, 2, 3], [4, 5, 6] ))
[(4, 5), (10, 7), (18, 9)]
m = 2
n = 5
reduce( lambda x, y: x * y, range( 1, n + 1 ), m )
240
list(zip( [1, 2, 3], [4, 5, 6] ))
[(1, 4), (2, 5), (3, 6)]

函数修饰器(Decorator)

Python中的函数是对象,故函数可以被当做变量使用。

参考资料:

作用:

对已有的函数添加一些小功能,却又不希望对函数内容有太多的刚性的修改。

  • 将需要添加功能的函数像普通对象一样作为参数传入修饰器在中;
  • 将函数作为修饰器的返回值返回。

修饰器的本质: Python解释器在发现函数调用修饰器后,将其传入修饰器中,然后用返回的函数对象将自身完全替换。

一个函数可以被多个修饰器嵌套修饰:

@deco3
@deco2
@deco1
def df():
    pass

等同于\(f=deco3(deco2(deco1(f)))\)

示例

添加水印Hello World_

def deco(func):          #修饰器deco
    def wrappedFunc():          #内嵌函数wrappedFunc(所有对于传入函数的修饰逻辑都将在此内嵌函数中实现。)
        return 'Hello World_'+func()
    return wrappedFunc

# 在程序中若有函数需要修饰器修饰,只需在函数定义前使用“`@+修饰器名`”即可使用此修饰器。
@deco
def f():    # 调用修饰器
    return 'I am f'
def g():     # 没有调用修饰器
    return 'I am g'

print(f())
print(g())
Hello World_I am f
I am g
def deco(f):
    def g():
        return [f()[i] for i in range(5)]
    return g
@deco
def h():
    return [1,2,3,4,56,7,'75s']
print(h())
[1, 2, 3, 4, 56]

带参修饰函数

在deco函数和wrappedFunc函数之间添加一个_deco函数来处理deco函数传入的参数prefix,修饰器不会立即将待修饰的函数作为参数传入完成修饰,而是先做了一个预处理,返回了一个_deco函数,而这个_deco函数才是真正被f函数调用的修饰器。

def deco(prefix):
    def _deco(func):
        def wrappedFunc():
            return prefix+func()
        return wrappedFunc
    return _deco

@deco('second_')
@deco('first_')
def f():
    return 'I am f'

print(f())  # 等同于  f=deco('second_')(deco('fist_')(f))
second_first_I am f
探寻有趣之事!