一个 lambda 表达式引起的思考

一个 lambda表达式 引起的思考

 
 
fun = [lambda x: x*i for i in range(4)]
for item in fun:
    print(item(1))

全文都是抄来的

1. 列表生成式

1.1 range 函数:
 
 
Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表。
函数语法:
range(stop)
range(start, stop[, step])
参数说明:
start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
1.2 列表生成式:
 
 
list 生成式的创建
首先,lsit 生成式的语法为:
[expr for iter_var in iterable] 
[expr for iter_var in iterable if cond_expr]
第一种语法:首先迭代 iterable 里所有内容,每一次迭代,都把 iterable 里相应内容放到iter_var 中,再在表达式中应用该 iter_var 的内容,最后用表达式的计算值生成一个列表。
第二种语法:加入了判断语句,只有满足条件的内容才把 iterable 里相应内容放到 iter_var 中,再在表达式中应用该 iter_var 的内容,最后用表达式的计算值生成一个列表。
其实不难理解的,因为是 list 生成式,因此肯定是用 [] 括起来的,然后里面的语句是把要生成的元素放在前面,后面加 for 循环语句或者 for 循环语句和判断语句。
例子:
# -*- coding: UTF-8 -*-
lsit1=[x * x for x in range(1, 11)]
print(lsit1)
输出的结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
可以看到,就是把要生成的元素 x * x 放到前面,后面跟 for 循环,就可以把 list 创建出来。那么 for 循环后面有 if 的形式呢?又该如何理解:
# -*- coding: UTF-8 -*-
lsit1= [x * x for x in range(1, 11) if x % 2 == 0]
print(lsit1)
输出的结果:
[4, 16, 36, 64, 100]
这个例子是为了求 1  10 中偶数的平方根,上面也说到, x * x 是要生成的元素,后面那部分其实就是在 for 循环中嵌套了一个 if 判断语句。
那么有了这个知识点,我们也可以猜想出,for 循环里面也嵌套 for 循环。具体示例:
# -*- coding: UTF-8 -*-
lsit1= [(x+1,y+1) for x in range(3) for y in range(5)] 
print(lsit1)
输出的结果:
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]
其实知道了 list 生成式是怎样组合的,就不难理解这个东西了。因为 list 生成式只是把之前学习的知识点进行了组合,换成了一种更简洁的写法而已。

2. 匿名函数: 原文链接

Python中的lambda的“一个语法,三个特性,四个用法,一个争论”。

一个语法

在Python中,lambda的语法是唯一的。其形式如下:

 
 
 lambda argument_list: expression

其中,lambda是Python预留的关键字,argument_list和expression由用户自定义。具体介绍如下。

  1. 这里的argument_list是参数列表。它的结构与Python中函数(function)的参数列表是一样的。具体来说,argument_list可以有非常多的形式。例如:
  • a, b
  • a=1, b=2
  • *args
  • **kwargs
  • a, b=1, *args
  • ......

这里的expression是一个关于参数的表达式。表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的。以下都是合法的表达式:

  • 1
  • None
  • a + b
  • sum(a)
  • 1 if a >10 else 0
  • ......

这里的 lambda argument_list: expression 表示的是一个函数。这个函数叫做lambda函数。

 

三个特性

lambda函数有如下特性:

 
 
lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。
lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。
lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明。

下面是一些lambda函数示例:

 
 
- lambda x, y: x*y;             函数输入是x和y,输出是它们的积x*y
- lambda:None;                  函数没有输入参数,输出是None
- lambda *args: sum(args);       输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
- lambda **kwargs: 1;           输入是任意键值对参数,输出是1

 

四个用法

由于lambda语法是固定的,其本质上只有一种用法,那就是定义一个lambda函数。在实际中,根据这个lambda函数应用场景的不同,可以将lambda函数的用法扩展为以下几种:

  1. 将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。

     
       
    例如,执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。
  1. 将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。

     
       
    例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。
  2. 将lambda函数作为其他函数的返回值,返回给调用者。

     
       
    函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础,在这里我们不展开。
  1. 将lambda函数作为参数传递给其他函数。
 
 
