python 函数进阶与闭包

函数的命名空间和作用域引言现在有个问题,函数里面的变量,在函数外面能直接引用么?上面为什么会报错呢?现在我们来分析一下python内部的原理是怎么样:我们首先回忆一下Python代码运行的时候

大家好,又见面了,我是你们的朋友全栈君。

函数的命名空间和作用域

引言

现在有个问题,函数里面的变量,在函数外面能直接引用么?

def func1():
    m = 1
    print(m)

print(m)  #这行报的错


报错了:
NameError: name 'm' is not defined

上面为什么会报错呢?现在我们来分析一下python内部的原理是怎么样:

  我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。

  等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

我们给这个‘存放名字与值的关系’的空间起了一个名字——-命名空间。

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;

在函数的运行中开辟的临时的空间叫做局部命名空间。

 二,命名空间和作用域

<span role="heading" aria-level="2">python 函数进阶与闭包
<span role="heading" aria-level="2">python 函数进阶与闭包

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

python之禅

python之禅

在python之禅中提到过:命名空间是一种绝妙的理念,让我们尽情的使用发挥吧!

命名空间的定义

定义:可以理解为一个容器,在这个容器中可以装许多个名字。不同容器中的同名的名字是不会相互冲突

命名空间的三种形式

内置命名空间     全局命名空间        局部命名空间 

内置   在启动解释器的时候生成的,存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法

全局   文件级别定义的名字都会存放在全局命名空间,执行python文件时会产生,文件执行完毕就会失效
文件级别:定义的名字:顶头写的没有缩进的名字
局部级别:定义在函数内部的名字,只在调用函数时才会生效,函数调用完毕就会失效

三种命名空间之间的加载与取值顺序

 加载顺序:

内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

取值顺序:

  (1) 在局部调用:局部命名空间->全局命名空间->内置命名空间

1 x = 1
2 def f(x):
3     print(x)
4 print(10)  结果为10
注释:执行代码 第一步x=1 定义函数f并传入形参x 第三步 打印x 第四步

  (2)在全局调用:全局命名空间->内置命名空间

 
1 x = 1
2 def f(x):
3     print(x)
4 
5 f(10)
6 print(x)

函数的作用域:

作用域定义:作用域就是作用范围

作用域分类

1 全局作用域:   包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效  

2 局部作用域:    包含局部名称空间,只能在局部范围内生效

作用域中的关键字

globals查看内置和全局的所有变量名字,
locals查看局部的所有变量名字:当把locals放到全局的位置时可以查看全局的所有变量名字,当把locals放到局部的位置时就可以查看局部范围的所有变量名字

作用域的作用范围

 globals 全局的作用范围
 locals  局部的作用范围
 
在局部调用globals和locals
 1 def func():
2     a = 12
3     b = 20
4     print(locals())
5     print(globals())
6 
7 func()

global关键字 能不用尽量不去用

1 a = 10    现在a=10
2 def func():
3     global a   使用global将变量改变为全局变量
4     a = 20     将其赋值为20
6 print(a)   打印函数func中的变量a=20
7 func()
8 print(a)   全局变量a初始值为10,经过globals改变以后值为20
所以结果 10 20

作用域总结:

站在全局的角度,从使用名字来看:如果全局有:用全局的,如果全局没有:用内置的

函数的嵌套和作用域链

函数嵌套的作用

作用:为了保护内部函数,确定内部函数只能在外部函数中被调用

函数的嵌套调用

  1 def f1():
2     print('f1')
3 def f2():
4     a = 10
5     f1()              
6 f2()
7 #代码从上到下读取,f2()调用会调用f2()函数体的内容,最后发现f1()然后调用f1()开始执行函数体的内容最后输出f1

示例二

 1 def animal():
 2     def tiger():
 3         print('bark')
 4     print('eat')
 5     tiger()
 6 animal()           #函数嵌套调用
 7 
 8 
 9 eat
10 bark 

 函数的嵌套定义

 1  def f1():
 2     def f2():
 3         def f3():
 4             print("in f3")
 5         print("in f2")
 6         f3()
 7     print("in f1")
 8     f2()
 9     
