Python-变量浅析

阅读本篇文章最好有一定的静态语言基础,python的方面是面向新手的,学没学过都行。

先来看一段简单的实验

>>> a = "hello"
>>> id(a)
2294334214320
>>> a = "world"
>>> id(a)
2294334214448
>>> b = "world"
>>> id(b)
2294334214448

可以看到a的地址是随后面赋予值的变换而变换的,我记得上次写Type源码的时候里面就说明,python一切皆对象,那么"hello",2.11,3,这些是不是对象?答案是肯定的,这些所谓字符串,浮点型,整型,统统是对象,如果在后面加上.__class__ 就可以看到,是属于<class 'str'>,<class 'float'>,<class 'int'>。所以可能学惯了c的一上来很习惯的认为

python的
a = 1

就等同于c中的
int a
a = 1

其实不是这样,c里面先给a分配了int的内存空间,然后把整数1写入了a;(这个确实翻了一些资料,不太确定,也有的说这个a是地址头+偏移,或者更深入去用汇编解释的,我在这就暂时假设c是这个样子的,,要不就深入的没完了),不管是什么实现,当c进行声明的时候,会进行内存的分配这个是肯定的,下面我用go代替c(c苦手😀)实现类似还是上面简单实验相似的例子

	a := 10
	fmt.Printf("%p\n", &a)
	b := 10
	fmt.Printf("%p\n", &b)
	a = 20
	fmt.Printf("%p\n", &a)
0xc00000a200
0xc00000a208
0xc00000a200

可以看到a的地址值是没变的,所以大部分时候我们都把a想象成一个容器,去往里面装各种value;

但是a毕竟是a,不是地址,a和地址的对应关系其实就有点类似python中a和变量的对应关系。

但是python中,1并不是一个单纯的整数,而是一个类对象,int类的对象,这个对象的生成可以参考前面博客里面的,是用c的一个结构体实现的;当通过C生成python的int类对象时,此时1的地址就已经确定了,是一个int类对象的地址

而python中 a=1,a是什么?a是一种引用,而不是一个指针,也不是一个什么地址头+偏移,a在内存中没有分配额外的空间去存储,而把a和1这个int对象连接起来的实现是在python的解释器中。

假设现在我有这么一个表,他里面存储的是变量名a和变量名a对应的一些东西(不管是什么)的对应关系,那么python和c的区别在哪?

上面的例子中,a都是个名字,是个代指,其实python和c没有差别;

但是在这个表中,c的情况下:变量名字a对应的是一个地址,也就是上面所说容器的地址;这里可能比较绕,因为&a也是取地址,如果a是地址了,&a是什么?但是c的实现我没有去了解,不太好说这是怎么回事,这里我也不去解释。a=1,本质就是把1载入a对应的内存地址,b = a,就是从a内存读取,b复制一份到自身所对应的内存。

python的情况下:变量名字a对应的是一个类对象!当id()取值的时候,就是相当于&类对象,取到的是类对象的地址,而为什么a=1 b=1地址是一样的,就是因为1这个int对象本来就是一个东西,在内存里面都是同一块地方,只不过a和b同时在这个表里面对应了这个对象而已。

这个表在c中叫做符号表,然而在python里面是没有的,Python本质上是动态的,而不是静态的。虚拟机自身就具有变量的可寻址命名空间,而不是已编译目标代码中的符号表。所以这个表在python中是用解释器实现的,在python中还有另一个名字叫namesapce。

那么就顺势说一下可变不可变类型
  • Number(数字)
  • String(字符串)
  • List(列表)
  • Tuple(元组)
  • Set(集合)
  • Dictionary(字典)

其中可变的类型就只有列表,集合和字典,为什么,因为其它的都是初始化变量时就固定好的,如果更改内容,那么就要重新的初始化变量,也就是创建一个新的变量对象。

而列表的底层是用C语言实现的长度可变的数组,从细节上看,Python中的列表是对其它对象的指针组成的连续数组,列表对象的结构体如下:

typedef struct {
    # 列表对象引用计数
    int ob_refcnt; 
    # 列表类型对象 
    struct _typeobject *ob_type;
    # 列表元素的长度
    int ob_size; /* Number of items in variable part */
    # 真正存放列表元素容器的指针,list[0] 就是 ob_item[0]
    PyObject **ob_item;
    # 当前列表可容纳的元素大小
    Py_ssize_t allocated;
} PyListObject;
ob_item其实就是python的列表,**ob_item在这里是PyObject对象类型,,这里两个*是指向指针数组的指针,也就是ob_item就是一个指针数组,ob_size是列表的长度,allocated列表的容量,这个是可以动态变的;这个**如果看不懂的话这里推荐一篇文章https://www.kawabangga.com/posts/1825这里不解释了
大致可以看出来,ob_item实现的是一个可变长的动态指针数组,数组里面的元素其实都是指针,所以列表是可变的,对元素的更改就是换上新的指针。

关于列表我提一个问题,列表容量改变的时候,是重新分配了一个新数组呢,还是在原数组直接操作的?如果是直接操作,是怎么实现的?这个我也在探究,如果有答案希望可以告诉我

下面是字典和集合,简单来说,字典和集合的底层实现都是hash表,字典的hash表保证了key的唯一性,集合的hash实现了去重,同时也限制了字典的key和集合的值都必须是可hash的,这个原理不在这里说了,略复杂有时间了再说吧。

本篇就到这里,因为有涉及到一些对CPython源码的解读,而本人其实C是小白级别的,所以如果有错误请大家指出。因为涉及到CPython底层实现,还有变量底层实现,查阅了不少的文章,这里没办法一一列出,如果有作者看到请联系我加上链接😀。

posted @ 2020-06-04 17:23  seas  阅读(152)  评论(0编辑  收藏  举报