可远观而不可亵玩焉:Python 中的自由变量

这篇具有很好参考价值的文章主要介绍了可远观而不可亵玩焉:Python 中的自由变量。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考

项目 描述
搜索引擎 Google 、Bing
Python 官方文档
哔哩哔哩 【python】定义好的变量读不出来?详解全局变量和自由变量的使用!

描述

项目 描述
PyCharm 2023.1 (Professional Edition)
Python 3.10.6

变量的三种姿态

全局变量

在 Python 中,全局变量是在程序的 顶层(顶层是指程序中最外层的作用域或命名空间,即全局作用域) 定义的变量,可以在整个程序的任何位置访问。它们不局限于特定的函数或类,而是在整个程序的作用域内可见。

举个栗子

# 全局作用域中定义变量 global_var
global_var = 'Hello World'


def func():
    # 尝试在局部作用域中访问全局变量
    print(global_var)


func()

执行效果

Hello World

虽然 全局变量可以在程序中随处访问,但过度使用全局变量可能会导致代码难以维护和调试。因此,在设计程序时,减少全局变量的使用,尽量使用函数参数和返回值来传递和获取需要共享的数据。

注:

在 Python 中,内层作用域可以访问外层作用域中的变量,而外层作用域不能直接访问内层作用域中的变量。

自由变量

Python 中,自由变量是在函数内部引用但没有在函数内部定义的变量,它们在函数外部的作用域中定义。自由变量可以是全局变量或局部变量。

当在函数内部引用一个变量时,Python 首先在函数的局部作用域中查找该变量。如果没有找到,它会继续在外部作用域中查找。这种查找过程会持续到 找到变量 或达到全局作用域(更准确来说,应该是达到 内置作用域)。

举个栗子

def external_func():
    free_var = 'Hello World'

    def internal_func():
        # 未定义 free_var 变量,但在该作用域中
        # 使用了该变量,故 free_var 为自由变量
        print(free_var)

    # 调用 internal_func() 函数
    internal_func()


external_func()

执行效果

Hello World
遮盖现象

遮盖现象(Name Shadowing)在作用域的层级结构(作用域间的嵌套关系)中发生。当在 较内层(相对于引用自由变量的作用域而言)作用域 中定义一个与 较外层作用域 相同名称的变量时,它会 遮盖 较外层作用域中的同名变量。这样,在较内层作用域中使用该变量时,实际上引用的是较内层作用域中的变量,而不是外层作用域中的变量。对此,请参考如下示例:

# 较外层作用域
# (相对于 internal_func 所在的作用域而言)
free_var = 'Hello World'


def external_func():
    # 较内层作用域
    # (相对于 internal_func 所在的作用域而言)
    free_var = 'Hello China'

    def internal_func():
        # 如果在当前作用域中定义了 free_var 
        # 变量,则将使用当前作用域中的 free_var 的值
        print(free_var)

    internal_func()

执行效果

Hello China

局部变量

在 Python 中,局部变量是在特定作用域可见和可访问的变量。它们是在函数、类或某个特定代码块中定义的变量,只能在其所在的作用域内使用

当在函数内部定义一个变量时,它被视为该函数的局部变量,只能在该函数内部使用。当函数执行完毕或退出时,局部变量的 生命周期 也会结束。

举个栗子

def func():
    # 定义局部变量 local_var
    local_var = 'Hello World'
    # 在局部作用域内访问局部变量
    print(local_var)
    

func()

执行效果

Hello World

注:

无法在函数外部访问局部变量。如果你尝试这样做,Python 将抛出异常错误信息。

UnboundLocalError

UnboundLocalError 异常错误

考虑如下代码:

func1()
free_var = 'Hello World'


def func1():
    print(free_var)
    

func1()

在上述代码的执行过程中,当函数被调用后 func1() 后,控制台中将正确输出结果 Hello World

func2()
free_var = 'Hello World'


def func2():
    print(free_var)
    # 尝试为 free_var 变量进行赋值操作
    free_var = 'Hello China'


func2()

执行上述代码将产生异常错误信息,其中包含如下内容:

UnboundLocalError: local variable 'free_var' referenced before assignment

UnboundLocalError: local variable 'free_var' referenced before assignment 是 Python 提供的一个异常错误,它表示在局部作用域中引用了一个在使用之前未被赋值的变量。