10 f1()

函数的作用域链

1 def f1():
2     a = 1
3     def f2():
4         print(a)
5     f2()
6 
7 f1()      结果为1  
  1 def f1():
2     a = 1
3     def f2():
4         a = 2
5     f2()
6     print('a in f1 : ',a)
7 
8 f1()    结果  1   

  nonlocal关键字

   1 def f1():
 2     a = 1       nolocal向上改变一层到这里,a的值被改变为2
 3     def f2():
 4         nonlocal a   改变变量a的值并且只可以向上改变一层
 5         a = 2   改变后的值为2
 6     f2()
 7     print('a in f1 : ',a)
 8 
 9 f1()              结果  2  2
10 #函数会使用自己本身的a的赋值,f2()函数体里面的赋值用完会从内存删除掉,想保留要加入nonlocal 加变量名才能会取f2()函数体里a的赋值

函数名的本质

函数名本质上就是函数的内存地址

函数名的作用

1.可以被引用

  def func():
    print('func')  #打印字符串的内容为func
print(func) #打印变量名func的内存地址
f=func #相当于定义了一个变量,名字是f 值为func
print(f) #打印f的内存地址
f() 调用f
#结果
#<function func at 0x02A94390>
#<function func at 0x02A94390>
#func
12 #func会指向函数的内存地址,不会取用函数里面的值,只有加()才能调用函数

 2.可以被当作容器类型(可变数据类型)的元素

   1 def func():
 2     print('func')
 3 print(func)
 4 f=func
 5 print(f)
 6 l = [f]
 7 print(l)
 8 l[0] == f
 9 l[0]()
10 
11 
12 <function func at 0x0000025B3C73B9D8>
13 <function func at 0x0000025B3C73B9D8>
14 [<function func at 0x0000025B3C73B9D8>]
15 func

3.可以当作函数的参数和返回值

1 第一类对象(first-class object)指
2 1.可在运行期创建
3 2.可用作函数参数或返回值
4 3.可存入变量的实体。

 总之就记住一句话,就当普通变量用

闭包

闭包的定义

内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数

1.闭 内部的函数
2.包 包含了对外部函数作用域中变量的引用

由于有了作用域的关系,我们就不能拿到函数内部的变量和函数了。如果我们就是想拿怎么办呢?返回呀!

我们都知道函数内的变量我们要想在函数外部用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数呢?

是不是直接就把这个函数的名字返回就好了?

闭包函数最常用的用法

  1 def func():
2     name = 'eva'
3     def inner():
4         print(name)
5     return inner
6 
7 f = func()
8 f()

判断闭包函数的方法.__closure__

 输出结果含有cell说明该函数是闭包函数

  
1 def func():
 2      name = 'Eden'
 3      def inner():
 4          print(name)
 5      print(inner.__closure__)
 6      return inner()
 7 func()    #看输出是否有cell元素有就是闭包函数
 8 
 9 
10 (<cell at 0x000002711EE465B8: str object at 0x000002711EEDA500>,)
11 Eden
12 

16 #输出的__closure__为None :不是闭包函数
17 name = '山鹰'
18 def func2():
19     def inner():
20         print(name)
21     print(inner.__closure__)
22     return inner
23 
24 f2 = func2()
25 f2()
26  
28 None
29 山鹰

闭包函数的嵌套

  
1 def wrapper():
 2     money = 1000
 3     def func():
 4         name = 'Eden'
 5         def inner():
 6             print(name,money)
 7         return inner
 8     return func
 9 
10 f = wrapper()
11 i = f()
12 i()

闭包函数在网络上的应用

 1 from urllib.request import urlopen
 2 
 3 def index():
 4     url = "http://www.xiaohua100.cn/index.html"
 5     def get():
 6         return urlopen(url).read()
 7     return get
 8 
 9 xiaohua = index()
10 content = xiaohua()
11 print(content)

小结:

命名空间:

  有三种命名空间从大到小的范围顺序:内置命名空间、全局命名空间、局部命名空间

