0
点赞
收藏
分享

微信扫一扫

Python 函数式编程(一)

搬砖的小木匠 2021-09-28 阅读 39

Python 中一个完整函数的样子:

@装饰器1
@装饰器2
...
def 函数名(位置形参, *元组形参, 命名关键字形参, **字典形参):
    """文档字符串"""
    语句块

我们发现,函数的最上面是 @装饰器,装饰器的本质其实是一个闭包函数。因此在开始介绍装饰器前,我们有必要学习一下闭包的概念。

一. 闭包 closure

闭包是指引用了此函数外部嵌套函数作用域变量的函数。闭包必须满足下面 3 个条件:

  • 必须有内嵌函数;
  • 内嵌函数必须引用外部函数中的变量;
  • 外部函数返回值必须是内嵌函数

下面,我们就以一个工作场景为例,一步步讲解闭包的由来。

场景假设:假设如下 decorated 函数,已经测试完毕并 release:

def decorated(x):
    print('%d ** %d = %d' % (x, x, x**x))

运行结果:

>> decorated(2)
2 ** 2 = 4
>> decorated(3)
3 ** 3 = 27

现在提出新的需求:想在每次调用 decorated 函数前,均输出一次当前的时间,但前提是不允许修改已经 release 的 decorated 函数。

首先,想到的就是,重新定义一个新的函数 decorate ,并在该函数中调用 decorated

import time
 
def decorate(x):
    print("%d:%d:%d" % time.localtime()[3:6])
    decorated(x)

运行结果:

>> decorate(2)
21:23:8
2 ** 2 = 4
>> decorate(3)
21:23:13
3 ** 3 = 27

依然存在的问题:如果原程序中多处调用了 decorated 函数,则需把每一处都修改为 decorate,工作量是一方面,如果你的 API 已经发布给外界的开发者使用,那将是件非常糟糕的事情。

继续尝试:在程序调用时无需修改原函数名。

在 Python 中,函数作为一等公民,因此可以像一个普通变量一样,作为参数传递给另一个函数,也可以作为函数的返回值:

import time
 
def decorate(func):
    print("%d:%d:%d" % time.localtime()[3:6])
    return func

这样,我们就可以在 decorated 函数被调用前,将其作为参数传递给 decorate 函数;并将 decorate 函数的返回值绑定到 decorated 变量:

>> decorated = decorate(decorated)
21:34:32
>> decorated(2)
2 ** 2 = 4
>> decorated(3)
3 ** 3 = 27

此方法可以实现在程序调用时,无需修函数名。

但仍存在一个非常严重的错误decorated 函数本身并未做任何改变,因此运行结果中,decorate 函数只在 decorated = decorate(decorated) 赋值时打印一次时间。

闭包登场:为了解决上述需求和问题,下面请出闭包:

import time
 
def decorate(func):
    def inner(x):
        print("%d:%d:%d" % time.localtime()[3:6])
        func(x)
    return inner

上述代码中的 decorate 就是一个闭包了:decorate 函数内嵌了一个 inner 函数,且返回值为 inner 函数;并且 inner 函数访问了enclosing 作用域变量 func,形成了一个闭包。

运行结果:

>> decorated = decorate(decorated)
>> decorated(2)
21:45:49
2 ** 2 = 4
>> decorated(3)
21:45:51
3 ** 3 = 27

由于执行 decorated = decorate(decorated) 语句之后,decorated 引用的就不再是原来的函数,而是闭包 decorated 的返回值 inner 函数。因此,后续调用 decorated 函数时,其实执行的都是 inner 函数。

目标完成:至此,我们使用闭包实现了在不修改函数内部及调用程序的情况下,改变了原函数的功能。

二. 装饰器

装饰器其实其实就是闭包的一个语法糖了,我们可以不用写诸如 decorated = decorate(decorated) 的赋值语句。只需要在 decorated 函数的最上方添加 @decorate 语句即可:

import time
 
def decorate(func):
    def inner(x):
        print("%d:%d:%d" % time.localtime()[3:6])
        func(x)
    return inner
 
@decorate
def decorated(x):
    print('x ** x = %d' % x ** x)