部分Python内置函数接收函数作为参数。典型的此类内置函数有这些。
- filter函数。
    此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]
- sorted函数。
    此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]
- map函数。
    此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]
- reduce函数。
    此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是'1, 2, 3, 4, 5, 6, 7, 8, 9'
  1. 另外,部分Python库函数也接收函数作为参数,例如 gevent 的 spawn 函数。此时,lambda 函数也能够作为参数传入。

3. 闭包 原文链接

1.什么是闭包,闭包必须满足以下3个条件:
  • 必须是一个嵌套的函数。
  • 闭包必须返回嵌套函数。
  • 嵌套函数必须引用一个外部的非全局的局部自由变量。

举个栗子

 
 
# 嵌套函数但不是闭包
def nested():
    def nst():
        print('i am nested func %s' % nested.__name__)
    nst()
# 闭包函数
def closure():
    var = 'hello world' # 非全局局部变量
    def cloe():
        print(var) # 引用var
    return cloe # 返回内部函数
cl = closure()
cl()

2.闭包优点
  • 避免使用全局变量
  • 可以提供部分数据的隐藏
  • 可以提供更优雅的面向对象实现

优点1,2 就不说了,很容易理解,关于第三个,例如当在一个类中实现的方法很少时,或者仅有一个方法时,就可以选择使用闭包。

举个栗子

 
 