作用域(包括函数的作用域链):

  调用时,如果在自己的空间内有,就用自己的。如果没有就使用大范围的。不能从大范围中用小范围的。

函数的嵌套:

  嵌套调用

  嵌套定义:定义在内部的函数无法直接在全局被调用

函数名的本质:

  就是一个变量,保存了函数所在的内存地址

闭包(必须理解掌握,闭包是为后面装饰器的出现打基础):

  内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/124538.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 三字经 全文解读(三字经全文朗读儿童版解读)

    前言:《三字经》自南宋王应麟(字伯厚)先生所创作以来,已有七百多年历史,内容大都采用韵文,每三字一句,四句一组,像一首诗一样,背诵起来,如唱儿歌,三字经是学习中华传统文化不可多得的的儿童启蒙读物,共一千多字,可谓家喻户晓,脍炙人口。内容包括了中国传统的教育、历史、天文、地理、伦理和道德以及一些民间传说,广泛生动而又言简意赅。用来教育子女琅琅上口十分有趣,又能启迪心智,时人觉得本书内容很好,纷纷翻印

    2022年4月10日
    83
  • thinkpad笔记本电脑指示灯图解_thinkpad笔记本关机后,指示灯还亮

    thinkpad笔记本电脑指示灯图解_thinkpad笔记本关机后,指示灯还亮顺序自左向右: 1挂起状态指示灯绿色:计算机处于挂起状态绿色且不断闪烁:计算机正在进入挂起或休眠状态,或者正在从挂起或休眠状态中恢复回来2AC电源状态指示灯绿色:计算机连接到交流电源上3电池状态指示灯绿色:电池电量在80%到100%of之间,以及电量处于20%到80%之间,正在使用中.绿色且不断闪烁:电池电量在20%到80%之间,且正在充电中.橙色:电池电量

    2022年9月16日
    2
  • 测试用例附实例[通俗易懂]

    一、测试用例的概念测试用例是测试过程中很重要的一类文档,它是测试工作的核心,是一组在测试时输入和输出的标准,是软件需求的具体对照。二、测试用例的作用检验软件是否满足客户需求 测试人员的工作量的一种体现 展示测试用例的设计思路三、测试用例的内容测试用例八个基本项是:测试用例编号、测试项目、测试标题、重要级别、预置条件、输入、操作步骤、预期输出(不同公司的测试用例内容不尽相同…

    2022年4月13日
    55
  • uat测试环境是预生产环境_php开发环境与测试环境

    uat测试环境是预生产环境_php开发环境与测试环境开发环境:开发环境是程序猿们专门用于开发的服务器,配置可以比较随意,为了开发调试方便,一般打开全部错误报告。测试环境:一般是克隆一份生产环境的配置,一个程序在测试环境工作不正常,那么肯定不能把它发布到生产机上。生产环境:是指正式提供对外服务的,一般会关掉错误报告,打开错误日志。可以理解为包含所有的功能的环境,任何项目所使用的环境都以这个为基础,然后根据客户的个性化需求来做调整或者

    2022年9月30日
    2
  • expect中的正则匹配[通俗易懂]

    expect中的正则匹配[通俗易懂]文档原文:xpect_out(x,string)expect_out(x,start|end)如果expect匹配是采用高级正则表达式的话(-re参数表示高级正则表达式方式匹配),那么每个子模式都有一个序号,序号从1-9,如:setoutput”abbbcabkkkka”expect-indices-re”b(b*).*(k+)”$output那么:setexpect_out(0,start)==>

    2025年8月9日
    3
  • Windows server 2003 安装vs2005 sp1补丁包报1718错误的解决方法

    Windows server 2003 安装vs2005 sp1补丁包报1718错误的解决方法收藏于2012-03-30迁移自个人的百度空间——————————–解决步骤如下1.在控制面板中打开“管理工具“。2.双击“本地安全策略”。 3.单击“软件限制策略”。(注意:如果未列出软件限制,请右击“软件限制策略”,然后单击“新建策略”。)4.在“对象类型”下,双击“强制”。 5.单击“除本地管理员以外的所有用户”…

    2022年10月5日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号