python中有指针吗_Python中的指针——到底指什么(一)

指针是C和C++系语言的重要概念,其本质是保存另一个变量内存地址的变量(Pointers are variables that hold the memory address of another variable.)。某些情况下,使用指针可以提高代码的效率。但是,指针也往往会对初学者造成困扰,即使是老手,也经常会出现内存管理的问题。

象Java、Python之类的语言并没有明确的指针的概念。但Python中如果需要使用类似指针行为的时候,可以模拟出指针的效果,同时又不会陷入内存管理的噩梦。

Python中为什么没有指针

看起来指针似乎违背了Python设计之禅(Zen of Python)。指针会带来隐含的修改,对初学者来说显得比较复杂,而且也为访问非预期内存块之类的危险操作提供了手段。Python倾向于对用户屏蔽类似内存管理之类的实现细节,更注重语言的实用性,甚至都不太关心执行速度。

Python中的对象

在Python中,一切都是对象。可以通过提供的内置函数isinstance()进行验证:

>>> isinstance(1, object)

True

>>> isinstance(str, object)

True

>>> isinstance(list(), object)

True

>>> def func():

pass

>>> isinstance(func, object)

True

由此可见,Python中无论基本类型、类、类实例、自定义函数等都是对象。对象至少由三部分数据组成:

引用计数(Reference Count)

对象类型(Type)

对象值(Value)

其中引用计数用来进行内存管理;对象类型用于在Python本身的实现层(CPython)保证运行期的类型安全;对象值才是对象的真实数据。

要想拨开Python指针的层层迷雾,首先需要了解Python中的两类对象。

不可变对象与可变对象

顾名思义,不可变对象(Immutable Object)就是值不能改变的对象,象int、bool、float、str、tuple等都是不可变对象,相反,list、set、directory等都是可变对象(Mutable Object)。可以使用id()函数(返回对象的内存地址)、is(两个对象具有同一个内存地址时返回True,否则返回False)对此进行验证:

>>> x = 5

>>> id(x)

8791652754384

>>> x = x + 1

>>> id(x)

8791652754416

>>> s = 'Python'

>>> id(s)

31016864

>>> s += ' test'

>>> id(s)

52447280

>>> s[0] = 'p'

Traceback (most recent call last):

File "", line 1, in

s[0]='p'

TypeError: 'str' object does not support item assignment

>>> lt = [1, 2, 3]

>>> id(lt)

52176968

>>> lt.append(4)

>>> id(lt)

52176968

>>> lt[0] = 5

>>> id(lt)

52176968

对整形对象x和字符串对象s进行操作后,得到了另一个对象(id函数返回了另一个值,说明对象的内存地址变了),对s[0]赋值时,得到TypeError,说明str对象的元素是不能进行赋值的;对list对象lt操作后,得到的还是原来的对象lt。这就说明x、s是不可变对象,lt是可变对象。

深入理解变量

Python中的“变量”与C和C++中的变量是截然不同的。甚至在Python中就不应该叫做变量(variables),而应该叫做名称(names)。听起来很怪异,但通常情况下把Python中的名称认为是变量也没关系,重要的是要理解它们的不同之处。

在C语言中,定义一个整形变量x:

int x = 2505;

挺简单的一句话,其背后发生了什么?

申请一个足够存储整型值的内存空间;

将2505赋值给该内存地址;

将x指向该值。

内存示意图如右图所示:

up-b29bc25847036bf44507ebad19db303107a.png

要修改x的值,只需要执行:

x = 2504;

该语句将新值2504赋给了变量x,覆盖了原值2505,更新后的内存显示了新值:

up-62dd13ed7986cdf785d9ae16bd3a2fe4486.png

需要注意的是x的地址没有发生变化,仅是值变化了。这意味着x是内存地址,而不仅仅是个名字。 可以从另一个角度思考:x拥有一个内存地址(容器),该地址可以存储一个整形数,当将值赋给x时,相当于把这个值放进了x所拥有的内存地址。执行:

int y = x;

代码创建了一个新的容器(占用一段内存),并把x的值复制到该容器中,内存看起来是这样的:

up-2ec7bbc269f2c4c54926d5784b29999876f.png

虽然x的值复制给了y,但y拥有了一段新的内存,改写y值的时候对x没有影响。

再来看看Python中等价的代码:

x = 2505

除了不用声明x的类型外,和C代码没有区别。同样,代码的背后会执行以下操作:

创建一个PyObject对象(不同于Python中的对象,是CPython特有的Python中所有对象的基本结构。PyObject是C结构体,无法直接访问typecode和refcount等,不过通过sys.getrefcount()可以获取一些内部信息);

将PyObject对象的类型设置为整形;

将PyObject对象的值设置为2505;

创建一个称为x的名称;

将x指向新创建的PyObject对象;

将PyObject对象的引用计数加1。

内存示意如下:

up-ebb511eee876425878e5f1bc412a4f423d7.png

与C语言中变量x拥有存储2505的内存块不同,Python中新创建的PyObject拥有这块内存,但是名称x并不拥有任何一块内存。

要将x的值修改为2504,执行:

x = 2504

这行代码会执行以下操作:

创建一个新的PyObject对象;

将新PyObject对象的类型设置为整形;

将新PyObject对象的值设置为2504;

将x指向新创建的PyObject对象;

将新PyObject对象的引用计数加1;

将旧PyObject对象的引用计数减1。

现在的内存示意图如下:

up-0a87f2c5b3eee038ccc9d1701dbffa83176.png

上图表明x指向一个对象的引用,而不拥有内存空间。x=2338语句不是一个赋值语句,而是将x绑定到一个引用。而且,旧的对象在内存中的引用计数为0,将等待被垃圾回收机制回收。

象C代码一样,将x赋值给一个新的名称y:

y = x

内存看起来就会是这个样子的:

up-ffc5e1b96f18b5be0b537f69541ef88a2a9.png

并没有创建新的对象,只是将新名称y指向了同一个对象,可以通过 y is x 返回True确定x和y是同一个对象。同样,y也是不可变对象,如果对y进行操作,如y += 1,那么y就会绑定到一个新的对象,这时y is x就会返回False。

深刻理解Python中的对象是了解指针的重要基础,下篇文章中将会详细说明Python指针的具体内容。

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值