一个 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]没有5step:步长,默认为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由用户自定义。具体介绍如下。
- 这里的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函数的用法扩展为以下几种:
将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。
例如,执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。
将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。
例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。
将lambda函数作为其他函数的返回值,返回给调用者。
函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础,在这里我们不展开。
- 将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'。
- 另外,部分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) # 引用varreturn cloe # 返回内部函数cl = closure()cl()
2.闭包优点
- 避免使用全局变量
- 可以提供部分数据的隐藏
- 可以提供更优雅的面向对象实现
优点1,2 就不说了,很容易理解,关于第三个,例如当在一个类中实现的方法很少时,或者仅有一个方法时,就可以选择使用闭包。
举个栗子
# 用类实现一个加法的类是这样class _Add(object):def __init__(self, a, b):self.a = aself.b = bdef add(self):return self.a + self.b# 用闭包实现def _Add(a):def add(b):return a + breturn addad = _Add(1) # 是不是很像类的实例化print(ad(1)) # out:2print(ad(2)) # out:3print(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))#输出结果为:0123
二、上面看不懂就看这儿
函数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*ifun_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*ifun_lambda_list.append(lambda_)print('外层函数 I 为:{} 命名空间为:{}'.format(i, locals()))return fun_lambda_listfl = 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*ifun_lambda_list.append(lambda_)return fun_lambda_listfl = 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的异常。
人生还有意义。那一定是还在找存在的理由
一个 lambda 表达式引起的思考的更多相关文章
- 基类的参考Expression能传一个lambda表达式
using System;using System.Collections.Generic;using System.Data.Entity.Infrastructure;using System.L ...
- Spark中Lambda表达式的变量作用域
通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如: package java8test; public class T1 { public static void main( ...
- 掌握 Java 8 Lambda 表达式
Lambda 表达式 是 Java8 中最重要的功能之一.使用 Lambda 表达式 可以替代只有一个函数的接口实现,告别匿名内部类,代码看起来更简洁易懂.Lambda 表达式 同时还提升了对 集合 ...
- C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)
Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...
- c++之—— lambda表达式(有个未能解决的问题等待大佬解答)——(在stack overflow找到了答案)
谓词: 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值.标准库算法所使用的谓词分为两类:一元谓词,意味着它只接受单一参数:二元谓词,意味着它有两个参数.接受谓词参数的算法对输入序列中的元素调 ...
- java中使用Lambda表达式的5种语法
1,标准写法 思考下述情况: String[] arr = {"program", "creek", "is", "a" ...
- 6.3 lambda 表达式
6.3.1 lambda 表达式是一个可传递的代码块,可以在以后执行一次或者多次. 思考(如何按指定时间间隔完成工作,将这个工作放在一个ActionListener的actionPerformed方法 ...
- Python中lambda表达式的应用
lambda表达式 Python中定义了一个匿名函数叫做lambda表达式,个人理解实现的作用就是代替一些简单的函数,使得代码看上去更简洁并且可读性高.举个例子,我们有一个元组列表[(‘a’,1),( ...
- [Java 8] (9) Lambda表达式对递归的优化(下) - 使用备忘录模式(Memoization Pattern) .
使用备忘录模式(Memoization Pattern)提高性能 这个模式说白了,就是将需要进行大量计算的结果缓存起来,然后在下次需要的时候直接取得就好了.因此,底层只需要使用一个Map就够了. 但是 ...
随机推荐
- python filter&sorted
filter filter()接收一个函数和一个序列和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素在一个list中, ...
- rolllup巧用
--构造环境drop table dept purge;drop table emp purge;create table dept as select * from scott.dept;creat ...
- GO语言(六)接口使用
<music> |------<src> |-------<library> |-------manager.go |-------manager_test.go ...
- ZT UML 类与类之间的关系
1.聚合关系是关联关系的一种,是强的关联关系. 2.聚合是整体和部分之间的关系,例如汽车由引擎.轮胎以及其它零件组成. 3.聚合关系也是通过成员变量来实现的.但是,关联关系所涉及的两个类处在同 ...
- Service Fabric eShop On Containers
Service Fabric承载eShop On Containers 从模块化到微服务化 从Pet Shop 到eShop on Container都是Microsoft在技术演进的路径上给开发者展 ...
- OC Copy and MutableCopy的使用
#import <Foundation/Foundation.h> @interface Student : NSObject <NSCopying> // copy代表set ...
- Python机器学习神器:sklearn&numpy
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMDE0MDMzOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- iOS的KVO使用和轻量级封装
KVO的使用方法 注冊 [object addObserver:observer forKeyPath:@"text" options:NSKeyValueObservingOpt ...
- Mac 导入maven项目详解
1.打开Eclipse,选择Help->Install New SoftWare2.点击add 地址输入:http://m2eclipse.sonatype.org/sites/m2e,name ...
- zip 函数
zip 函数,看上去是打包的意思,其实功能是将多个可迭代对象,组合成一个个元组. zip(iter1,iter2) a,b = zip(*zip(iter1,iter2)) a = [1,2,3] b ...