Python基础教程之名称空间以及作用域

目录
  • 前言
  • 名称空间
    • 什么是名称空间
    • 名称空间的意义
    • 名称空间的查找顺序
    • 局部名称空间详解
      • 嵌套函数中的查找顺序
      • 关于嵌套函数的使用
  • 作用域
    • 什么是作用域
    • global语句
    • nonlocal语句
  • 题目题目
  • 小结
  • 总结

前言

所谓“基础不狠,人站不稳”,对于任何一种编程语言来说基础往往都是重中之重,以Python为例,其中的两大分水岭就是函数编程和面向对象,而今天所要巩固的知识点后续会多次使用,那就是名称空间和作用域

名称空间

什么是名称空间

在Python中名称空间是用存储对象和名字绑定关系的地方,那么问题来了,什么是对象,什么是名字,什么是绑定关系?

1)在目前,我们对于对象的认知可以暂时只停留在人云亦云的“Python中一切都是对象”基础上,函数是对象、类是对象、变量,模块、所有一切都是对象,有这样的认知就可以了,后续有机会将继续补充;

2)名字,很简单,每一次我们对模块、变量、函数、类的定义都需要取名字,而这些名字都会放在名称空间之中;

3)Python对于名字和数据之间给出了绑定关系,举个例子,当我们在定义a = 6时,Python就自动将变量a这个名字与6这个对象给出了绑定关系,我们可以使用del语句将绑定关系解除。

明白了名称空间是用于存储对象和名字绑定关系的地方,那么接下来就可以细致了解一下名称空间可以分为哪几类了:

1)内置名称空间 —— 用于存放各种内置函数(built-in functions)、内置模块(built-in modules),例如abs()就是内置函数,内置名称空间可以在Python任何一处使用;

2)全局名称空间 —— 全局名称空间中的名字可以在同一个模块中任意处使用;

3)局部名称空间 —— 局部名称空间中的名字仅仅只能够在函数内部使用。

名称空间的意义

名称空间最大的作用就是防止名字重复造成的引用不当,我们可以在全局名称空间中定义一个a = 6同时也可以在局部名称空间中定义一个a = 7,这两者之间是不会产生任何冲突的,这就是名称空间最大的作用,防止名字重复造成的引用不当。

名称空间的查找顺序

知道了名称空间的意义,那么肯定会有读者意识到,我在全局定义一个a = 6,在局部定义一个a = 7,那么接下来调用a这个名字的时候,Python究竟会从哪个空间开始寻找a所对应的对象呢?

我只能说,这位读者你很上道,我们将以实例解答这个问题;

a = 6              # 在全局名称空间中定义一个a
b = 8            # 在全局名称空间中定义一个b,为了测验调用函数时能否找到全局中的b
def test():
    a = 7        # 在局部名称空间中定义一个a
    return a,b
print(test())
print(a)

(7,8)
6

从以上我们的测验中,调用函数test时输出的a将会是7,而当直接使用print(a)时输出的a将会是6。

所以我们可以大胆的下结论:

1)当调用函数的时候,函数寻找名字的顺序将会是 局部名称空间—>全局名称空间—>内置名称空间;

2)当没有调用函数,直接使用名字的时候查找顺序就是 全局名称空间 —>内置名称空间;

3)只要在某个名称空间(局部也好、全局也罢)中找到了对应的名字,就停止寻找;

4)在不同名称空间中定义相同名字是可行的,后续定义的并不会将原先覆盖掉。

局部名称空间详解

在局部名称空间中有一个非常神奇的事情,因为函数是可以相互嵌套的,在一个函数中嵌套另外一个函数是很正常的现象:

def test_1():           # 定义一个函数
    def test_2():       # 在test_1中定义一个嵌套函数
        print('球球好心人给个赞吧')
# 这是最简单的函数嵌套,
# 但也是最不规范的函数嵌套,
# 因为如果不改进的话,则无法使用嵌套的test_2函数

以上就是最简单形式的函数嵌套,那么问题接踵而至,上文中说过局部命中空间是在函数中产生的,那么如果我在一个函数中定义一个嵌套函数,是不是意味着我在局部名称空间中创建了一个局部名称空间?

对头!

但是在术语上我们会称test_2为最内部名称空间,而test_1则是被我们称为附属函数名称空间;

我们可以如此反复俄罗斯套娃:

def test_1():           # 定义一个函数
    def test_2():       # 在test_1中定义一个嵌套函数
        def test_3():   # 在内嵌函数test_2中再定义一个嵌套函数
            # 省略一万层....
            print('球球好心人给个赞吧')
        print('球球好心人给个赞吧')

嵌套函数中的查找顺序

在前文中已经介绍过了关于嵌套函数所产生的附属函数名称空间、内部名称空间,那么如果在附属函数名称空间和内部名称空间都定义一个相同名字,那么查找顺序是如何呢?

b = 10                    # 在全局定义一个b
def test_1():           # 定义一个函数
    def test_2():       # 在test_1中定义一个嵌套函数
        a = 6           # 在内部名称空间中定义一个a
        return a,b
    a = 7                # 在附属名称空间中定义一个a
    b = 8               # 在附属名称空间中定义一个b
​​​​​​​print(test_1())            # 调用函数

如果真的如上文中这样写的话,那么将不会输出任何结果哦,因为我们只调用了test_1,而作为嵌套的内部函数test_2没有被使用到,想要使用嵌套函数的话,就只能通过将嵌套函数作为返回值,返回出去

所以将代码修改一下

b = 10                    # 在全局定义一个b
def test_1():           # 定义一个函数
    def test_2():       # 在test_1中定义一个嵌套函数
        a = 6           # 在内部名称空间中定义一个a
        return a,b
    a = 7                # 在附属名称空间中定义一个a
    b = 8               # 在附属名称空间中定义一个b
    return test_2
print(test_1()())        # 调用函数

10
(6,8)

按照修改后,我们所得到的结果将会是6,当我们调用嵌套函数的时候,嵌套函数会从自身的局部空间中开始寻找是否有该名称

就像调用嵌套函数test_2一般,它从自己的局部名称空间开始寻找,找到了a = 6后就停止寻找

所以我们又可以下结论了:

1)当调用嵌套函数的时候,它的查找顺序是 内部名称空间—>附属函数名称空间—>全局名称空间—>内置名称空间;

2)找到对应的名字后就会停止寻找。

关于嵌套函数的使用

b = 10
def test_1():
    def test_2():
        a = 6
        return a,b
    a = 7
    b = 8
    return test_2
print(test_1()())                # 仔细看一下调用函数的过程

为什么调用函数过程中需要写两个括号test_1()(),而不是直接test_1()呢?

我们仔细看一下test_1函数的返回值,test_1的返回值是一个函数对象test_2,所以我们如果调用函数的话只写一个括号将会得到一个函数对象,也就是test_2

来实例示范一下;

b = 10
def test_1():
    def test_2():
        a = 6
        return a,b
    a = 7
    b = 8
    return test_2
print(test_1(), type(test_1()))        # 打印输出一下结果

<function test_1..test_2 at 0x000001A8E9981F30> <class ‘function’>
以上就是打印输出的结果,代表了函数对象

可能有读者想要唱反调了,我就是想直接写一个test_1()就能够直接得到想要的结果该怎么办呢?

我只能说,这位看官你很有成为天才的潜力,因为懒才是人类进步的基石,这个需求可以实现,但是我们要这样改代码:

b = 10
def test_1():
    def test_2():
        a = 6
        return a,b
    a = 7
    b = 8
    return test_2()
print(test_1())                # 调用函数

(6,8)

我们的确得到了想要的结果,仔细想一下为什么呢?

还是因为返回值,函数test_1的返回值是test_2(),也就是说返回的结果是函数test_2运行后的结果,又开始俄罗斯套娃了,我拿到的返回值是另外一个函数的返回值!?

我只能说没错,是这样的。

但是用这个方法需要注意一点,那就是内嵌函数必须是无参数的!

b = 10
def test_1():
    def test_2(c):           # 随便定义一个参数
        a = 6
        return a,b
    a = 7
    b = 8
    return test_2()
print(test_1())                # 调用函数

TypeError: test_1..test_2() missing 1 required positional argument: ‘c’
这将会报错,给出的错误是函数test_2()缺失一个名为’c’的位置参数
所以想要使用这种方法还是需要注意下的,
但是换另外一种思路,如果内嵌函数需要参数,那么我返回的时候先把参数定义不行么?
这种方法的确是可行的,
但是如果这样的话那不如直接使用默认参数,在定义的时候直接将参数c定义好

