Python笔记:变量与函数内存深度解析

第14节课 变量与函数内存分析

1.Python中变量的本质

  • 静态编译型语言:C、C++、Java
  • 编译型:源代码不能直接交给计算机运行,必须先编译,生成可执行的二进制文件,再将该二进制文件交给计算机执行。C/C++编译的二进制结果 .exe文件,Java编译的二进制结果 .clss文件。【将一本英语书翻译为中文书】
  • 静态:变量的定义必须声明数据类型,而且基本数据类型的数据是直接存储在变量内部的!(C\C++的指针和Java中的引用数据类型除外)
  • 动态解释型语言:Python、JS
  • 解释型:源代码依旧不能直接交给计算机运行,也是必须先编译,但是可以不生成二进制文件,二进制的代码在内存中临时存储的,运行的时候就是将二进制代码按照编译的顺序依次执行。【同声传译】
  • 动态:变量的定义不需要声明数据类型,数据存储在堆内存中的,变量仅存储数据对象在堆内存中的地址而已。
  • # Java
    public class Demo {
        public static void main(String[] args) {
            int a = 1231231231; //范围没超
            int b = 100;        //范围没超
            int c = a * b;      //暂时不确定 只有运行的时候才能确定大小
            // 整型溢出
            System.out.println(a * b);
        }
    }
    # C
    #include<stdio.h>
    #include<stdio.h>
    void main(){
        // overflow in implicit constant conversion
        // 存不下了
        int a = 123123123123;
        printf("%d\n", a);
    }
    

    image-20250323221049181

    num = 1
    print(num)
    print(type(num)) # <class 'int'>
    print(id(num))   # 140731596990904
    
    num = 3.14
    print(num)
    print(type(num)) # <class 'float'>
    print(id(num))   # 1998567916656
    
    num = "Hello"
    print(num)
    print(type(num)) # <class 'str'>
    print(id(num))   # 1998567809472
    
    xixi = num
    # 假设xixi和num都嗝屁了 上述三个数据对象怎么办?
    # 没有被任何变量指向的对象,则成为垃圾,则被GC垃圾回收器自动回收
    

    image-20250323221908215

    print("Hello")
    haha = print
    haha("World")
    print = 1
    haha(1 + 2)
    haha(print)
    print(1 + 2) # TypeError: 'int' object is not callable
    
    """
    使用type()查看类型 id()查看地址
    """
    

    image-20250323222922360

    总而言之,在Python中,变量(包含函数名)一律存储的数据对象在堆内存中的地址!

    def show(a, b):
        print(a + b)
        return
    test = show
    print(test(1,2))
    

    2.常量驻留问题

    常量驻留是一种优化机制,它允许Python在内存中之保留一份特定值的副本,而多个变量可以引用这个相同的副本。这样做的目的,为了节省内存空间,并提高某些操作的效率。

  • 小整数驻留问题
  • >>> a = 1
    >>> b = 1
    >>> a == b # 指向的对象内容是否一致
    True
    >>> a is b # 指向的是否是同一个对象
    True
    

    解释:a变量和b变量存储的是数据对象1的内存地址,只有一个1的数据对象。

    image-20250324210258151

    >>> a = 300
    >>> b = 300
    >>> a == b
    True
    >>> a is b
    False
    

    解释:a变量和b变量存储的是数据对象300的内存地址,有两个300的数据对象。

    image-20250324210441855

    Python会为范围在[-5,256]之间的整数自动进行常量驻留。这就意味着,当你创建多个值在次范围内的话,他们实际上引用的是同一个数据对象。

    上述的演示,是在控制台交互模式下的效果

    在脚本模式下,有体现出了不一样的结果:

    a = 1
    b = 1
    print(a == b)
    print(a is b)
    
    a = 300
    b = 300
    print(a == b)
    print(a is b)
    """
    True
    True
    True
    True
    """
    

    脚本当中,两个300是同一个数据对象,为什么呢?因为会有潜在的优化问题。

    >>> id(-5)
    140732088707320
    >>> id(-4)
    140732088707352
    >>> id(-3)
    140732088707384
    >>> id(254)
    140732088715608
    >>> id(255)
    140732088715640
    >>> id(256)
    140732088715672
    >>> id(300)
    1267470938224
    >>> id(300)
    1267470938224
    >>> num = 400
    >>> id(num)
    1267473808912
    >>> num2 = 400
    >>> id(num2)
    1267473808880
    

    可以看到,自带驻留常量的地址是连续的,重新创建的再范围之外的常量,地址有可能连续也有可能不连续,但是跟常量驻留的地址连续吗?反证出300、400是新建的。

    a = 300
    b = 300
    print(id(a))
    print(id(b))
    """
    3110779789776
    3110779789776
    """
    
  • 字符串驻留问题
  • 对于字符串而言,Python也会对一些符合特定条件的字符串进行常量驻留,字符串常量只能包含字母、下划线、数字,并且在编译期间就能确定其值的字符串。

    >>> s1 = "HelloWorld"
    >>> s2 = "HelloWorld"
    >>> s1 == s2
    True
    >>> s1 is s2
    True
    

    由于下面的字符串当中出现了空格,所以则不会进行常量驻留,就是两个字符串对象,只不过内容相同而已。

    >>> s1 = "Hello Python"
    >>> s2 = "Hello Python"
    >>> s1 == s2
    True
    >>> s1 is s2
    False
    

    s1的值在代码编辑期间,甚至到编译的时候,它的值就已经确定了,s2的值只有程序执行的时候才能确定。虽然s1和s2最终的内容是一样的,但是s2是运行期间创建的,它是另外一个数据对象。

    >>> s1 = "HelloWorld"
    >>> a = "Hello"
    >>> b = "World"
    >>> s2 = a + b
    >>> s1 == s2
    True
    >>> s1 is s2
    False
    

    同样,在脚本环境中,结果又有不同的表现。

    3.函数在内存中的运行逻辑

    函数的运行是基于栈内存的,栈就是一个先进后出的线性表

    我们可以把一个函数当成是栈当中的一个元素:栈帧 -> 函数本身需要占用的内存

    都包含哪些内容呢:

  • 函数名-引用关系
  • 参数列表
  • 函数内容:函数体 return 返回值
  • 接着来看,具体的运行机制是这样的:

    (1)函数被调用时,会从堆内存中将函数的代码加载进栈内存:进栈

    (2)哪个函数在栈顶,哪个函数就有限执行

    (3)直到栈顶函数遇到return时,结束函数并将返回值传递给调用者:出栈

    (4)在栈顶函数运行期间,如果又调用了其他函数,则当前函数暂停运行,直到成为新的栈顶则继续执行。

    def pow(a, b):
        c = sum(a,b) ** b
        return c
    
    def sum(a,b):
        c = a + b
        return c
    
    a = 1
    b = 2
    ret = pow(a,b)
    print(ret)
    

    image-20250324214542620

    总结:

  • 局部变量是随着函数的进栈而创建的,随着函数的出栈而消亡
  • 全局变量是随着程序的执行而创建的,随着程序的技术而消亡
  • num = 10
    def show():
        # 函数内部寻找变量或函数的逻辑:就近原则
        print(num)
    show()
    

    一般不建议在函数中直接使用全局变量,而是作为参数进行传递,为啥?一旦改变全局变量的值,那么直接调用该变量的函数在运行时就会出现有任务逻辑问题。

    作者:小熊h

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python笔记:变量与函数内存深度解析

    发表回复