函数基础#

一个函数就是将一些语句集合在一起的部件,它们能够不止一次地在程序中运行。函数的主要作用:

  • 最大化的代码重用和最小化代码冗余

  • 流程的分解

一般地,函数讲的流程是:告诉你怎样去做某事,而不是让你使用它去做某事。

Python 中的 def 语句实际上是一个可执行的语句:当它运行时,它会创建一个新的函数对象并将其赋值给一个变量名。因为它是一个语句,一个 def 可以出现任一语句可以出现的地方——甚至是嵌套在其他的语句中。例如,下面的使用也是合法的:

test = ...
if test:
    def func():
        ...
else:
    def func():
        ...
...
func()

def 在运行时才进行评估,而在 def 之中的代码在函数调用后才会评估。

Python 中的多态#

下面以一个 times() 函数为例来说明:

def times(x, y):
    return x * y
times(2, 5)

结果:

10
times(3.14, 4)

结果:

12.56
times('Ni', 4)

结果:

'NiNiNiNi'

从上面我们可以看出:times 函数中表达式 x*y 的意义完全取决于 xy 的对象类型。这种依赖类型的行为称为多态。任何支持函数所预期的接口 (函数所执行的一组方法和表达式运算符) 都能够被函数所支持。

作用域#

当你在一个程序中使用变量名时,Python 创建、改变或查找变量名都是在所谓的命名空间 (作用域,一个保存变量名的地方) 中进行的。也就是说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。包括作用域的定义在内,所有变量名都是在 Python 赋值时产生的。

  • 内嵌的模块是全局作用域

  • 全局作用域的作用范围仅限于单个文件

  • 每次对函数的调用都创建了一个新的本地作用域

  • 赋值的变量名除非声明为 global 或者 nonlocal,否则均为本地变量

  • 所有其他的变量名都可以归纳为本地、全局或者内置的

  • 对函数的每次调用都会创建一个新的本地作用域

对对象的就地更改(in-place changes)不将变量归类为局部变量;只有实际的名称分配。例如, 如果将名称 L 分配给模块顶层的列表, 函数中的语句 L = X 会将 L 作为本地分类, 但 L.append(X) 不会。在后一种情况下, 我们正在更改 L 引用的 list 对象, 而不是 L 本身在全局范围中像往常一样被发现, 并且 Python 在不需要全局 (或非本地) 声明的情况下愉快地修改它。与往常一样, 它有助于保持名称和对象之间的区分清楚: 更改对象不是对名称的赋值。

Name Resolution: The LEGB Rule#

变量名解析:LEGB 原则

  • Name assignments create or change local names by default.

  • Name references search at most four scopes: local, then enclosing functions (if any), then global, then built-in.

  • Names declared in global and nonlocal statements map assigned names to enclosing module and function scopes, respectively.

工厂函数#

闭合 (closure) 或者工厂函数:一个能够记住嵌套作用域的变量值的函数,尽管那个作用域或许已经不存在了。

例如,工厂函数有时用于需要及时生成事件处理、实时对不同情况进行反馈的程序中:

def maker(N):
    def action(X):
        return X ** N
    return action
f = maker(2)  # pass 2 to N
f
<function __main__.maker.<locals>.action(X)>
f(3)
9
f(4)
16

函数的不定长参数#

当函数的参数不确定时,可以使用*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}

函数设计原则#

  • 耦合性

    • 对于输入使用参数并且对于输出使用 return 语句:一般地,你需要力求让函数独立于它外部的东西。参数和 return 语句通常就是隔离对代码中少数醒目位置的外部依赖关系的最好方法。

    • 只有在真正必要的情况下使用全局变量:全局变量可能会引发依赖关系和计时问题,导致程序调试和修改的困难。

    • 不要改变可变类型的参数,除非调用者需要这样做:这种耦合性会导致一个函数过于特殊和不友好。

  • 聚合性

    • 每一个函数都应该有一个单一的、统一的目标:在设计完美的情况下,每一个函数都应该做一件事:这件事可以用一个简单说明句来总结。(这样做可以提高代码的开发效率)

  • 大小

    • 每一个函数都应该相对较小:保持简单,提高代码的可读性和扩展性。

  • 耦合

    • 避免直接改变在另一个模块文件中的变量:提高代码的可读性和扩展性。

