以下内容参考自:
Python学习系列之值类型与引用类型_answer3lin的博客-CSDN博客_python值类型和引用类型
Python中的值类型与引用类型
其实各个标准资料中没有说明Python有值类型和引用类型的分类,这个分类一般是C++和Java中的。但是语言是相通的,所以Python肯定也有类似的。实际上Python 的变量是没有类型的,这与以往看到的大部分语言都不一样(JS等弱类型的也是这样)。但 Python 却是区分类型的,那类型在哪里呢?事实是,类型是跟着内存中的对象走的。类型
属于对象
;变量
是没有类型的,一般也分实参和形参。
《learning python》中的一个观点:变量无类型,对象有类型。
Python3中有可变对象和不可变对象.
直接上定义:参考自下面链接,其中包含有意思的实例
-
可变对象:当有需要改变对象内部的值的时候,这个对象的id不发生变化。
-
不可变对象:当有需要改变对象内部的值的时候,这个对象的id会发生变化。
变量
Python中的变量都是指针,这确实和之前学过的强类型语言是有不同的。因为变量是指针,所以所有的变量无类型限制,可以指向任意对象。指针的内存空间大小是与类型无关的,其内存空间只是保存了所指向数据的内存地址。
Python 的所有变量其实都是指向内存中的对象的一个指针,所有的变量都是!此外,对象还分两类:一类是可修改的,一类是不可修改的。我的理解是把可修改(mutable)的类型叫做值类型,不可修改(immutable)类型叫做引用类型。
值类型
在Python中,数值(整型,浮点型),布尔型,字符串,元组属于值类型,本身不允许被修改(不可变类型),数值的修改实际上是让变量指向了一个新的对象(新创建的对象),所以不会发生共享内存问题。 这种方式同Java的不可变对象(String)实现方式相同。原始对象被Python的GC回收。
修改值类型的值,只是让它指向一个新的内存地址,并不会改变变量b的值。
类似于Java的字符串常量池。
- Python在底层做了一定的优化,对于使用过小整数以及短字符串都会被缓存起来。
- 之所以采用这种优化的方式,是因为python中数字和字符串一经创建都是不可修改的。所以不会出现,因使用了缓存的对象值造成“脏读”的问题
引用类型
在Python中,列表,集合,字典是引用类型,本身允许修改(可变类型)。
可变数据类型是允许同一对象的内容,即值可以变化,但是地址是不会变化的。但是需要注意一点,对可变数据类型的操作不能是直接进行新的赋值操作,比如说a = [1, 2, 3, 4, 5, 6, 7],这样的操作就不是改变值了,而是新建了一个新的对象,这里的可变只是对于类似于append、+=等这种操作。
不可变的例外
并非所有的不可变对象都是不可变的。
如前所述,Python容器比如元组,是不可变的。这意味着一个tuple的值在创建后无法更改。但是元组的“值”实际上是一系列名称,它们与对象的绑定是不可改变的。关键点是要注意绑定是不可改变的,而不是它们绑定的对象。
让我们考虑一个元组t =('holberton',[1,2,3])
上面的元组t包含不同数据类型的元素,第一个元素是一个不可变的字符串,第二个元素是一个可变列表。元组本身不可变。即它没有任何改变其内容的方法。同样,字符串是不可变的,因为字符串没有任何可变方法。但是列表对象确实有可变方法,所以可以改变它。这是一个微妙的点,但是非常重要:不可变对象的“值” 不能改变,但它的组成对象是能做到改变的。
其实主要原因是元组内保存的是变量(也就是内存地址)。所以当变量指向对象发生变化时,如果导致变量发生变化(即不可变类型),此时元组保证该不可变类型不能修改。而如果当变量指向对象发生变化时,如果不会导致变量发生变化(即可变类型),此时元组中存储的该可变类型可以修改(因为变量本身并无变化)。
总结
python中的不可变数据类型,不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一个引用计数来记录有多少个变量引用这个对象;可变数据类型,允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。
参数传递
C++中是传值和传引用(指针)。c语言加上*号传递指针就是引用传递,而直接传递变量名就是值传递)
Java是传值(传值和传引用,只不过引用就是内存地址,所以也是值)。Java里区分值和引用,是因为值存储在栈里,而引用对象存储在堆里(引用本身在栈里)。
而Python所有的都是对象,都是引用,所以所谓的值类型都是不可变类型。类似于Java的字符串类型。
所以Python中的参数传递都是传递引用,也就是传递的是内存地址。只不过对于不可变类型,传递引用和传递值没什么区别。而对于可变类型,传递引用是真的传递内存的地址。
听说python只允许引用传递是为方便内存管理,因为python使用的内存回收机制是计数器回收,就是每块内存上有一个计数器,表示当前有多少个对象指向该内存。每当一个变量不再使用时,就让该计数器-1,有新对象指向该内存时就让计数器+1,当计时器为0时,就可以收回这块内存了。当然还有其他的GC方法,否则计数器回收,无法解决循环引用的问题。
另外:列表,字典,集合是容器类型,嵌套引用。
我究竟做错了什么? Python对象实例化保留以前实例化的数据?
以下内容摘自 上面链接
下面的实例化两个对象的代码似乎应该为每个实例化提供单独的数据.
错误代码如下:
class Node:
def __init__(self, data = []):
self.data = data
def main():
a = Node()
a.data.append('a-data') #only append data to the a instance
b = Node() #shouldn't this be empty?
#a data is as expected
print('number of items in a:', len(a.data))
for item in a.data:
print(item)
#b data includes the data from a
print('number of items in b:', len(b.data))
for item in b.data:
print(item)
if __name__ == '__main__':
main()
但是,第二个对象是使用第一个数据创建的:
>>>
number of items in a: 1
a-data
number of items in b: 1
a-data
解决方法:
您不能将可变对象用作默认值.所有对象将共享相同的可变对象.
要做这个.
class Node:
def __init__(self, data = None):
self.data = data if data is not None else []
创建类定义时,它会创建[]列表对象.每次创建类的实例时,它都使用相同的列表对象作为默认值.