一个 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的异常。

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

一个 lambda 表达式引起的思考的更多相关文章

  1. 基类的参考Expression能传一个lambda表达式

    using System;using System.Collections.Generic;using System.Data.Entity.Infrastructure;using System.L ...

  2. Spark中Lambda表达式的变量作用域

    通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如: package java8test; public class T1 { public static void main( ...

  3. 掌握 Java 8 Lambda 表达式

    Lambda 表达式 是 Java8 中最重要的功能之一.使用 Lambda 表达式 可以替代只有一个函数的接口实现,告别匿名内部类,代码看起来更简洁易懂.Lambda 表达式 同时还提升了对 集合 ...

  4. C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

    Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...

  5. c++之—— lambda表达式(有个未能解决的问题等待大佬解答)——(在stack overflow找到了答案)

    谓词: 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值.标准库算法所使用的谓词分为两类:一元谓词,意味着它只接受单一参数:二元谓词,意味着它有两个参数.接受谓词参数的算法对输入序列中的元素调 ...

  6. java中使用Lambda表达式的5种语法

    1,标准写法 思考下述情况: String[] arr = {"program", "creek", "is", "a" ...

  7. 6.3 lambda 表达式

    6.3.1 lambda 表达式是一个可传递的代码块,可以在以后执行一次或者多次. 思考(如何按指定时间间隔完成工作,将这个工作放在一个ActionListener的actionPerformed方法 ...

  8. Python中lambda表达式的应用

    lambda表达式 Python中定义了一个匿名函数叫做lambda表达式,个人理解实现的作用就是代替一些简单的函数,使得代码看上去更简洁并且可读性高.举个例子,我们有一个元组列表[(‘a’,1),( ...

  9. [Java 8] (9) Lambda表达式对递归的优化(下) - 使用备忘录模式(Memoization Pattern) .

    使用备忘录模式(Memoization Pattern)提高性能 这个模式说白了,就是将需要进行大量计算的结果缓存起来,然后在下次需要的时候直接取得就好了.因此,底层只需要使用一个Map就够了. 但是 ...

随机推荐

  1. 关于 Windows 7 语言包

    在对IE浏览器进行多语言对应的时候,网页会检测当前系统的语言,来判断网页需要以哪种语言显示.但是,在给系统安装指定语言包时,可能会遇到安装失败的情况,原因就是需要在你的电脑上安装必需的基本语言包.请看 ...

  2. [转]Android开源项目收藏分享

    转自:http://blog.csdn.net/dianyueneo/article/details/40683285 Android开源项目分类汇总 如果你也对开源实现库的实现原理感兴趣,欢迎 St ...

  3. hdu-2620 Ice Rain---数论(取模运算规律)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2620 题目大意: 给出n和k求: 解题思路: kmodi=k-i*[k/i] ,所以=nk-(1*[ ...

  4. springmvc常用注解标签详解(转载)

    1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ...

  5. try...finally的用法

    finally里面只是执行完成try中的代码后,必须执行的代码,即使是try中有异常抛出,也是会去执行finally. >>> try: ... 1/0 ... finally: . ...

  6. 使用appium在android9.0真机上测试程序时报错command failed shell “ps ‘uiautomator’”的解决办法

    appium目前最新的windows版本是1.4.16,在android9.0真机上测试程序时会报错:command failed shell “ps ‘uiautomator’”. 网上大多数人的解 ...

  7. C++Builder编写计算器

    用C++Builder确实能快速上手, 只要是会一点C++基础的,都能很快的编写一些小程序,而且VCL库组件也很丰富,比微软MFC强多了. 自己动手写了一个计算器来增加自己的兴趣.C++基础以后有空还 ...

  8. Spring时间(Date)类型转换+自定义

    第一种方法:利用内置的 CustomDateEditor(不推荐,每个类都需要定义) 首先,在我们的 Controller 的 InitBinder 里面,注册 CustomEditor //首先初始 ...

  9. Msql数据库连接写一个共有的连接工具

    为了避免在每一个DAO中都需要自行连接connection,有多个DAO里都需要获取数据库的连接,并且在很多项目中都是一样的数据库连接. 所以就可以把获取数据库连接的代码重构到一个类里. 这样做的好处 ...

  10. [转] 有关java中两个整数的交换问题

    转载申明:本文主要是用于自己学习使用,为了完善自己的只是框架,没有任何的商业目的. 原文来源:有关Java中两个整数的交换问题 如果侵权,麻烦告之,立刻删除. 在程序开发的过程,要交换两个变量的内容, ...