今天突來興趣,留檔一些python常見的錯誤和思維
一圖讀懂:(2019元旦補:)
下面内容感興趣可以看,不然就隻看一圖讀懂:
在說錯誤之前,先來看一下在python中的哪些是可變資料和哪些是不可變資料吧
- 1.可變資料類型
- 清單list
- 字典dict 2.不可變資料類型
- 整型int
- 浮點型float
- 字元串型string
- 元組tuple。
下面我們以int和list為例,來說一下“可變資料類型”和“不可變資料類型”的差別
(1).不可變資料類型分析:x=1
y=1
print(id(x))
print(id(y))
結果
1376539424
1376539424
可以看出,x和y綁定同一個位址(類比了解 用c的指針了解,p1和p2中的,指針變量p1和p2的值是一樣,都是儲存了同一個記憶體的位址,*p1意思就是":p1的内容(值)所指(代表)的位址的内容"),就是x和y其實是引用了同一個對象,即1,也就是說記憶體中對于1隻占用了一個位址,而不管有多少個引用指向了它,都隻有一個位址值,隻是有一個引用計數器會記錄指向這個位址的引用到底有幾個而已。
(2).可變資料類型分析:x=[2,3]
print(id(x))
x=[2,3]
print(id(x))
y=[2,3]
print(id(y))
結果
2285423966792
2285423964360
2285423964388
兩次x變量綁定在不同的位址(即變量x(引用x)的值不同,有人說:python中 [變量]更準确叫法是[名字],指派操作 = 就是把一個名字綁定到一個對象上。或者 類比了解(python中的變量是沒有類型資訊和不占記憶體區域的),c中一個指針變量(java php js中叫引用)指向了一個對象(記憶體空間)),也就是說其實建立了兩個不同的對象,這一點明顯不同于不可變資料類型,是以對于可變資料類型來說,具有同樣值的對象是不同的對象,即在記憶體中儲存了多個同樣值的對象,位址是不同。
重點:
x=[2,3]
print(id(x))
x.append(4)
print(x)
x += [1]
print(x)
print(id(x))
結果
2655391136328
[2, 3, 4]
[2, 3, 4, 1]
2655391136328
我們對清單進行添加操作,分别x.append(4)和x += [1],發現這兩個操作使得x引用的對象值變成了上面的最終結果,但是x變量綁定的位址依舊是2655391136328,也就是說對x引用指向的對象進行的操作,不會改變x變量的值(x引用指向的位址),隻是在位址後面又擴充了新的位址,改變了位址裡面存放的值,是以可變資料類型的意思就是說對一個變量進行操作時,其值是可變的,值的變化并不會引起建立對象,即位址是不會變的,隻是位址中的内容變化了或者位址得到了擴充。
參考:http://blog.csdn.net/dan15188387481/article/details/49864613
開始常見錯誤
1.用一個可變的值作為預設值(可變資料類型作為函數定義中的預設參數)def fn(var1,var2=[]):
var2.append(var1)
print(var2)
print(id(var2))
fn(1)
fn(2)
fn(3)
輸出結果:
[1]
2275344529992
[1, 2]
2275344529992
[1, 2, 3]
2275344529992
在Python裡,函數的預設值是在函數定義的時候執行個體化的,而不是在調用的時候。如同上面,當解析器運作到定義函數時,清單[]被執行個體化為函數定義的一部分。當函數運作時,它并不是每次都被執行個體化。這意味着,這個函數會一直使用完全一樣的清單對象,除非我們提供一個新的對象傳進去。
就是說 函數定義時,如果沒有傳進新對象,可變類型[]的位址一直沒有變化,var2變量一直綁定在同一個位址上,這樣就回到了上面可變資料類型分析問題上了
代碼修正:
def fn(var1,var2=None):
if not var2:
#if var2 is None:
var2=[]
var2.append(var1)
print(var2)
fn(1)
fn(2)
結果
[1]
[2]
類似例子
import time
def print_now(now=time.time()):
print(now)
print_now()
print_now()
print_now()
結果
1513001005.1218028
1513001005.1218028
1513001005.1218028
跟上面一樣,time.time() 的值是可變的 類似可變資料類型,那麼它隻會在函數定義的時候計算,是以無論調用多少次,都會傳回相同的時間 — 這裡輸出的時間是程式被Python解釋運作的時間。
2.可變資料類型作為類變量class Url(object):
urls = []
def add_url(self,url):
self.urls.append(url)
print(id(self.urls))
a = Url()
a.add_url('www.baicai.com')
b = Url()
b.add_url('www.wljsb.com')
print(a.urls)
print(b.urls)
結果
2264790084168
2264790084168
[‘www.baicai.com’, ‘www.wljsb.com’]
[‘www.baicai.com’, ‘www.wljsb.com’]
這兩個對象怎麼會都有這兩個元素呢?
這和第一個問題是類似的。建立類定義時,urls清單将被執行個體化。該類所有的執行個體使用相同的清單。在有些時候這種情況是有用的,但大多數時候你并不想這樣做。
就是說,清單的位址一直沒有變化,urls變量綁定在同一個位址上,最終還是回到上面的分析中
你希望每個對象有一個單獨的儲存。為此,我們修改代碼為:
class Url(object):
def __init__(self):
self.urls = []
def add_url(self,url):
self.urls.append(url)
print(id(self.urls))
a = Url()
a.add_url('www.baicai.com')
b = Url()
b.add_url('www.wljsb.com')
print(a.urls)
print(b.urls)
結果
2019524248968
2019524233800
[‘www.baicai.com’]
[‘www.wljsb.com’]
3.了解python變量(也叫可變配置設定錯誤)
a = {'1': "one", '2': 'two'}
b = a
b['3'] = 'three'
print(a)
結果
{‘2’: ‘two’, ‘3’: ‘three’, ‘1’: ‘one’}
現在看這個很簡單了, a 和 b 變量都是綁定在同一個對象上,就相當于看同一台電視,誰換台都受影響的。
結尾,根據這些也可以判斷python中函數的參數 為啥有些人老是搞糊塗 是平常說的 傳值還是傳引用(位址), 其實 python中參數的傳遞本質上是一種 指派操作
def foo(arg):
arg = 2
print(arg)
a = 1
foo(a)
print(a)
結果
2
1
上面根據不可變類型分析,a 和 arg 綁定的位址不同 類似傳值操作
為啥有人會搞糊塗,說參數是傳引用呢?看下面
def bar(args):
args.append(2)
b = [1]
print(b)
print(id(b))
bar(b)
print(b)
print(id(b))
結果
[1]
3001474107976
[1, 2]
3001474107976
看到這個結果,你思考一下,是不是很像把清單的位址傳了進去,這就是c java php js的思維了,因為他們覺得調用函數後 再輸出b清單是[1,2] ,函數内部修改了函數外部的變量,看位址輸出都沒有變化,是以認為是傳引用。
這就是python變量和其他語言的不同之處了,上面根據可變類型分析,b和arg綁定在了同一個位址上,對同一個清單對象進行操作的。
python的變量和其他語言不同,可以說把傳值傳引用的說法統一成,說 指派操作(綁定),根據不可變類型和可變類型分析,就了解到具體的操作變化了
以上内容總結成:變量當作标簽貼在記憶體上,看是可變還是不可變,傳參是時候,可變的話在記憶體基礎上變,不可變的話,就開辟另外記憶體空間,把便簽貼過來.
python常注意點:
a =(1,) #元組隻有一個元素是後面要有逗号
b = a[::-1] #這是反向切片指派
a[-1:6] #分片中最左邊的索引比它右邊的晚出現在序列中,結果就是一個空序列