没有银弹(万能药)NO silver [email protected] Brooks (图灵奖得主 《人月神话》作者) http://program-think.blogspot.com/2009/03/book-review-mythical-man-month.html

浅讲Python函数变量作用域&闭包

Python 2018-05-12 浏览量: 744 字数统计: 1593 最后更新: 2018-05-12 14:03

文章目录[显示]

[TOC]

Python什么时候创建函数

  • Python运行到 def 的时候 函数会被创建

函数内部的语句什么时候运行

  • 表达式调用函数时,函数内部的语句运行
In [5]: def outer(s1, s2):
   ...:     a = []
   ...:     for x in s1:
   ...:         if x in s2:
   ...:             a.append(x)
   ...:     return a
   ...:

In [6]: b= 'aabbc'

In [7]: c= 'ancmd'

In [8]: outer (b, c)
Out[8]: ['a', 'a', 'c']  

In [11]: list(set(outer(b,c)))    # 去重
Out[11]: ['a', 'c']

# 混合类型
In [9]: outer([1, 2, 3], (1, 4))
Out[9]: [1]

函数没有return 语句的时候 会返回什么

None

In [1]: def no_return():
   ...:     a=1+2
   ...:

In [3]: type(no_return)
Out[3]: function

In [4]: print no_return()
None

作用域

  • 在程序中使用变量名时,Python创建、修改或查找变量名都是在 命名空间(一个保存变量名的地方)里进行的,并且 所有变量名都是在Python赋值的时候生成的,函数的所有变量,都与函数的命名空间相关联。

很明显两个a 不一样

a = 1
def func():
    a = 0
  • 函数定义了本地作用域
    • 每次对函数调用都创建了一个新的本地作用域
  • 模块定义了全局作用域,全局作用域的范围仅限于单个文件(这里说明一下 ,Python没有真正全局的变量,所谓的全局作用域默认指的都是 某个文件里面 的全局 )

赋值的变量名除非声明为全局的,否则均为本地变量

Python 三个作用域

变量名引用分为三个作用域进行查找,首先是本地,之后是函数内,之后是全局,最后是内置

所谓的LEGB原则

In [12]: X = 1

In [13]:
    ...: def func():
    ...:     X = 2
    ...:     def a():
    ...:         X = 3
    ...:         print X
    ...:     a()
    ...:

In [14]: func()
3

当在函数中使用未认证的变量名时,Python 会去 搜索4个作用域,如果变量名在这次搜索中没有找到,Python就会报错

  • L 本地作用域
  • E 上一层结构中的本地作用域
  • G 全局作用域
  • B 内置作用域

本地作用域

全局变量在函数内部不经过声明,也可以引用,但是不能改变,如果在函数内部改变的话,必须先声明,需要用到global

In [15]: a=1
    ...: b=2
    ...: c=3
    ...:

In [16]: def test_change_global():
    ...:     a = b + c
    ...:     print a
    ...:

In [17]: test_change_global()
5

In [18]: print a
Out[18]: 1

In [19]: def test_change_global():
    ...:     global a
    ...:     a=b+c
    ...:

In [20]: test_change_global()

In [21]: print a
5
X = 1  # X的值与时间相关联,取决于那个函数是最后进行调用的
def func1():
    global X
    X = 2
def func2():
    global X
    X = 3

嵌套作用域

  • 一个引用,首先在本地作用域查找;之后在代码的语法嵌套了的函数中的本地作用域中,从内到外查找;之后查找当前的全局作用域,最后在内置作用域内
In [23]: def f1():
    ...:     x = 1
    ...:     def f2():
    ...:         print x
    ...:     f2()
    ...:

In [24]: f1()
1
  • 默认情况下,一个赋值会创建或改变变量名X的当前作用域,如果变量在函数内部声明为全局变量,它将为创建或改变全局变量
  • 如果变量在函数内部声明为nonlocal (Python 3 ),赋值会修改最近的嵌套函数的本地作用域中的变量
In [26]: def outer():
    ...      x = 1
    ...      def inner():
    ...           nonlocal x
    ...           x += 1
    ...           print(x)
    ...      return inner
In [27]: f = outer()