字节码与 dis.dis()

字节码

在 Python 中,字节码 是一种中间形式的指令集,它由 Python 解释器执行。Python 源代码在运行之前会被编译为字节码,然后由解释器逐条执行字节码指令。

dis.dis()

dis.dis()函数是 Python 标准库 中的一个函数,用于对给定的函数或方法的字节码进行 反汇编操作。它可以用来查看函数的字节码指令,帮助我们理解代码的执行过程和优化代码

dis.dis(x=None, *, file=None, depth=None)

注:

参数列表中的 * 表示该符号后续的参数必须通过关键字的方式进行传递。

其中:

参数 作用
x 要进行反汇编的函数对象或代码对象。可以是函数、方法、类、生成器、源代码字符串等对象。
file 将反汇编结果输出至该参数提供的文件对象中。如果未指定文件对象,则默认输出到标准输出对象( sys.stdin )中。
depth 控制反汇编的深度。如果为 None(默认值),则对所有嵌套的代码对象进行反汇编。如果为 正整数,表示限制反汇编的嵌套深度。
尝试对 func1() 进行反汇编操作
from dis import dis


def func1():
    print(free_var)


dis(func1)

执行效果

  5           0 LOAD_GLOBAL              0 (print)
              2 LOAD_GLOBAL              1 (free_var)
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

让我们逐条解释这些字节码指令的含义和作用:

  1. LOAD_GLOBAL 0 (print)
    这条指令将全局命名空间中的 print 对象作为操作数压入 (栈是一种存储结构,具有 First In Last Out 的特点)中。

  2. LOAD_GLOBAL 1 (free_var)
    这条指令将全局命名空间中的名称free_var加载到栈上。它将全局命名空间中的free_var对象作为操作数压入栈中。

  3. CALL_FUNCTION 1
    这条指令调用位于栈顶的函数对象,并传递给定数量的参数。在这里,函数对象是 print,参数数量为 1。因此,它将栈顶的 1 个参数,即 free_var 传递给 print 函数并对其进行调用。

  4. POP_TOP
    这条指令从栈顶弹出一个对象并丢弃。在这里,它将弹出 print 函数调用后返回的结果。

  5. LOAD_CONST 0 (None)
    这条指令将常量 None 加载到栈上。它将 None 对象作为操作数压入栈中。

  6. RETURN_VALUE
    这条指令将栈顶的值作为函数的返回值,并结束函数的执行。在这里,它将栈顶的 None 对象作为函数的返回值。

尝试对 func2() 进行反汇编操作
from dis import dis


def func1():
    print(free_var)
    free_var = 'Hello China'


dis(func1)

执行效果

  5           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (free_var)
              4 CALL_FUNCTION            1
              6 POP_TOP

  6           8 LOAD_CONST               1 ('Hello China')
             10 STORE_FAST               0 (free_var)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

让我们逐条解释这些字节码指令的含义和作用:

对于源代码中第五行代码所对应的字节码有:

  • LOAD_GLOBAL 0 (print)
    这条指令将全局命名空间中的 print 对象加载到栈上。

  • LOAD_FAST 0 (free_var)
    这条指令将函数的局部变量 free_var 加载到栈上。

  • CALL_FUNCTION 1
    这条指令调用栈顶的函数对象,传递给定数量的参数。在这里,函数对象是 print,参数数量为 1。于是,Python 将栈顶的 1 个参数(即 free_var)传递给 print 函数进行调用。

  • POP_TOP
    这条指令从栈顶弹出一个对象并丢弃。在这里,它将弹出 print 函数调用后返回的结果。

对于源代码中第五行代码所对应的字节码有:

  • LOAD_CONST 1 ('Hello China')
    这条指令将常量 'Hello China' 加载到栈上。

  • STORE_FAST 0 (free_var)
    这条指令将栈顶的值(即 'Hello China')存储到函数的局部变量 free_var 中。

  • LOAD_CONST 0 (None): 这条指令将常量 None 加载到栈上。

  • RETURN_VALUE
    这条指令将栈顶的值作为函数的返回值,并结束函数的执行。在这里,它将栈顶的 None 对象作为函数的返回值。

注意:这是字节码指令的解释,它展示了代码在底层的执行过程。对于了解