简言之,我们应该竭力使函数和其他编程组件中的外部依赖性最小化。函数的自我包含性越好,它就越容易被理解、复用和修改。

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

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

递归函数一般包括:

  • 当函数直接结束是时有基本的返回值。

  • 递归调用函数自身:包括一个或多个对自身函数的调用。

\(n!\)#

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

求序列的和#

def seq_sum(L):
    if not L:
        return 0
    else:
        return L[0] + seq_sum(L[1:])


seq_sum([1, 2, 3, 4, 5])
15

为了更好的理解上面的代码,我们可以借助 print 函数:

def seq_sum(L):
    print('*'*30)
    print(L)
    if not L:
        s = 0
    else:
        s = L[0] + seq_sum(L[1:])
    print('>'*30)
    print(L)
    print('-'*30)
    print(s)
    return s


seq_sum([1, 2, 3, 4, 5])
******************************
[1, 2, 3, 4, 5]
******************************
[2, 3, 4, 5]
******************************
[3, 4, 5]
******************************
[4, 5]
******************************
[5]
******************************
[]
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[]
------------------------------
0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[5]
------------------------------
5
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[4, 5]
------------------------------
9
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[3, 4, 5]
------------------------------
12
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[2, 3, 4, 5]
------------------------------
14
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1, 2, 3, 4, 5]
------------------------------
15





15

从打印的结果可以看出:Python 中的递归是以堆栈的形式来实现的。我们可以利用解包技术来进一步简化递归函数:

def seq_sum(L):
    return 0 if not L else L[0] + seq_sum(L[1:])   # 仅仅适用数值型
def seq_sum(L):
    return L[0] if len(L) == 1 else L[0] + seq_sum(L[1:])  # 适用更多的类型


# 更好的写法
def seq_sum(L):
    first, *rest = L
    return first if not rest else first + seq_sum(rest)

函数式编程(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)\),函数合成就非常麻烦。 这时就需要函数柯里化了。

所谓”柯里化”,就是把一个多参数的函数,转化为单参数函数。

# 柯里化之前
def add(x, y):
    return x + y

add(1, 2)
3
# 柯里化之后
def addX(y):
    def f(x):
        return x + y
    return f


addX(2)(1)
3

有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。

函子#

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

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

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

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

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

def f(x):
    return x**2


def even(x):
    return x % 2 == 0