In [28]: f()
2
In [29]: f()
3 
>>> a = "abc"
>>> def outer():
...     a = "123"
...     def inter():
...             print(a)
...     inter()
...
>>> outer()
123
>>> print(a)
abc

----------------------------

>>> a = "abc"
>>> def outer():
...     a = "123"
...     def inter():
...             nonlocal a
...             a = "nnnnnnnnnn"
...     inter()
...     print(a)
...
>>> outer()
nnnnnnnnnn
>>> print(a)
abc

闭包

闭包:可以理解为 嵌套函数,内部函数调用外部函数的变量

def outer():
    a = 1
    def inner():  # inner 是一个闭包 
        print(a)
    inner()
outer()

常见的闭包形式

  • 内部创建了一个函数,把这个函数以 返回值的形式 在外部函数返回
  • 最简单的闭包 就是在 内部函数里引用外部的函数,但是如果我们这么使用闭包的话,我们调用内部的函数就会出一个问题 ,只能通过 调用 outer 函数去 调用inner 函数,就是说只要想调用inner 就必须通过 outer来调,但是 如果频繁的去调用 outer,那么outer里面的所有变量都会 频繁的 在内存里 生成消失 生成消失(因为每一次调用都要生成,每次结束都消失),很消耗时间,所以我们可以 用函数名 以返回值的形式 被函数返回,所以由 inner() 变成了 return inner ,由直接执行inner 变成了 把 inner的内存地址 返回回去,在下面用inn 接收,inn是一个全局的变量,也就是全局的变量 指向了一个内部的函数 ,此时 a 不会随着outer函数的结束而消失,因为他在inner里面用到
def outer():
    a = 1
    def inner():
        print(a)
    return inner  # 返回一个函数地址
inn = outer()     # 赋值的是 函数地址
inn()    # 加括号表示,在函数的外部使用内部的函数

使用 闭包的好处就是 保护了这个变量a, 不是全局变量,不是想用就能用,但是因为inner 想使用a 就可以随意的去使用,说白了就是延长了 a 的生存周期,让他 a 节省了很多在内存里面重复的 创建删除的时间


其他问题

Python什么时候创建函数

  • Python运行到 def 的时候 函数会被创建

函数内部的语句什么时候运行

  • 表达式调用函数时,函数内部的语句运行
    lambda 表达式和 def 语句的关系

都会创建函数对象,以便稍后调用。但因为lambda是表达式,可以出现在def不能出现的地方(如列表推导),从语法上将,lambda只允许单个的返回值表达式,因为它不支持语句代码块,因此,不适合用于较大的函数

比较map、filter和reduce

这三个内置函数都是对一个可迭代对象以及集合结果中的各项,应用另一个函数,map把每一项传递给函数并收集结果,filter收集那些函数返回结果为True的项,reduce通过“累加”,给出单一的一个结果

列表解析放在方括号和圆括号中有什么区别

  • 方括号中的列表解析会一次在内存中产生结果列表;列表解析位于圆括号中,是生成器表达式,与列表解析有类似的意义,但不会一次产生结果列表。与此相对应的,生成器表达式会返回一个生成器对象,用在迭代环境中,一次产生结果中的一个元素

生成器和迭代器有什么关系

  • 生成器是支持迭代协议的对象,它们有__next__方法,依次返回结果,直到产生StopIteration异常,在Python中,我们可以用def+yield产生一个生成器,也可以用”圆括号的列表推导”产生一个生成器,生成器对象是自动支持迭代器协议的,不需要我们再做什么

如何分辨函数是否为生成器函数

  • 如果函数中有yield语句,则会返回生成器对象。除此之外,生成器函数和普通函数语法上是一样的。但是,生成器由Python特别编译,以便在调用的时候返回一个可迭代的对象

yield语句是做什么的

  • yield语句让一个普通函数成为一个生成器函数,生成器函数自动实现迭代器协议
小蜗牛 说:
Freedom is the source from which all meaning and all values spring .


文章版权归 原文作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权| 转载必须包含本声明,并以超链接形式注明原文作者和本文原始地址: https://www.tougetu.com/2018/05/python-LEGB-1.html

还不快抢沙发

添加新评论

代码 Pastebin Gist 加粗 删除线 斜体 链接 签到