• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

Python函数式编程

2020-01-19

1. 函数式编程

函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程


函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数

2. 第一类对象

第一类对象(英语:First-class object)在计算机科学中指可以在执行期创造并作为参数传递给其他函数或存入一个变数的实体[1]。将一个实体变为第一类对象的过程叫做“物件化”(Reification)

C语言与C++中的函数不是第一类对象,因为在这些语言中函数不能在执行期创造,而必须在设计时全部写好。相比之下,Scheme中的函数是第一类对象,因为可以用lambda语句来创造匿名函数并作为第一类对象来操作

  • 维基百科编者. 第一類物件[G/OL]. 维基百科, 2018(20181204)[2018-12-04]

In Python, functions are the first class objects, which means that –

  • Functions are objects; they can be referenced to, passed to a variable and returned from other functions as well.
  • Functions can be defined inside another function and can also be passed as argument to another function.

3. 闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外


闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量


所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。cell 对象的cell_contents属性就是闭包中的自由变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def adder(x):
def wrapper(y):
return x + y
return wrapper


>>> adder5 = adder(5)
>>> adder5(10)
15
>>> adder.__closure__
>>> adder5.__closure__
(<cell at 0x103075910: int object at 0x7fd251604518>,) # 注意这是一个<type 'tuple'>
>>> adder5.__closure__[0].cell_contents
5

简单来讲,一个闭包就是一个函数, 只不过在函数内部带上了一个额外的变量环境。闭包关键特点就是它会记住自己被定义时的环境。 闭包避免了使用全局变量

  • 局部变量

    闭包返回现场的环境变量,不能在闭包里定义的函数里面再被定义了,而且函数里必须要有调用环境变量的地方,否则就不叫做闭包了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    def f1():
    a = 20
    def f2():
    a = 10 # 重复定义
    return a
    return f2
    f = f1()
    print(f.__closure__)

    None # 可见此时返回了None,不再是闭包了。本质上是认为此时a被认为是一个局部变量,不再是环境变量了!

    --------------------------------------------------------------------------

    # 如果想要环境变量在函数里被改变,可以这样:

    def f1():
    a = 25
    def f2():
    nonlocal a # nonlocal关键字强制让a不再为局部变量,跳到上一级作为了环境变量。
    a = a + 10
    return a
    return f2
    f = f1()
    print(f.__closure__)
    print(f())
    print(f())
    print(f())

    (<cell at 0x000002A5CF348708: int object at 0x0000000065DCECD0>,)
    35
    45
    55
    # 可以看到a的值是可以保存的,这是因为闭包的环境变量具有保存现场的功能,记忆住上次调用的状态,所以可以这样做。

    ---------------------------------------------------------------------------


    def f1():
    a = 20
    def f2():
    return 2 # 里面不再调用a了
    return f2
    f = f1()
    print(f.__closure__)

    None # 可见此时仍然不是闭包

    ---------------------------------------------------------------------------

    def f1():
    a = 20
    def f2():
    s = a+20
    return 2
    return f2
    f = f1()
    print(f.__closure__)

    (<cell at 0x00000294E8568708: int object at 0x0000000065DCEC30>,)
    # 可见就算返回值里不包括a,但是只要调用了,就可以是一个闭包。
  • 全局变量

    从闭包可以看出函数式编程的优点。如果出现需要保存值进行迭代的情况,就不得不定义一个全局变量来保存上一次的值。但是在闭包里不需要使用到全局变量,只需要闭包里定义的环境变量即可记忆上一次的状态,这样就具有了封闭性,否则过多的使用全局变量会使代码变得混乱

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    = 10

    def f1(x):
    a_new = a + x
    a = a_new

    print(f1(5))

    Traceback (most recent call last):
    File "c4.py", line 7, in <module>
    print(f1(5))
    File "c4.py", line 4, in f1
    a_new = a + x
    UnboundLocalError: local variable 'a' referenced before assignment

    # 看起来美滋滋其实报错了。再Python里,如果再定义了a的话,无论是在哪里,在定义的时候系统会默认a是局部变量不再是全局变量了。所以在执行代码的时候,就会出现了找不到局部变量a的情况,因为f1中第一段代码中用到了局部变量a,但是此时还没有定义啊。

    ----------------------------------------------------------------------

    # 可以这么解决:
    a = 10

    def f1(x):
    global a # 定义global关键字强制认为是全局变量
    a_new = a + x
    a = a_new
    return a

4. cell对象

“Cell” objects are used to implement variables referenced by multiple scopes. For each such variable, a cell object is created to store the value; the local variables of each stack frame that references the value contains a reference to the cells from outer scopes which also use that variable. When the value is accessed, the value contained in the cell is used instead of the cell object itself.


这解释了为什么局部变量脱离函数之后,还可以在函数之外被访问的原因的,因为它存储在了闭包的cell_contents中了

5. 高阶函数(Higher-order function)

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. procedural parameters)
  • returns a function as its result.