运行结果:

>> decorated(2)
22:0:18
x ** x = 4
>> decorated(3)
22:0:23
x ** x = 27

装饰器的定义:装饰器是一个函数,主要作用是用来包装另一个函数和类。即在不改变原函数名(或类名)的情况下,改变被包装对象的行为。

什么是函数装饰器:函数装饰器是指装饰器是一个函数,传入的是一个函数,返回的也是一个函数。函数一旦被装饰,它绑定的将是装饰器函数的返回值。

语法结构

def 装饰器函数名(函数名作为参数):
        语句块
        return 函数对象

    @装饰器函数名
    def 函数名(形参列表):
        语句块

使用说明

def decorator_fun(fn):
    def fx():
        print("[No1] 在函数 original_fun 调用之前,新增的处理程序!")
        fn()
        print("[No3] 在函数 original_fun 调用之后,新增的处理程序!")
    return fx

@decorator_fun
def original_fun():
    print("[No2] 函数 {} 正在被调用!".format(__name__))

运行结果:

>> original_fun()
[No1] 在函数 original_fun 调用之前,新增的处理程序!
[No2] 函数 __main__ 正在被调用!
[No3] 在函数 original_fun 调用之后,新增的处理程序!

关乎函数的几点说明

  • 函数本身可以赋值给变量,赋值后变量绑定函数;
  • 允许将函数本身作为参数,传递给另一个函数;
  • 允许函数返回一个函数;

我们应使用上述的函数式编程思想,而不是去修改函数内部,在程序中修改模块内部是大忌。

三. 形参

函数的4种形参定义方式为:

  • 位置形参
  • 星号元组形参
  • 命名关键字形参
  • 双星号字典形参

位置形参:传入位置实参。

def 函数名(形参名1, 形参名2, ...):
    语句块

星号元组形参:允许传入任意个位置实参。

def 函数名(*元组形参名):
    语句块

命名关键字形参:强制所有的传参都必须使用关键字传参。

def 函数名(*, 命名关键字形参)
    语句块

def 函数名(*args,命名关键字形参)
    语句块

双星号字典形参:允许传入任意个关键字实参。

def 函数名(**字典形参名):
    语句块

以上就是 4 中形参的语法形式了,其中需要注意形参定义的顺序:位置形参、星号元组形参、命名关键字形参、双星号字典形参。

四. 文档字符串

函数可以包含自己的文档字符串,语法格式如下:

def 函数名(形参列表)
    """函数的文档字符串"""
    函数语句块

示例:

def func():
    '''此函数用来打招呼...
    这是函数的文档字符串
    '''
    pass

可以在交互模式下输入如下命令,查看函数的文档字符串:

>> help(func)
Help on function func in module __main__:

func()
    此函数用来打招呼...
    这是函数的文档字符串

函数的 __doc__ 属性也记录了函数的文档字符串:

>> print(func.__doc__)
此函数用来打招呼...
    这是函数的文档字符串

此外,函数的 __name__ 属性则记录了函数的名称:

>> print(func.__name__)
func

五. 实参

Python 中的函数有4种实参传递方式:

  • 位置传参
  • 序列传参(字符串,列表,元组)
  • 关键字传参
  • 字典关键字传参

位置传参:实参与形参的对应关系按位置来依次对应。

def func(a, b, c):
    pass
func(1, 2, 3)

序列传参:函数在调用过程中用 * 将序列拆解后按位置进行传递的传参方式。

def func(a, b, c):
    pass

s = "ABC"
L = [4, 5, 6]
t = (1.1, 1.2, 1.3)

func(*s)
func(*L)
func(*t)

关键字传参:按着形参的名称给形参赋值;实参和形参按名称进行匹配。

字典关键字传参:将字典用 ** 拆解后进行关键字传参的传参方式。

def func1(a, b, *, c, d):
    pass

def func2(a, b, *args, c, d):
    pass

func1(1, 2, c=300, d=400)
func1(1, 2, **{'d':400, 'c':300})
func2(1, 2, 3, 4, c=300, d=400)
func2(1, 2, 3, 4, **{'d':400, 'c':300})
举报

相关推荐

0 条评论