print('map(even, range(10)):', list(map(even, range(10))))
print('filter(even, range(15)):', list(filter(even, range(15))))
print('filter(f, range(10)):', list(filter(f, range(10))))
print('map(f,range(10)):', list(map(f,range(10))))
print('map(lambda x:x**2,range(10)):', list(map(lambda x:x**2,range(10))))
map(even, range(10)): [True, False, True, False, True, False, True, False, True, False]
filter(even, range(15)): [0, 2, 4, 6, 8, 10, 12, 14]
filter(f, range(10)): [1, 2, 3, 4, 5, 6, 7, 8, 9]
map(f,range(10)): [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
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

上接 python 函数式编程学习笔记

参考:www.sigai.cn/

1 函数式编程概述#

  • 前提:函数在 Python 中是⼀等对象

  • 工具:built-in ⾼阶函数;lambda 函数;operator 模块;functools 模块

  • 模式:闭包与装饰器

  • 替代:⽤用 List Comprehension 可轻松替代 map 和 filter(reduce 替代起来⽐比较困难)

  • 原则:No Side Effect

何为 No Side Effect? 函数的所有功能就仅仅是返回一个新的值而已,没有其他行为,尤其是不得修改外部变量。因⽽,各个独⽴的部分的执⾏顺序可以随意打乱,带来执⾏顺序上的⾃自使得⼀系列新的特性得以实现:⽆锁的并发;惰性求值;编译器器级别的性能优化等

1.1 程序的状态与命令式编程#

  • 程序的状态首先包含了当前定义的全部变量

  • 有了程序的状态,我们的程序才能不断往前推进

  • 命令式编程,就是通过不断修改变量的值,来保存当前运⾏的状态,来步步推进

1.2 函数式编程#

  • 通过函数来保存程序的状态(通过函数创建新的参数和返回值来保存状态)

  • 函数一层层的叠加起来,每个函数的参数或返回值代表了⼀个中间状态

  • 命令式编程⾥一次变量值的修改,在函数式编程⾥变成了⼀个函数的转换

  • 最自然的方式:递归

2 一等函数#

一等对象的定义:

  • 在运⾏时创建

  • 能赋值给变量或数据结构中的元素

  • 能作为参数传给函数

  • 能作为函数的返回结果

Python 中,所有函数的都是一等对象,简称为一等函数

2.1 高阶函数#

定义:接受函数为参数,或把函数作为返回结果的函数

2.1.1 map 映射#

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


def f(x):
    return x * x


print('map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])):',
      list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

show:

map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])): [1, 4, 9, 16, 25, 36, 49, 64, 81]

替代方案:

[x*x for x in range(1,10)]

def format_name(s):
    s1 = s[0:1].upper() + s[1:].lower()
    return s1


print("map(format_name, ['adam', 'LISA', 'barT']):",
      list(map(format_name, ['adam', 'LISA', 'barT'])))
map(format_name, ['adam', 'LISA', 'barT']): ['Adam', 'Lisa', 'Bart']

替代方案:

[format_name(name) for i, name in enumerate(['adam', 'LISA', 'barT'])]

因而,列表推导可以很好的替换 map 函数。

2.1.2 filter 过滤器#
x = [(), [], {}, None, '', False, 0, True, 1, 2, -3]

x_result = filter(bool, x) 
list(x_result)
[True, 1, 2, -3]

替代方案:

[i for i in x if bool(i)]

print("filter((lambda x: x>0), range(-5, 5)):",
      list(filter((lambda x: x > 0), range(-5, 5))))
filter((lambda x: x>0), range(-5, 5)): [1, 2, 3, 4]

替代方案:

[x for x in range(-5, 5) if x > 0]
2.1.3 reduce 递推#
from functools import reduce

m = 2
n = 5
reduce(lambda x, y: x * y, list(range(1, n + 1)), m)
240
def multiply(a, b):
    return a * b


reduce(multiply, range(1, 5))
24
2.1.4 zip 并行#
print("zip( [1, 2, 3], [4, 5, 6]):", list(zip([1, 2, 3], [4, 5, 6])))

show:

