Python基礎之變數進階,包括了 變數的引用,可變類型和不可變類型,哈希;其中,變數的引用 包括 函數引用的概念,函數引用理解,函數傳參與引用的關係,函數返回值與引用;可變類型和不可變類型 包括 可變類型修改和重賦值對引用的影響;哈希 僅包含 哈希演算法 等 ...
變數的引用
- 變數和數據都是保存在記憶體中的;
- 在python中函數的參數傳遞以及返回值都是靠引用傳遞的。
函數引用的概念
在python中
- 變數和數據時分開存儲的;
- 數據保存在記憶體中的一個位置;
- 變數保存著數據在記憶體中的地址;
- 變數中記錄數據的地址,就叫做引用;
- 使用id()函數可以查看變數中保存數據所在的記憶體地址。
註意:如果變數已經被定義,當給一個變數賦值的時候,本質上是自改了數據的引用;即變數不再對之前的數據引用;變數改為對新賦值的數據引用。
a = 1
id(a)
140721952793280
id(1)
140721952793280
b = a
id(b)
140721952793280
a = 2
id(a)
140721952793312
id(b)
140721952793280
b = a
id(b)
140721952793312
b = 2
id(b)
140721952793312
函數引用理解
我們可以把變數的名字理解為便簽紙,而變數名和數據就相當於把便簽紙貼在數據上;
當我們a = b時,就是把a,b兩張標簽紙貼在了同一個數據上,而如果我們把a重新賦值,就是把a的便簽紙撕下來貼在另一個數據上,但b的便簽紙位置不變;
函數傳參與引用的關係
函數參數的傳遞,實際傳送的是對應實參變數的引用,而不是實參保存的數據;
def test(num):
print("在函數內部%d對應的記憶體地址是%s" % (num, id(num)))
a = 10
print("a 變數保存數據的記憶體地址是 %s" % id(a))
test(a)
# a 變數保存數據的記憶體地址是 140722085962720
# 在函數內部10對應的記憶體地址是140722085962720
函數返回值與引用
函數的返回值同樣也是返回變數的引用,而不是真實的數據;
數據地址本質上就是一個數字;
def test(num):
result = "test_password"
print("函數內返回值result的記憶體地址是 %s" % id(result))
return result
a = 10
r = test(a)
print("返回的 %s 的記憶體地址是 %s" % (r, id(r)))
# 函數內返回值result的記憶體地址是 2333111002800
# 返回的 test_password 的記憶體地址是 2333111002800
可變類型和不可變類型
修改可變類型 是修改數據的內容,而不會修改變數引用的地址;修改可變類型,要用對象.方法()進行修改;
重新賦值會修改變數引用的地址;
不可變類型,記憶體中的數據不允許被修改:
- 數字類型;
- 元組;
- 字元串;
可變類型,記憶體中的數據可以被修改:
- 列表;
- 字典;
可變類型修改和重賦值對引用的影響
可變類型比如列表,字典,對它們進行數據修改時,不會對引用的記憶體地址造成影響;
只有當我們對變數進行重新賦值之後,才會影響引用;
下麵舉例僅舉列表的例子,字典一樣,就不贅述了。
# 列表數據修改和重賦值對引用的影響
a = [1,2,3]
id(a)
1956997579272
a.append(4)
a
[1, 2, 3, 4]
id(a)
1956997579272
a.remove(2)
a
[1, 3, 4]
id(a)
1956997579272
a.clear()
a
[]
id(a)
1956997579272
a = ['a','s','d']
id(a)
1956997945160
字典的key只能使用不可變類型;
註意:可變類型的數據變化,是通過方法來是實現的;
哈希演算法
d = {}
d["name"] = "zhangsan"
d
{'name': 'zhangsan'}
d[1] = "整數"
d
{'name': 'zhangsan', 1: '整數'}
d[(1,)] = "元組"
d
{'name': 'zhangsan', 1: '整數', (1,): '元組'}
d[[1,2,3]] = "列表"
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unhashable type: 'list'
d[{"age":18}] = "字典"
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unhashable type: 'dict'
- Python中內置一個名字叫做hash(o)的函數,它接收一個不可變類型的數據作為參數,返回結果是一個整數;
- 哈希是一種演算法,其作用是提取數據的特征碼(指紋);相同的數據得到相同的結果,不同的數據得到不同的結果;
- 在python中,設置字典的鍵值對時,會首先對key進行hash,以決定如何在記憶體中保存字典的數據,以方便後續的字典的增刪改查;
- 字典 鍵值對的key必須是不可變類型數據;鍵值對的value可以是任意類型的數據;
哈希演算法,只能哈希不可變類型;
因為字典的key要使用哈希,所以,字典的key只能是不可變類型;
hash(1)
1
hash("hello")
2061306992742373012
hash("hello python")
9189581639312291988
hash((1,2))
3713081631934410656
hash([1,2])
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unhashable type: 'list'
hash({"age":18})
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unhashable type: 'dict'
局部變數和全局變數
局部變數,就是在函數內部定義的變數,僅供函數內部使用;
全局變數,就是在函數外部定義的變數,所有函數內部都可以使用這個變數。
在其他語言中,大多都不推薦使用全局變數,因為可變範圍太大,不可控情況多;
局部變數
局部變數介紹
- 局部變數是在函數內部定義的變數,只能在函數內部使用;
- 函數執行完成後,函數內部的局部變數,會被系統回收;
- 不同的函數,可以定義相同的名字的局部變數,彼此之間不會產生影響;
局部變數的作用:在函數內部使用,臨時保存函數內部需要使用的數據;
局部變數只能在定義的函數內部使用,不能被函數外部或函數外部函數使用
def demo1():
num = 10
print("demo1內部的局部變數num的值為%d" % num)
# 因為num是num1的局部變數,而demo1外面也沒有定義num變數,所以本句運行後會報錯,註釋掉
# print(num) # NameError: name 'num' is not defined
def demo2():
# 同樣的,demo2重嗎既沒有num的變數,外部也沒有定義全局的num變數,運行會報錯,註釋掉
# print(num) # NameError: name 'num' is not defined
pass
demo1() # demo1內部的局部變數num的值為10
demo2()
局部變數的生命周期
當局部變數被執行時創建;當函數執行完後局部變數被系統回收,生命結束;
局部變數在生命周期內可以用來臨時存儲信息。
用斷點可以驗證局部變數的生命周期。
不同函數內的同名局部變數
不同函數間可以定義相同名的局部變數,彼此之間互不關聯,這就像1班有一個小明,2班也有一個小明,但他們並不是同一個人;
def demo1():
num = 10
print("demo1內部的局部變數num的值為%d" % num) # demo1內部的局部變數num的值為10
def demo2():
num = 100
print("demo2的num:", num) # demo2的num: 100
demo1()
demo2()
全局變數
全局變數的使用
在所有函數外部定義的變數,就叫做全局變數;
可以給全局所有代碼調用,包括全局變數的平行級和下級函數內部;
num = 10
def demo1():
print("demo1的num", num)
def demo2():
print("demo2的num", num)
demo1()
demo2()
print(num)
# demo1的num 10
# demo2的num 10
# 10
函數內部不能直接修改全局變數的值
在函數內部,可以直接通過全局變數的引用獲取對用的數據;
但是,在python中,函數內部不能直接修改全局變數的值,如果用全局變數名在函數內部重新賦值,本質上只是創建一個同名局部變數而已;
num = 10
def demo1():
# 這個語句 並不是修改全局變數的值,而是創建一個同名局部變數
num = 90
print("demo1的num", num)
def demo2():
print("demo2的num", num)
demo1()
demo2()
print(num)
# demo1的num 90
# demo2的num 10
# 10
變數查找順序
註意:函數執行時,需要處理變數時 會:
- 先從函數內部找指定名稱的局部變數,如果有,直接使用;
- 函數內部沒找到變數,就去函數外部找指定名稱的全局變數,如果有,直接使用;
- 還沒找到,就報錯;
用global在函數內修改全局變數
如果希望在函數內部修改全局變數的值,使用global聲明一下變數即可;
global關鍵字會告訴解釋器後面聲明的變數是一個全局變數,這樣,再使用賦值語句時,就不會創建局部變數了。
num = 10
def demo1():
# 告訴解釋器,這個就是全局變數,不用再創建同名局部變數了
global num
num = 90
print("demo1的num", num)
def demo2():
print("demo2的num", num)
demo1()
demo2()
print(num)
# demo1的num 90
# demo2的num 90
# 90
全局變數定義的位置
- 在函數中要使用的變數必須在函數被調用前就被定義好,否則會報錯;
- 一般講所有的全局變數都放在其他函數的上方,這樣可以確保每個函數都能正確的使用全局變數。
代碼結構順序:
- shebang
- import 模塊
- 全局變數
- 函數定義
- 執行代碼
全局變數命名的建議:全局變數建議在變數名前g_變數名 或者gl_變數名。