UnboundLocalError 异常错误抛出的原因

比较 func1()func2() 函数的字节码可以发现,func1 加载 free_var 变量时使用的字节码指令为 LOAD_GLOBAL,而在 func2 加载 free_var 变量时使用的字节指令为 LOAD_FAST
func1() 尝试从全局变量中加载 free_var 变量,而 func2 则尝试从局部变量中加载 free_var 变量。由于 func2 在局部变量定义前使用了局部变量,由此产生了 UnboundLocalError 异常错误。

产生上述现象的原因是:在默认情况下,Python 仅允许局部作用域访问自由变量,但不支持修改自由变量的值

Python 默认不支持修改自由变量所带来的优点:

优点 描述
避免意外的行为 禁止函数直接修改自由变量可以减少程序中的不确定性和难以调试的错误。
明确作用域边界 限制自由变量的修改有助于明确作用域的边界,使代码 更易于理解和维护。变量的修改仅发生在特定作用域内部,不会影响外部作用域。
保护全局状态 限制函数对全局变量的直接修改可以保护 全局状态的一致性,提高代码的可维护性。全局变量是在全局作用域中定义的,可以被程序的任何部分访问和修改。
函数封装和代码重用 限制函数对自由变量的修改有助于实现函数的 封装性 和代码的 重用性,促进 模块化 的开发方式。

如果你需要修改自由变量的值,需要使用 globalnonlocal 向 Python 声明这一点。

global 与 nonlocal 关键字

global 关键字

在 Python 中,global 是一个关键字,用于在函数内部访问和修改全局变量。

当在函数内部需要修改(也可以只访问不修改)全局作用域中定义的变量时,需要使用 global 关键字来声明该变量为全局变量。这样可以告诉 Python 解释器在函数内部使用该变量时,不要创建一个同名的局部变量,而是直接使用全局作用域中的变量。对此,请参考如下示例:

free_var = 'Hello World'


def func():
    global free_var
    # 尝试访问全局变量 free_var
    print(free_var)
    # 尝试修改全局变量 free_var
    free_var = 'Hello China'


func()

# 函数内部对 free_var 的更改将
# 反应到全局作用域中。
print(free_var)

执行效果

Hello World
Hello China

注:

  1. global 关键字能够在全局作用域中使用。但在全局作用域中使用 global 关键字是没有意义的,并不会导致语法错误的产生。

  2. 在使用 global 关键字将一个变量声明为全局变量时,该变量可以不存在,但在尝试访问或修改该变量时,若该变量仍未在全局作用域中创建,则 Python 将抛出 NameError 异常错误信息。对此,请参考如下示例:

    def func():
        # 此时虽未在全局作用域中定义 free_var 全局变量
        # ,但仍可以将 free_var 声明为一个全局变量。
        # 在后续访问该变量时将直接从全局作用域中寻找该变量。
        global free_var
    
        # 若在全局作用域中为定义相关的全局变量,
        # 尝试访问或修改变量将导致 NameError 异常错误。
        # 此时,若执行如下语句将导致 NameError 异常错误。
        # print(free_var)
    
    
    func()
    

    也就是说,global 关键字的作用仅仅是告诉 Python 解释器应该在全局作用域中寻找某一个变量而不会检查该变量是否在全局作用域中定义。

  3. 如果你使用 global 关键字将一个变量声明为全局变量,那么你不能在 global 关键字之前尝试访问或修改该变量。否则,Python 将抛出异常错误。对此,请参考如下示例:

    free_var = 'Hello World'
    
    
    def func():
        # 在 global 关键字前尝试访问或修改其
        # 所声明的全局变量都将导致 SyntaxError。
        # Python 执行到下一行代码时将抛出 SyntaxError。
        print(free_var)
        global free_var
        print(free_var)
        free_var = 'Hello China'
    
    
    func()
    
    print(free_var)
    

nonlocal 关键字

当在一个 嵌套函数(位于一个函数中内部的函数被称为嵌套函数) 中需要访问并修改位于外部函数作用域的变量时,如果该变量不是 全局变量,而是外部函数的局部变量或自由变量,就需要使用 nonlocal 关键字。通过使用 nonlocal 关键字,我们可以显式地声明一个变量为非局部的,从而允许在 嵌套函数 中修改该变量的值。对此,请参考如下示例:

