python知识点1之引用、拷贝、函数参数传递、实例调用了类变量

1.引用 

python中变量与对象之间的细节。(或者说 引用和对象分离 ) 
在python中,如果要使用一个变量,不需要提前进行声明,只需要在用的时候,给这个变量赋值即可。 
例1: 
a=1 
这是一个简单的赋值语句,其中整数1为一个对象,a是一个引用,利用赋值语句,引用a指向了对象1。可以通过python的内置函数id()来查看对象的内存地址。 
例2:

a=2
print id(a)    #24834392
a='banana'
print id(a)    #139990659655312
  • 1
  • 2
  • 3
  • 4

第一个语句中, 2是储存在内存中的一个整数对象,通过赋值 引用a 指向了 对象 1; 
第二个语句中,内存中建立了一个字符串对象‘banana’,通过赋值 将 引用a 指向了 ‘banana’,同时,对象1不在有引用指向它,它会被python的内存处理机制给当我垃圾回收,释放内存。 
例3:

a=3
print id(a)    #10289448
b=3
print id(b)    #10289448
  • 1
  • 2
  • 3
  • 4

可以看到 这俩个引用 指向了同一个 对象,这是为什么呢? 这个跟python的内存机制有关系,因为对于语言来说,频繁的进行对象的销毁和建立,特别浪费性能。所以在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。 
(具体详见python知识点之垃圾回收机制) 
例4:

a=4
print id(a#36151568
b=a    #引用b指向引用a指向的那个对象
print id(b)   #36151568
a=a+2
print id(a#36151520
print id(b)   #36151568
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第3句对 a 进行了重新赋值,让它指向了新的 对象6,虽然a 的引用改变了,但是 b 的引用未发生改变,a,b指向不同的对象。 
可以得到,即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。

例5: 
引用又分为指向可变对象(如列表)和指向不可变对象(数字、字符串、元祖)。

L1=[1,2,3]
L2=L1
print id(L1)  #1396430512
print id(L2)  #1396430512
L1(0)=10
print id(L1)  #1396430512
print id(L2)  #1396430512
print L2  #[10,2,3]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

与例4相同都修改了其中一个对象的值,但是可以发现 结果 并不相同。L1 和 L2 的引用没有发生任何变化,但是 列表对象[1,2,3] 的值 变成了 [10,2,3](列表对象改变了) 
在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。

对比例4以及例5可得,引用不可变对象,不能改变对象自身,只是改变引用的指向。引用可变对象,赋值操作可直接改变引用的对象自身(即修改对象的值)。

列表可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。 而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。

判断两个引用所指的对象是否相同,可用is关键字: 
is是通过对比内存地址(id)来判断的,返回True 或False。

(扩充:python对象有三要素:id、type、value, 
is 用id 判断 , == 用 value判断 )

2.拷贝

浅拷贝(copy)拷贝一个对象,但是对象的属性依然引用原来的,即增加了一个指针指向已经存在的内存。 
假设原对象”will”,由于浅拷贝”will”会创建一个新的对象”willber”,所以”will”的 id和”willber”的id不同。(即”wilber is not will”)但是,对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址),也就是说”wilber[i] is will[i]”。具体原始对象修改元素如何反映在拷贝后对象上见例6。

扫描二维码关注公众号,回复: 153337 查看本文章

深拷贝(deepcopy)增加一个指针并且申请一个新的内存,使这个增加的指针指向新的内存。

例6:

import copy
a=[1,2,3,4,['a','b']] #原始对象

b=a  #赋值,传对象的引用
c=copy.cpoy(a) #浅拷贝
d=cpy.deepcopy(a)  #深拷贝

a.append(5) #修改对象a
a[4].append('c')   #修改对象中的['a','b']数组对象

print a   #[1,2,3,4,['a','b','c'],5]
print b   #[1,2,3,4,['a','b','c'],5]
print c   #[1,2,3,4,['a','b','c']]
print d   #[1,2,3,4,['a','b']]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

首先,a为可变对象的引用,所以修改a,可变对象的值也随之改变,a和b指向同一个对象,所以a、b值相同。 
其次,c是a 的浅拷贝,浅拷贝的各个元素整体上改变是没有影响。但是仅元素部分修改是互相牵制的。若修改的元素是不可变类型,比如a[0],则a对应的list的第一个元素会使用一个新的对象,而c依然指向原始对象;若修改的元素是不可变类型,比如a[4],修改操作不会产生新的对象,所以a的修改结果会相应的反应到c上。 
最后,d为深拷贝,有新的内存存储原始对象,所以不改变。

3.引用问题在函数以及类、实例上的使用:

3.1函数的参数传递

a = 1
def fun(a):
    a = 2
fun(a)
print a
  • 1
  • 2
  • 3
  • 4
  • 5
a = []
def fun(a):
    a.append(1)
fun(a)
print a  # [1]
  • 1
  • 2
  • 3
  • 4
  • 5

当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了。 
所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉。 
而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改。

3.2.实例调用类变量

class Person:
    name="aaa"

p1=Person()
p2=Person()
p1.name="bbb"
print p1.name  # bbb
print p2.name  # aaa
print Person.name  # aaa
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

类变量就是供类使用的变量,实例变量就是供实例使用的. 
这里p1.name=”bbb”是实例调用了类变量,这其实和上一个问题一样,就是函数传参的问题,p1.name一开始是指向的类变量name=”aaa”,但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.

class Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1)
print p1.name  # [1]
print p2.name  # [1]
print Person.name  # [1]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

引用部分参考网址https://www.cnblogs.com/ShaunChen/p/5656971.html 
参考http://blog.csdn.net/u013510614/article/details/50751017

猜你喜欢

转载自blog.csdn.net/qq_41804164/article/details/80202120