zip( [1, 2, 3], [4, 5, 6]): [(1, 4), (2, 5), (3, 6)]
2.1.5 sorted 排序#
sorted([x * (-1) ** x for x in range(10)])
[-9, -7, -5, -3, -1, 0, 2, 4, 6, 8] 
sorted([x * (-1) ** x for x in range(10)], reverse=True) 
[8, 6, 4, 2, 0, -1, -3, -5, -7, -9] 
>>> sorted([x * (-1) ** x for x in range(10)], key=abs)
 [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
>>> sorted([x * (-1) ** x for x in range(10)], reverse=True, key=abs) 
[-9, 8, -7, 6, -5, 4, -3, 2, -1, 0]

minmax 同理。

2.2 partial#

functools 这货用于高阶函数:指那些作用于函数或者返回其他函数的函数。通常情况下,只要是可以被当做函数调用的对象就是这个模块的目标。

假设有如下函数:

def multiply(x, y):
    return x * y

现在,我们想返回某个数的双倍,即:

>>> multiply(3, y=2)
6
>>> multiply(4, y=2)
8
>>> multiply(5, y=2)
10

上面的调用有点繁琐,每次都要传入 y=2,我们想到可以定义一个新的函数,把 y=2 作为默认值,即:

def double(x, y=2):
    return multiply(x, y)

现在,我们可以这样调用了:


>>> double(3)
6
>>> double(4)
8
>>> double(5)
10

事实上,我们可以不用自己定义 double,利用 partial,我们可以这样:

from functools import partial

double = partial(multiply, y=2)

partial 接收函数 multiply 作为参数,固定 multiply 的参数 y=2,并返回一个新的函数给 double,这跟我们自己定义 double 函数的效果是一样的。

所以,简单而言,partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。

需要注意的是,我们上面是固定了 multiply 的关键字参数 y=2,如果直接使用:

double = partial(multiply, 2)

2 是赋给了 multiply 最左边的参数 x


from functools import partial

def subtraction(x, y):
    return x - y

f = partial(subtraction, 4)  # 4 赋给了 x
>>> f(10)   # 4 - 10
-6

组合高阶函数:


from functools import partial

abs_sorted = partial(sorted, key=abs)
abs_sorted([x * (-1) ** x for x in range(10)])

show:

[0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
abs_reverse_sorted = partial(sorted, key=abs, reverse=True)
abs_reverse_sorted([x * (-1) ** x for x in range(10)])

show:

[-9, 8, -7, 6, -5, 4, -3, 2, -1, 0]

2.3 匿名函数#

  • 定义:使⽤用 lambda 表达式创建的函数,函数本身没有名字

  • 特点:只能使⽤用纯表达式,不能赋值,不能使⽤用 whiletry 等块语句

  • 语法: lambda [arg1 [,arg2 [,arg3]]]: expression

Expressions get a value; Statements do something

lambda & def#

写法上:

  1. def 可以用代码块,一个代码块包含多个语句

  2. lambda只能⽤单行表达式,⽽表达式仅仅是单个语句中的⼀种

结果上:

  1. def 语句一定会增加⼀个函数名称

  2. lambda 不会,这就降低了了变量名污染的⻛险

能用一个表达式直接放到 return 里返回的函数都可以⽤ lambda 速写

def multiply(a, b):
    return a * b

multiply_by_lambda = lambda x,y: x * y

List + lambda 可以得到⾏为列表

f_list = [lambda x: x + 1, lambda x: x ** 2, lambda x: x ** 3] 
[f_list[j](10) for j in range(3)] 
[11, 100, 1000]

在 AI 领域里,这种写法常用于处理数据,比如按预定的⼀系列模式处理数据

下面我们以两个例子来结束高阶函数:

例1:


L = range(6)

# 计算l中每个元素的两倍和平方,并将两种组成一个列表
# lambda表达式和python函数一样,也可以接受函数作为参数


def twoTimes(x):
    return x * 2


def square(x):
    return x**2


print([list(map(lambda x: x(i), [twoTimes, square])) for i in L])
print(list(filter(lambda x: x % 2 == 0, L)))

# 内置reduce函数,计算 L 的和
print(reduce(lambda accumValue, newValue: accumValue + newValue, L, 0))
[[0, 0], [2, 1], [4, 4], [6, 9], [8, 16], [10, 25]]
[0, 2, 4]
15

我们依然可以使用列表解析的方式替换 map & filter

[[twoTimes(x), square(x)] for x in L]

[x for x in L if x % 2 == 0]

通过上面的例子我们发现,使用列表推导要比 map 与 filter 简洁且易于理解得多。

但是,我们这里还有一个惰性计算的坑:


f_list = [lambda x:x**i for i in range(5)]

[f_list[j](3) for j in range(5)]
[81, 81, 81, 81, 81]

大家可以思考为什么会出现这个意想不到的结果?


函数修饰器(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]