# 用类实现一个加法的类是这样
class _Add(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        return self.a + self.b
# 用闭包实现
def _Add(a):
    def add(b):
        return a + b
    return add  
ad = _Add(1) # 是不是很像类的实例化
print(ad(1)) # out:2
print(ad(2)) # out:3
print(ad(3)) # out:4

闭包的概念差不多就是这样了。

 

4. 延迟绑定:

 
 
 
       
def _Add(a):
    def add(b):
        return a + b
    # 1. 这是一个闭包
    # 2. 当 python 解释器走到 add 函数时, 发现这是一个函数, 定义下 参数 a 然后跳过该函数 接着往下执行
    # 3. 当执行 add 函数的时候, 因为 add 函数定义的时候, 并没有定义 b对象, 所以按照 LEGB 规则 需要向外找
    # 4. 这就是 延迟绑定
    return add 

 

5. 解决问题 原文链接

一、问题描述
 
 
fun = [lambda x: x*i for i in range(4)]
for item in fun:
    print(item(1))

上述式子的输出结果: 预计结果为:0, 2, 4, 6 实际输出为:3, 3, 3, 3

  • 原理:i 在外层作用域 lambda x: x*i 为内层(嵌)函数,他的命名空间中只有 {'x': 1} 没有 i , 所以运行时会向外层函数(这儿是列表解析式函数 [ ])的命名空间中请求 i 而当列表解析式运行时,列表解析式命名空间中的 i 经过循环依次变化为 0-->1-->2-->3 最后固定为 3 , 所以当 lambda x: x*i 内层函数运行时,去外层函数取 i 每次都只能取到 3
  • 解决办法:变闭包作用域为局部作用域。 给内层函数 lambda x: x*i 增加参数,命名空间中有了用来存储每次的 i , 即改成 [lambda x, i=i: x*i for i in range(4)] 这样每一次,内部循环生成一个lambda 函数时, 都会把 --i--作为默认参数传入lambda的命名空间 循环4次实际lambda表达式为: 第一次:lambda x, i=0 第二次:lambda x, i=1 第三次:lambda x, i=2 第四次:lambda x, i=3
 
 
fun = [lambda x, i=i: x*i for i in range(4)]
for item in fun:
    print(item(1))
#输出结果为:
0
1
2
3
二、上面看不懂就看这儿

函数fun = [lambda x: x*i for i in range(4)]等价于:如下函数

 
 
def func():
    fun_lambda_list = []
    for i in range(4):
        def lambda_(x):
            return x*i
        fun_lambda_list.append(lambda_)
        
    return fun_lambda_list

查看该函数命名空间及 I 值变化:

 
 
def func():
    fun_lambda_list = []
    for i in range(4):
        def lambda_(x):
            print('Lambda函数中 i {} 命名空间为:{}:'.format(i, locals()))
            return x*i
        fun_lambda_list.append(lambda_)
        print('外层函数 I 为:{} 命名空间为:{}'.format(i, locals()))
    return fun_lambda_list
fl = func()
fl[0](1)
fl[1](1)
fl[2](1)
fl[3](1)

#运行结果为:为了排版美观,我已将输出lambda_函数地址改名为:lam函数1 2 3

 
 
外层函数I为:0 命名空间为:{'i': 0, 'lambda_': lam函数1 'fun_lambda_list': [lam函数1]}
外层函数I为:1 命名空间为:{'i': 1, 'lambda_': lam函数2, 'fun_lambda_list': [lam函数1, lam函数2]}
外层函数I为:2 命名空间为:{'i': 2, 'lambda_': lam函数3, 'fun_lambda_list': [lam函数1, lam函数2, lam函数3]}
外层函数I为:3 命名空间为:{'i': 3, 'lambda_': lam函数4, 'fun_lambda_list': [lam函数1, lam函数2, lam函数3, lam函数4]}
Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:

可以看见:就像上面所说的:四次循环中外层函数命名空间中的 i 从 0-->1-->2-->3 最后固定为3, 而在此过程中内嵌函数-Lambda函数中因为没有定义 i 所以只有Lambda 函数动态运行时, 在自己命名空间中找不到 i 才去外层函数复制 i = 3 过来,结果就是所有lambda函数的 i 都为 3, 导致得不到预计输出结果:0,1,2,3 只能得到 3, 3, 3, 3

  • 解决办法:变闭包作用域为局部作用域。
 
 
def func():
    fun_lambda_list = []
    for i in range(4):
        def lambda_(x, i= i):
            print('Lambda函数中 i {} 命名空间为:{}:'.format(i, locals()))
            return x*i
        fun_lambda_list.append(lambda_)
    return fun_lambda_list
fl = func()
res = []
res.append(fl[0](1))
res.append(fl[1](1))
res.append(fl[2](1))
res.append(fl[3](1))
print(res)
#输出结果为:
Lambda函数中 i 0 命名空间为:{'x': 1, 'i': 0}:
Lambda函数中 i 1 命名空间为:{'x': 1, 'i': 1}:
Lambda函数中 i 2 命名空间为:{'x': 1, 'i': 2}:
Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}:
[0, 1, 2, 3]

给内层函数 lambda增加默认参数,命名空间中有了用来存储每次的 i , 即改成 `def lambda(x, i=i) :` 这样每一次, 内部循环生成一个lambda 函数时,都会把 i 作为默认参数传入lambda的命名空间 循环4次实际lambda表达式为: 第一次:lambda( x, i=0) 第二次:lambda(x, i=1) 第三次:lambda(x, i=2) 第四次:lambda(x, i=3)

这样我们就能得到预计的结果:0, 1, 2, 3

5. LEGB

只有函数、类、模块会产生作用域,代码块不会产生作用域。作用域按照变量的定义位置可以划分为4类:

 
 
Local(函数内部)局部作用域
Enclosing(嵌套函数的外层函数内部)嵌套作用域(闭包)
Global(模块全局)全局作用域
Built-in(内建)内建作用域

python解释器查找变量时,会按照顺序依次查找局部作用域--->嵌套作用域--->全局作用域--->内建作用域,在任意一个作用域中找到变量则停止查找,所有作用域查找完成没有找到对应的变量,则抛出 NameError: name 'xxxx' is not defined的异常。

人生还有意义。那一定是还在找存在的理由

转载于:https://www.cnblogs.com/amou/p/9726630.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值