def external_func():
    free_var = 'Hello World'

    def internal_func():
        nonlocal free_var
        # 尝试访问非全局变量的自由变量
        print(free_var)
        # 尝试修改非全局变量的自由变量
        free_var = 'Hello China'

    # 执行嵌套函数 internal_func()
    internal_func()

    # 嵌套函数对非全局变量的自由变量的修改将反映
    # 到该变量所处的局部作用域中。
    print(free_var)


external_func()

执行效果

Hello World
Hello China

注:

nonlocal 关键字的限制与 global 关键字的限制大致相同,存在如下不同:

  1. nonlocal 关键字不能在全局作用域中使用。否则,Python 将抛出 SyntaxError 异常错误。

  2. 在函数定义过程中, nonlocal 所声明的非全局变量的自由变量必须已经存在。否则,Python 将抛出 SyntaxError 异常错误。对此,请参考如下示例:

def external_func():

    def internal_func():
        # 执行下一行代码时将产生 SyntaxError 异常错误。
        # Python 会检查是否存在由 nonlocal 
        # 关键字声明的非全局变量的自由变量。
        nonlocal free_var

    internal_func()


external_func()

不允许修改自由变量所指向的值还是其保存的引用?

可变对象与不可变对象

在 Python 中,对象分为 可变对象(Mutable Objects)不可变对象(Immutable Objects) 两种类型。

可变对象

可变对象是指创建后其内部状态可以被修改的对象。常见的可变对象包括列表(list)、字典(dict)和集合(set)等。当对可变对象进行操作时,其 内部的状态可以发生改变,而对象本身的 身份(Identity,对象的身份即该对象在内存中的存储地址)不会发生改变。对此,请参考如下示例:

arr = [1, 2, 3, 4, 5, 6]

# 尝试输出可变对象 arr 的身份标识
print(id(arr))

# 对可变对象进行修改
arr.pop()

# 尝试输出可变对象 arr 的身份标识
print(id(arr))

执行效果

由于对象的身份标识是一个内存地址。因此,在不同的计算机中输出的结果存在极大的概率是不相同的。但可以肯定的是,两次输出的身份标识的值是相同的。

2609552406912
2609552406912
不可变对象

不可变对象是指创建后其内部状态不可被修改的对象。常见的不可变对象包括整数(int)、浮点数(float)、布尔值(bool)、字符串(str)和元组(tuple)等。当对不可变对象进行操作时,将 重新创建一个对象,该对象的身份表示与原对象不同。

think = 'Hello World'

# 尝试输出不可变对象 think 的身份标识
print(id(think))

think += '\nHello China'

# 尝试输出不可变对象 think 的身份标识
print(id(think))

执行效果

由于修改不可变对象将创建一个新的对象,因此标识符所关联的 引用(也即内存地址) 也发生了变化。

2629283135216
2629283524688

揭晓答案

当在函数内部修改自由变量时,Python 不允许 直接修改自由变量所保存的引用(内存地址),即 将自由变量绑定到一个新的对象。如果要修改自由变量所保存的引用,需要使用 globalnonlocal 关键字来声明变量,并在其后进行修改操作。

然而,Python 允许 修改自由变量所指向的值,即 修改对象内部的状态而不改变对象本身的引用。这意味着,对于可变对象(如列表、字典等),可以通过引用修改其内部的值,而无需使用 globalnonlocal 关键字。但对于不可变对象(如整数、字符串等),无法修改其值,每次操作都会返回一个新的对象。

free_var = list('Hello ')


def func():
    # 尝试在未使用 global 或 nonlocal 关键字
    # 的情况下对可变对象进行修改。
    free_var.extend(list('World'))


func()
# 使用空字符串将列表中的所有元素连接为一个字符串
print(''.join(free_var))

执行效果文章来源地址https://www.toymoban.com/news/detail-455243.html

Hello World

