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。

深拷貝(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

發佈了14 篇原創文章 · 獲贊 17 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章