以上就是关于俄罗斯套娃的名称空间的讲解,接下来我们要介绍一下作用域了,如果能够将名称空间中的知识点李姐,那么作用域也不过尔尔。

作用域

什么是作用域

作用域是根据名称空间所产生的,意思就是名字的作用范围;

在上文之中我们其实已经或多或少涉及到了作用域了。

b = 10                            # 这个全局变量的作用域就是该模块中的全部范围
def test_1():
    def test_2():
        a = 6
        return a,b                # 正因为全局变量的作用于是全部范围,才能够返回b
    a = 7
    b = 8
    return test_2
print(test_1()())

或多或少读者对于这个作用域已经有些许了解,

我直接将结论摆出:

内置名称空间 —— 其作用域是Python中的所有模块,能够在所有的模块中使用;
全局名称空间 —— 其作用域是该模块的所有范围,能够在模块内随意使用;
局部名称空间 —— 其作用域仅仅在于该函数内部,只能够在函数内部使用。

可能正是因为作用域的不同,所以查找顺序也会不同,作用域越大的名称空间反而查找的优先级越低,

正如上文中的,即使全局和局部中都有a这个名字,调用函数的时候也会先从局部开始。

global语句

不同的名称空间可以定义相同的名字,这样不会有任何冲突,可这也意味着,当我们在局部名称空间的时候是无法修改全局中的名字绑定关系,于是Python提供了一个方法去解决这个问题:

a = 10               # 定义一个全局语句

def test():
    global a        # 使用global语句,声明我是用的名字a全局名称空间中的那个a
    a = 5
    return a

print(a)            # 先打印输出一下没有调用函数前的a是什么
print(test())        # 输出一下函数中的结果
print(a)            # 看一下全局中的a是否发生了改变

10
5
5

所以我们可以知道,使用global语句后,我们使用的名字都将会是全局名称空间的

nonlocal语句

既然在局部可以修改全局名称空间中的名字绑定关系,那么在内部名称空间是否可以修改附属函数名称空间中的绑定关系呢?

答案显然是可以的,但是需要使用到nonlocal语句。

def test_1():
    def test_2():				# 在test_1里定义一个嵌套函数test_2
        a = 15

        def test_3():			# 俄罗斯套娃一波
            nonlocal a			# 使用nonlocal,声明接下来使用的a是附属名称空间的a,所以究竟是test_2中的还是test_1中的?
            a = 5
            print('调用test_3对a这个名字的绑定关系进行更改')

        test_3()
        print(f'输出附属函数名称空间中的a:{a}')

    a = 10
    test_2()
    print(f'输出最外部函数的a值:{a}')

test_1()

调用test_3对a这个名字的绑定关系进行更改
输出附属函数名称空间中的a:5
输出最外部函数的a值:10

由此我们可以知道,在调用nonlocal声明使用的a是函数test_2中的,而不是test_1中的

因此我们可以得出的结论是:

1)在使用nonlocal语句的过程中,仅仅只会向上寻找到对应名字修改一次

题目题目

还是老规矩,写一个题目对上述内容进行测试

def discount(price,rate):
    final_price = price * rate
    old_price = 6
    print('old_price的值',old_price)
    return final_price

old_price = float(input('请输入价格'))
rate = float(input('请输入折扣率'))
print(discount(old_price,rate))
print('old_price的值',old_price)

在上述代码中,假设我输入的old_price是100,rate是0.6
那么请问两个问题

print(discount(old_price,rate))中会输出的值是多少?
print('old_price的值',old_price)输出的值是多少?

小结

  1. 命名空间是一个名字(变量)和对象的映射表。
  2. 作用域是指命名空间的作用范围,或者说管辖区域。
  3. 变量的查找遵循 LEGB 原则,先从基层(最内层函数找),然后到市委(外层函数)…,再到省委(模块命名空间),最后到中央(builtin 命名空间)。
  4. 各个命名空间相互独立,创建时间和生命周期各不相同。
  5. global 用于在函数内创建和修改全局变量。
  6. nonlocal 用于在内层函数修改外层函数局部变量。
  7. 没有声明 global 和 nonlocal,尝试修改全局变量或外层函数局部变量,实际上只会在函数或者内层函数创建一个新的局部变量,同名的全局变量或者外层函数局部变量不会受影响。

能够看到这里的都是一条汉子!

