C++中的指针和引用 ,可以参考博客
https://www.cnblogs.com/heyonggang/archive/2012/12/13/2815730.html
问题导入?
如果你使?Python声明了?个变量并同时为其赋值,即a = 20,你是否将这条语句简单理解为:1. 计算机开辟了?块地址别名为a的内存;2. 往这块内存存储了数据20?
如果你对上述问题的回答为是,那么你对诸如“Python是?门动态类型语?”、“Python效率远不如C语?等静态语言效率高”等说法也?定不了解其本质。
问题解答:
变量由三部分组成:
标识,表示对象所储存的内存地址,使用内置函数id()获取
类型,表示对象的数据类型,使用内置函数type获取
值,表示对象所储存的具体数据,使用print打印输出。
在Python中:
1.变量和对象是分开储存的, 2.和其他编程语言一样的是,变量是对象内存地址的别名,即a代表了地址0x1002; 3.和其他编程语?不?样的是,Python中的变量和数据(对象)分开存储:变量a的地址0x1001处仅保存了数据20存储的内存地址0x1002。 4.变量a所标示的内存空间存储数据20所在内存地址的过程称为引?。
5.变量是对象的一个引用,对对象的操作都是通过引用来完成的
6.赋值操作 = 就是给对象添加一个引用
python中一切的传递都是引用(地址),无论是赋值还是函数调用,即python中所有的变量赋值、参数传递等都是引?传递。
python中数值类型(int和float),布尔型bool,字符串,元组都是不可变对象,列表list,字典dict,集合set都是可变对象,自定义的类对象也是可变对象?。对于不可变对象,我们?法在内存中直接修改这个变量(如 100,"student"),如果我们尝试对不可变类型进?修改,就会断开原始的引?,重新分配内存地址;修改可变对象的值不会断开原始引?,是直接在原始值上进?修改,因此这些和原始值有引?关系的变量的“值”都被修改了。
函数调?时,传递给函数的参数中保存的仍然是变量的引?(真实值的地址),函数参数是?个局部变量,初始时,参数和函数外部的值指向同?个内存地址。 - 如果参数是不可变对象,则断开原始引?,重新进?新的引?,函数外部的值不受影响 - 如果参数是可变对象,直接修改原始值,函数外部的值同步变化
- 解释器对常?的不可变类型进?了优化,把常?的数值、短字符串赋值给某些变量时,这些变量都指向相同的内存地址 ?
?示例一:
a='123'
执行上面这个赋值语句的时候,Python解释器首先在内存中创建一个字符串"123"对象。之后在内存中创建一个名为"a"的变量,将"a"指向"123"(将"123"的地址保存到“a”中)
?接着执行
b=a#指向同一个对象
因为a已经存在,所以不会创建a,会创建变量"b",并将"b"指向"a"指向的字符串"123"
然后执行
a = "456"#不可变对象,对其进行修改时会断开原来的引用
a还是存在的,Python会新分配一块内存来存储新的值,会创建字符串"456", 然后将"456"的地址赋给"a"
?对于不可变对象,我们?法在内存中直接修改这个变量(如 100,"student"),如果我们尝试对不可变类型进?修改,就会断开原始的引?,重新分配内存地址.那么下面的代码就不难理解
a=100
print(id(a))#1888557553104
a=a+1
print(id(a))#1888557553136
对于可变对象,修改可变对象的值不会断开原始引?
lst=[1,2,3]
print(id(lst))
lst.append(4)#2167621434240
print(id(lst))#2167621434240
给出一段代码检验是否理解
a=100
b=a
a=a+1
print(a,b)
print(id(a),id(b))
lst1=[1,2,3]
lst2=lst1
lst1.remove(2)
print(lst1,lst2)
print(id(lst1),id(lst2))
'''
运行结果
101 100
2413522146800 2413522146768
[1, 3] [1, 3]
2413527186432 2413527186432
'''
解释器对常?的不可变类型进?了优化,把常?的数值、短字符串赋值给某些变量时,这些变量都指向相同的内存地址,即python中的驻留机制。
a=100
b=100
c=100
print(id(a),id(b),id(c))
lst1=[1,2]
lst2=[1,2]
lst3=[1,2]
print(id(lst1),id(lst2),id(lst3))
'''
运行结果
1872572732880 1872572732880 1872572732880
1872577903488 1872577900416 1872579060160
'''
函数参数
参数的传递本质上是一种赋值操作,函数的参数也是一种引用传递。
def foo(arg):
arg = 2
print(arg)
a = 1
foo(a) # 输出:2
print(a) # 输出:1
?
变量 a 指向?1,调用函数 foo(a) 时,arg=a,,这时两个变量都指向 1。在函数里面 arg 重新赋值为 2 之后,不可变对象不可修改时原先引用会断开,而 a的引用没变。因此 print(a) 还是 1。 再来看下一段代码
def bar(args):
args.append(1)
b = []
print(b)# 输出:[]
print(id(b)) # 输出:4324106952
bar(b)
print(b) # 输出:[1]
print(id(b)) # 输出:4324106952
执行 append 方法前 b 和 arg 都指向同一个对象,执行 append 方法时,并没有重新赋值操作,也就没有新的引用过程,append 方法只是对列表对象插入一个元素,对象还是那个对象,只是对象里面的内容变了。因为 b 和 arg 都是绑定在同一个对象上,执行 b.append 或者 arg.append 方法本质上都是对同一个对象进行操作,因此 b 的内容在调用函数后发生了变化(但id没有变,还是原来那个对象)。
?python中+=在可变对象运算中的特殊用法
num = 100
def update(a):
a+=10
print(a) #打印110
update(num)
print(num) #打印100
num1 = 100
def update1(a):
a=a+10
print(a) #打印110
update(num1)
print(num1) #打印100
'''
python中所有的变量都是引用类型
num和update(a),实参num和形参a都指向同一片内存地址
a += 10
这里对a做出修改的操作
因为a是数值类型,属于不可变类型,不能修改
所以,python会创建一个临时变量a,用来存储110
所以print(num) 打印的仍然是100
'''
list = [2]
def test1(num):
num += num
print(num) #打印[2,2]
test1(list)
print(list) #打印[2,2]
#从结果而言,修改了可变类型的值
list2 = [3]
def test2(num):
num = num + num #num+num 的结果是[3,3] 这里表示将[3,3]这个列表赋值给num这个临时变量
print(num) #打印[3,3]
test2(list2)
print(list2)#打印[3]
'''
在python中 +=运算符表示对当前变量进行操作
并不完全等同于 +
'''
简单的加法中,列表的运算还是创建了一个新的列表对象;但在简写的加法运算+=实现中,则并没有创建新的列表对象。这一点要十分注意。
验证一下是否理解
def add_list(p):
p = p + [1]
p1 = [1,2,3]
add_list(p1)
print p1
>>> [1, 2, 3]
def add_list(p):
p += [1]
p2 = [1,2,3]
proc2(p2)
print p2
>>>[1, 2, 3, 1]
词典的引用
a = []
b = {'num':0, 'sqrt':0}
resurse = [1,2,3]
for i in resurse:
b['num'] = i
b['sqrt'] = i * i
a.append(b)
print a
#输出 [{'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}]
但我们实际想要的结果是这样的:
输出 [{'num': 1, 'sqrt': 1}, {'num': 2, 'sqrt': 4}, {'num': 3, 'sqrt': 9}]
这是由于a中的元素就是b的引用。可以修改为:
a = []
resurse = [1,2,3]
for i in resurse:
a.append({"num": i, "sqrt": i * i})
print(a)
#输出[{'num': 1, 'sqrt': 1}, {'num': 2, 'sqrt': 4}, {'num': 3, 'sqrt': 9}]
呕心沥血之作,。。。。,看了好长时间
|