彻底理解python中函数内赋值操作和对象的可变性

来源:互联网 发布:西部世界解析 知乎 编辑:程序博客网 时间:2024/05/19 18:41

一.函数内赋值操作

1.API中术语

    1.--- 4.6. Defining Functions
        实参在传递时会被引入被调函数的局部符号表
    2.--- 4.6. Defining Functions
        函数中所有变量的赋值会把值存储在局部符号表中
        引用变量时查找顺序: 当前局部符号表 --> 外层封闭函数局部符号表 --> 全局符号表 --> 内置符号表
    3.--- 4.2. Naming and binding
        在块内被绑定的变量是该块内的局部变量
        在模块级被绑定的变量时全局变量
        如果一个变量在某块内使用,但未在该块内定义,该变量是自由变量
def f1(a):
    print(a)


b = 3
f1(b)
1.b = 3,b在模块级绑定,即b是全局变量,把值存储在全局符号表
2.f(b)调用函数f(b),实参b被引入函数f1的局部符号表
    1.函数块内赋值b = a = int型对象3的地址值,存储在该函数的局部符号表中
3.print(a)引用变量a,在函数f1的局部符号表找到a的值,根据该值访问int型对象3
-------------------------------------------------------------------------------------------------------------
def f1(a):
    a = 4
    print(a)
b = 3
f(b)
1.b = 3,b在模块级绑定,即b是全局变量,把值存储在全局符号表
2.f1(b),调用函数f(b),实参b被引入函数f1的局部符号表
    1.函数块内赋值b = a = int型对象3的地址值,存储在该函数的局部符号表中
3.a = 4,函数块内赋值,值(int型对象4的地址值)存储在该函数的局部符号表
    1.此时a存储在局部符号表中的值由int型对象3的地址值变成了int型对象4的地址值,即变量a所指向的对象改变
4.print(a),引用变量a,在函数f1的局部符号表中找到a的值,根据该值访问int型对象4
-------------------------------------------------------------------------------------------------------------
def f1(a):
    print(id([0])) #1568742546
    a[0] = 4
    print(id(a[0])) #2564578412
    print(a)
b = [3]
f1(b)
1.b = [3],b在模块级绑定,即b是全局变量,把值存储在全局符号表
2.f(b),调用函数f(b),实参b被引入函数f1的局部符号表
    1.函数块内赋值b = a = 列表对象[3]的地址值,存储在该函数的局部符号表中
3.print(id(a[0])),引用变量
    1.首先在函数f1的局部符号表中找到列表对象a的地址值,根据该地址值访问列表对象[3]
    2.然后根据该下标访问列表元素a[0]
        1.列表中元素不是直接存储在列表中(即列表不是一块连续的内存空间),列表中有一个指向存储各元素对象地址值的数组的引用,通过该引用(数组的首地址)和下标找到(计算)数组中对应元素的地址,根据该地址访问元素对象实体
            >>> num = [1, 2, 3]
            >>> id(num)
            2284487238152
            >>> id(num[0])
            1922872384
            >>> id(num[1])
            1922872416
            >>> id(num[2])
            1922872448
            >>>
            ----------------
            id(num[2]) - id(num[1]) = id(num[1]) - id(num[0]) = 32 --- 说明是顺序存储结构
4.a[0] = 4,修改元素值,实际上是改变了列表中所引用数组指定位置存储的地址值,即新创建的int型对象4的地址值
5.print(id(a[0])),与3中输出的地址值不同,也说明了4中的观点
6.print(a),引用变量,在函数f1局部符号表中找到变量a的值,根据该值访问列表对象[4]
-------------------------------------------------------------------------------------------------------------
def f1(a):
    print(id(a)) #2284487238728
    a = [4] --- 覆盖,变量a原来存储到局部符号表中的值(名称num1绑定的列表对象的的地址值)被新值(列表对象[4]的地址值)覆盖
    print(id(a)) #2284487238152
    print(a)
b = [3]
f1(b)
1.b = [3],b在模块级绑定,即b是全局变量,把值存储在全局符号表
2.f(b),调用函数f(b),实参b被引入函数f1的局部符号表
    1.函数块内赋值b = a = 列表对象[3]的地址值,存储在该函数的局部符号表中
3.print(id(a)),引用变量,在函数f1局部符号表中找到a的值,根据该值访问队列表对象[3]
4.a = [4]函数内赋值,值(列表对象[4]的地址值)存储在该函数的局部符号表
    1.此时存储在局部符号表中的a的值由列表对象[3]的地址值变成了列表对象[4]的地址值,即变量a的指向改变
5.print(id(a)),引用变量,在函数f1局部符号表中找到a的值,根据该值访问列表对象[4]

6.print(a),同5


=======================================================================================

二.当可变对象的元素是不可变对象(数字, 元组, 字符串, bytes等),在修改元素值时似乎这些不可变对象可以被修改 --- 事实并非如此,这里的改变实际上是改变了列表内部数组存储对应元素对象的位置上的地址值,即改变了指向的对象

num = ["123", "22", "3"]
def f():
    print(type(num[0])) #<class 'str'>  --- 字符串
    print(id(num[0])) #2314830389296
    num[0] = "22"                        #--- 修改字符串
    print(id(num[0])) #2314830389352
    num[0][0] = "7" #TypeError: 'str' object does not support item assignment
f()
1.print(type(num[0]))说明列表元素对象为字符串类型
2.print(id(num[0])) #2314830389296
3.num[0] = "2" 改变列表元素的值,单从表面看似乎是对字符串进行修改,但是字符串不是不可变对象吗?
4.print(id(num[0])) #2314830389352,对比2可以发现实际上是改变了列表内部数组中对应位置上存储列表元素对象的地址值,由原来的字符串对象"123"的地址值,变成了"22"的地址值
    1.Note: 对于已存在的不可变对象,python不会重新创建,即此时列表对象内部数组中保存第一个元素和第二个元素地址值的位置上的值相等,都指向字符串对象"22"
5.num[0][0] = "7"#TypeError: 'str' object does not support item assignment, 可以发现字符串对象确实是不可变对象


0 0
原创粉丝点击