到了这里,关于可远观而不可亵玩焉:Python 中的自由变量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • websockets,一个不可思议的 Python 库!

    📚 个人网站:ipengtao.com 大家好,今天为大家分享一个不可思议的 Python 库 - websockets。 Github地址:https://github.com/python-websockets/websockets WebSocket是一种在现代Web开发中变得越来越重要的协议。它允许客户端和服务器之间建立持久的双向通信,使得实时应用程序(如在线聊天、实

    2024年02月01日
    浏览(47)
  • Python 教学 | Pandas 妙不可言的条件数据筛选

    目录 Part 1  前言 Part 2  Excel 的数据筛选与分布统计 Part 3  Pandas 条件数据筛选 1、条件数据筛选的不同维度 (1) 比较数据值 (2) 是否为空值 (3) 文本内容筛选 (4) 数据值长度 (5) 日期筛选 (6) 其他 2、复合条件筛选 Part 4  总结 Part 5  Python教程 在 Python 中,第三方库 Pandas 是数据清

    2024年02月10日
    浏览(40)
  • 17行python代码,openai帮你实现下班自由

    chatgpt最近火到不行,AI受到了前所未有的关注,openai作为开发团队不仅仅开发了一个在线尝鲜的聊天机器人,也提供API并且提供了python语言的的pypi库。 火出圈的聊天机器人是chatgpt3,既然排行老三,就说明这个张飞的前面还有大哥刘玄德和二哥关云长,当年一起桃园结义……

    2024年02月01日
    浏览(52)
  • 【Python项目实战】京东自动抢茅台脚本,此项目不可商用,仅为Python练手使用!

    目前,在多家电商平台都可以抢购茅台酒,包括天猫超市、京东、天猫会员店、国美、苏宁、网易严选等渠道,消费者使用一台手机便可参与抢购,不过,很多消费者依旧不清楚用手机抢茅台怎么抢,因为抢购的人实在太多,需要有技巧才能提高成功抢购的概率。 今天给大家

    2024年02月13日
    浏览(46)
  • Python自由职业可以做什么?副业月入3000的快乐你根本想象不到

    很多有时间的程序员都会在业余时间接一些“私活”,也就是我们说的副业! 毕竟虽然程序员加班时间长,但是也不是所有程序员都是需要997的...许多事业编制或者说一部分公司并不会出现特别夸张的加班时长。平常周末的时候也就会接一些副业,在得到一些小收入的同时还

    2024年02月07日
    浏览(39)
  • 【npm link】Node命令中的npm link命令的使用,还有CLI全局命令的使用,开发命令行工具必不可少的部分

    😁 作者简介:一名大四的学生,致力学习前端开发技术 ⭐️个人主页:夜宵饽饽的主页 ❔ 系列专栏:NodeJs 👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气 ​🔥​前言: 本文是关于Node命令中的npm link命令的详细使用,还有脚手架的背后原理,如

    2024年01月16日
    浏览(46)
  • Python入门教程50:Pycharm中鼠标滚动,如何实现字体大小自由的缩放

    ★★★★★博文创作不易,我的博文不需要打赏,也不需要知识付费,可以白嫖学习编程小技巧。使用代码的过程中,如有疑问的地方,欢迎大家指正留言交流。喜欢的老铁可以多多点赞+收藏分享+置顶,小红牛在此表示感谢。★★★★★ ↓ 视频教程如下 ↓ 11.Pycharm中鼠标滚

    2024年02月03日
    浏览(60)
  • 14种混沌映射,python代码,可自由切换,以鲸鱼和蜣螂算法为例,方便应用于所有算法...

    “  本期采用PYTHON代码实现14种常见的和不常见的混沌映射用于优化群智能算法,作者写好了一个Chaos类,方便调用,代码可一键切换,可用于所有智能算法优化,本篇文章以鲸鱼和蜣螂算法为例进行介绍 ” 本文涉及14种混沌映射算法,用于在初始化智能算法粒子时使用,1

    2024年04月13日
    浏览(50)
  • python实例100第20例:一球从100米高度自由落下,每次落地后反跳回原高度的一半的问题

    题目: 一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?

    2024年01月16日
    浏览(42)
  • 自由人数藏/自由人数字藏品/自由人nft

    今天我们就从三个角度来简单分析一下数字产品抢购指南。一,艺术价值虽说各花入各眼,从博物馆收藏的历代文物,数字产品,到现代性的人员看下塑缝艺术的流行,数字产品的艺术审美是各异的。在评价一件数字产品的艺术价值时,可可以从这件作品的创作故事,历史背

    2024年02月15日
    浏览(67)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包