特点

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

    1
    2
    3
    >>> f = abs
    >>> f
    <built-in function abs>
  • 函数名也是变量

    • 对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数

      1
      2
      3
      4
      5
      >>> abs = 10
      >>> abs(-10)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      TypeError: 'int' object is not callable
  • 函数作为返回值

    • 首先实现一个可变参数的求和

      1
      2
      3
      4
      5
      def calc_sum(*args):
      ax = 0
      for n in args:
      ax = ax + n
      return ax
    • 改变需求,不需要立刻求和,而是在后面的代码中,根据需要再计算

      1
      2
      3
      4
      5
      6
      7
      def lazy_sum(*args):
      def sum():
      ax = 0
      for n in args:
      ax = ax + n
      return ax
      return sum # 返回函数名
      • 当调用lazy_sum()时,返回的并不是求和结果,而是求和函数f(f相当于闭包)

        1
        2
        3
        4
        5
        >>> f = lazy_sum(1, 3, 5, 7, 9)
        >>> f
        <function lazy_sum.<locals>.sum at 0x101c6ed90>
        >>> f()
        25

高阶函数

  • filter

    1
    even = filter(lambda x: x % 2 == 0, integers)
  • map

    1
    2
    3
    4
    5
    6
    >>> map(lambda x: x ** 2, [1, 2, 3, 4, 5]) 
    [1, 4, 9, 16, 25]

    # 提供了两个列表,对相同位置的列表数据进行相加
    >>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
    [3, 7, 11, 15, 19]
  • reduce

    1
    2
    3
    from functools import reduceintegers = range(1, 10)  
    reduce(lambda x, y: x * y, integers)
    >>> 362880
  • sum

  • any&all

6. 匿名函数(Anonymous Functions)

In Python, anonymous function means that a function is without a name. As we already know that def keyword is used to define the normal functions and the lambda keyword is used to create anonymous functions


Syntax

1
lambda arguments: expression

difference between a normal def defined function and lambda function

1
2
3
4
def cube(y): 
return y*y*y;

g = lambda x: x*x*x

6.1. 示例

1
2
3
4
5
6
>>> add = lambda x, y: x + y
>>> add(2,3)
5
>>> add('hello', 'world')
'helloworld'
>>>

6.2. 匿名函数捕获变量值

  • 需要注意:lambda表达式中的参数是一个自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    >>> funcs = [lambda x: x+n for n in range(5)]
    >>> for f in funcs:
    ... print(f(0))
    ...
    4
    4
    4
    4
    4

    >>> funcs = [lambda x, n=n: x+n for n in range(5)]
    >>> for f in funcs:
    ... print(f(0))
    ...
    0
    1
    2
    3
    4

    • 通过使用函数默认值参数形式,lambda函数在定义时就能绑定到值

7. 装饰器(Decorators)

Decorators allow us to wrap another function in order to extend the behavior of wrapped function, without permanently modifying it

  • 在代码运行期间动态增加功能,又不希望修改原函数定义

Syntax

1
2
3
4
5
6
7
8
9
10
@gfg_decorator
def hello_decorator():
print("Gfg")

'''Above code is equivalent to -

def hello_decorator():
print("Gfg")

hello_decorator = gfg_decorator(hello_decorator)'''

gfg_decorator is a callable function, will add some code on the top of some another callable function –hello_decorator function and return the wrapper function


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

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

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


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

'''
same as
now=log(now)
now()
'''

Python中的装饰器是通过利用了函数特性的闭包实现的

7.1. 带参数的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def outer(outer_args):
def middle(func):
def wrapper(*args,**kwargs):
print("before func")
print("use args: "+outer_args)
wrapper_result=func(*args,**kwargs)
print("after func")
return wrapper_result
return wrapper
return middle

@outer("hello")
def foo(a,b):
return a+b

>>> res=foo(3,3)
before func
use args: hello
after func
>>> res
6

# 相当于 python method chaining
>>> foo=outer("hello")(foo)
  • 有点像闭包嵌套

8. 偏函数

Partial functions allow us to fix a certain number of arguments of a function and generate a new function.

1
2
3
4
5
6
7
8
9
10
11
12
from functools import partial 

# A normal function
def f(a, b, c, x):
return 1000*a + 100*b + 10*c + x

# A partial function that calls f with
# a as 3, b as 1 and c as 4.
g = partial(f, 3, 1, 4)

# Calling g()
print(g(5))

也可指定参数

1
2
3
4
5
6
# A normal function 
def add(a, b, c):
return 100 * a + 10 * b + c

# A partial function with b = 1 and c = 2
add_part = partial(add, c = 2, b = 1)

Partial functions can be used to derive specialized functions from general functions and therefore help us to reuse our code.

9. 参考资料

  • 函数式编程 - 廖雪峰的官方网站
  • 一步一步教你认识Python闭包 - FooFish-Python之禅
  • Cell Objects — Python 3.8.1 documentation
  • Decorators in Python - GeeksforGeeks
  • Python进阶细节 - 个人文章 - SegmentFault 思否
  • Program Language
  • Python
  • Advanced
python源码分析-1
python垃圾回收
  1. 1. 1. 函数式编程
  2. 2. 2. 第一类对象
  3. 3. 3. 闭包
    1. 3.1. 4. cell对象
    2. 3.2. 5. 高阶函数(Higher-order function)
    3. 3.3. 6. 匿名函数(Anonymous Functions)
      1. 3.3.1. 6.1. 示例
      2. 3.3.2. 6.2. 匿名函数捕获变量值
    4. 3.4. 7. 装饰器(Decorators)
      1. 3.4.1. 7.1. 带参数的装饰器
    5. 3.5. 8. 偏函数
  4. 4. 9. 参考资料
© 2024 何决云 载入天数...