总结

到此这篇关于Python基础教程之名称空间以及作用域的文章就介绍到这了,更多相关Python名称空间及作用域内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python命名空间及作用域原理实例解析

    Python命名空间和作用域 总结 emmm,这一块讲了2个内容,一个是命名空间,一个是作用域.一个一个说吧 命名空间 A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries 上边这段是官方文档定义的,简单来说就是名字到对象的映射,我们知道,python的一切都是对象,你所定义的变量名其实只是个引用.这样就好理解了.python的

  • 解读Python编程中的命名空间与作用域

    变量是拥有匹配对象的名字(标识符).命名空间是一个包含了变量名称们(键)和它们各自相应的对象们(值)的字典. 一个Python表达式可以访问局部命名空间和全局命名空间里的变量.如果一个局部变量和一个全局变量重名,则局部变量会覆盖全局变量. 每个函数都有自己的命名空间.类的方法的作用域规则和通常函数的一样. Python会智能地猜测一个变量是局部的还是全局的,它假设任何在函数内赋值的变量都是局部的. 因此,如果要给全局变量在一个函数里赋值,必须使用global语句. global VarName的

  • 浅析Python的命名空间与作用域

    名称空间 名称空间(namespaces):用于存放名字与内存地址绑定关系的地方,是对栈区的划分 作用:名称空间可以使栈区中存放相同的名字,从而解决命名冲突 名称空间分为三种: 内置名称空间 全局名称空间 局部名称空间 内置名称空间 内置名称空间:用于存放Python解释器中内置的名字 生命周期:Python解释器启动则产生,Python解释器关闭则销毁 例如:print.input.int ... 全局名称空间 全局名称空间:运行顶级代码所产生的名字,或者说除函数内定义以及内置的外,剩下的都是

  • Python命名空间namespace及作用域原理解析

    曾经学C++的时候,经常听到这个名词,它主要是为了避免命名冲突而产生的. 就像有A(4个苹果),B(6个苹果)两个人,10个苹果,如果只标签了苹果,你无法判断哪个苹果是属于哪个人的,因为标签都是一样的:但是如果标签是A.苹果,B.苹果,那么是不是很容易就知道了苹果是谁的了. 命名空间:提供了一种从名称到对象的映射:主要是通过字典来实现的. 在python中,函数.模块等都有自己的命名空间: 局部命名空间(local namespace):即函数中定义的名称 -- 包括函数中的变量.参数.局部变量

  • Python进阶_关于命名空间与作用域(详解)

    写在前面 如非特别说明,下文均基于Python3 命名空间与作用于跟名字的绑定相关性很大,可以结合另一篇介绍Python名字.对象及其绑定的文章. 1. 命名空间 1.1 什么是命名空间 Namespace命名空间,也称名字空间,是从名字到对象的映射.Python中,大部分的命名空间都是由字典来实现的,但是本文的不会涉及命名空间的实现.命名空间的一大作用是避免名字冲突: def fun1(): i = 1 def fun2(): i = 2 同一个模块中的两个函数中,两个同名名字i之间绝没有任何

  • Python基础教程之名称空间以及作用域

    目录 前言 名称空间 什么是名称空间 名称空间的意义 名称空间的查找顺序 局部名称空间详解 嵌套函数中的查找顺序 关于嵌套函数的使用 作用域 什么是作用域 global语句 nonlocal语句 题目题目 小结 总结 前言 所谓“基础不狠,人站不稳”,对于任何一种编程语言来说基础往往都是重中之重,以Python为例,其中的两大分水岭就是函数编程和面向对象,而今天所要巩固的知识点后续会多次使用,那就是名称空间和作用域 名称空间 什么是名称空间 在Python中名称空间是用存储对象和名字绑定关系的地

  • 实例详解python函数的对象、函数嵌套、名称空间和作用域

    函数的对象 python中一切皆对象 函数对象的四大功能 引用 def f1(): print('from f1') f1() #调用函数 print(f1) print('*'*50) f = f1 # 将函数名f1赋值给f f() # f也可以调用函数 print(f) from f1 <function f1 at 0x000001FB05534620> ************************************************** from f1 <func

  • python 名称空间与作用域详情

    目录 一.名称空间 1.1 内置名称空间 1.2 全局名称空间 1.3 局部名称空间 1.4 加载顺序 1.5 查找顺序 二.作用域 2.1 全局作用域 2.2 局部作用域 2.4 函数对象+作用域应用 三.补充知识点 3.1 global关键字 3.2 nonlocal关键字 3.3 注意点 函数内部的函数只能在函数内部调用,不能在函数外部调用,通过接下来的学习你将会知道为什么会出现这种情况. 一.名称空间 名称空间(name spaces):在内存管理那一章节时,我们曾说到变量的创建其实就是

  • Python名称空间与作用域

    目录 一 名称空间 1.1 内建名称空间 1.2 全局名称空间 1.3 局部名称空间 二 作用域 2.1 全局作用域与局部作用域 2.2 作用域与名字查找的优先级 一 名称空间 名称空间即存放名字与对象映射/绑定关系的地方.对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系. ​ 在程序执行期间最多会存在三种名称空间 1.1 内建名称空间 伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放

  • python 名称空间与作用域详情

    目录 一.名称空间 1.1 内置名称空间 1.2 全局名称空间 1.3 局部名称空间 1.4 加载顺序 1.5 查找顺序 二.作用域 2.1 全局作用域 2.2 局部作用域 2.4 函数对象+作用域应用 三.补充知识点 3.1 global关键字 3.2 nonlocal关键字 3.3 注意点 函数内部的函数只能在函数内部调用,不能在函数外部调用,通过接下来的学习你将会知道为什么会出现这种情况. 一.名称空间 名称空间(name spaces):在内存管理那一章节时,我们曾说到变量的创建其实就是

  • Python 基础教程之闭包的使用方法

    Python 基础教程之闭包的使用方法 前言: 闭包(closure)是函数式编程的重要的语法结构.函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式).在面向过程编程中,我们见到过函数(function):在面向对象编程中,我们见过对象(object).函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability).闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性. 不同的语言实现闭包的方式不同.Python以函数对象为基础,为闭

  • Python基础教程之内置函数locals()和globals()用法分析

    本文实例讲述了Python基础教程之内置函数locals()和globals()用法.分享给大家供大家参考,具体如下: 1. 这两个函数主要提供,基于字典的访问局部变量和全局变量的方式. python 使用叫做名字空间的东西来记录变量的轨迹.名字空间是一个字典 ,它的键就是字符串形式的变量名字,它的值就是变量的实际值. 名字空间可以像 Python 的 dictionary 一样进行访问. 在一个 Python 程序中的任何一个地方,都存在几个可用的名字空间. 每个函数都有着自已的名字空间,叫做

  • python基础教程项目三之万能的XML

    这个项目的名称与其叫做万能的XML不如叫做自动构建网站,根据一份XML文件,生成对应目录结构的网站,不过只有html还是太过于简单了,如果要是可以连带生成css那就比较强大了.这个有待后续研发,先来研究下怎么html网站结构. 既然是通过XML结构生成网站,那所有的事情都应该由这个XML文件来.先来看下这个XML文件,website.xml: <website> <page name="index" title="Home page"> &l

  • Python基础教程之错误和异常的处理方法

    目录 前言 异常与错误的概念 错误分类 语法错误 运行时错误 逻辑错误 异常处理机制 捕获异常并处理 主动抛出异常 断言处理 自定义异常类 常见异常及释义 附:用户自定义异常实例 总结 前言 继续更新Python基础语法,到目前为止Python基础语法已经接近了尾声,本次错误与异常处理更新完后会对文件.数据库.包管理.模块管理.正则表达式的使用进行更新.完成这几个大致的任务之后将会更新爬虫与数据分析.本专栏所写的东西非常的适用初学者,当然也非常适合老手,每一部分都有很全面的编程技巧.相信大家看完

  • Python基础教程之异常处理详解

    目录 前言 异常 错误与异常 语法错误 异常 异常处理 自主抛出异常 自定义异常 finally子句 总结 前言 最近发现有些东西长时间不用就要忘了,坚持每天复习总结一个小知识点吧~ 异常是什么呢?就是在代码执行过程中非预期的执行结果,随着代码越来越复杂,代码中的执行逻辑也会越来越复杂,如果没有处理好异常情况,很有可能造成软件执行错误,导致重大损失.相反,如果合理的处理异常情况,则可以增强软件的稳定性,提高体验感. 异常 在Python中,使用异常对象(exception object)来表示代

随机推荐