一个 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. photoshopcs6破解补丁用来干嘛的

    photoshopcs6破解补丁为 Adobe CS6 系列软件通用破解补丁,亲测可用,终于能用了不再出现那个烦人的购买页面了,cs6破解补丁解压后得到32和64两个文件夹,根据自己的系统类型选择,6 ...

  2. python 面向对象:类,作用域

    类(Class)和实例(Instance) 定义类是通过class关键字:class Student(object): pass class后面紧接着是类名,即Student接着是(object),表 ...

  3. ROS Create a Catkin Workspace

    Step1 : First, create the top level catkin workspace directory and a sub-directory named src (pronou ...

  4. June 30th 2017 Week 26th Friday

    Love me little and love me long. 不求情意浓,但愿情意久. Some people say beautiful young people are the creatur ...

  5. easyui学习笔记3—在展开行内的增删改操作

    这一篇介绍在一个展开行内进行的增删操作,如果一行很长有很多的数据,在一个展开行内进行编辑将更加方便. 1.先看引用的资源 <link rel="stylesheet" hre ...

  6. PHP中使用substr()截取字符串出现中文乱码问题该怎么办

    一.使用mbstring扩展库的mb_substr()截取就不会出现乱码了. 可以用mb_substr()/mb_strcut()这个函数,mb_substr()/mb_strcut()的用法与sub ...

  7. ASP.NET SingalR + MongoDB 实现简单聊天室(三):实现用户群聊,总结完善

    前两篇已经介绍的差不多了,本篇就作为收尾. 使用hub方法初始化聊天室的基本步骤和注意事项 首先确保页面已经引用了jquery和singalR.js还有对应的hubs文件,注意,MVC框架有时会将jq ...

  8. es6的解构赋值

    分类:数组.对象.字符串.布尔值.函数参数.数值解构赋值

  9. CUDA与OpenGL互操作实例

    本文要解决的问题是如何实现CUDA和OpenGL的互操作,使得GPU能够将通用计算的运算结果交给OpenGL进行绘制. 本文的应用程序主要包括两个方面: 1.      使用CUDA核函数生成图像数据 ...

  10. 金融新手投标模块布局小Demo

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...