python變數與引數
名稱空間與作用域
名稱空間提供了在專案中避免名字衝突的一種方法。各個名稱空間是獨立的,沒有任何關係的,所以一個名稱空間中不能有重名,但不同的名稱空間是可以重名而沒有任何影響。我們舉一個計算機系統中的例子,一個資料夾(目錄)中可以包含多個資料夾,每個資料夾中不能有相同的檔名,但不同資料夾中的檔案可以重名。
一般有三種名稱空間:
-
內建名稱(built-in names),Python 語言內建的名稱,比如函式名
abs
、chr
和異常名稱BaseException
、Exception
等等。 - 全域性名稱(global names),模組中定義的名稱,記錄了模組的變數,包括函式、類、其它匯入的模組、模組級的變數和常量。
- 區域性名稱(local names),函式中定義的名稱,記錄了函式的變數,包括函式的引數和區域性定義的變數。(類中定義的也是)
名稱空間查詢順序:
假設我們要使用變數 LintCode
,則 Python 的查詢順序為:區域性的名稱空間 -> 全域性名稱空間 -> 內建名稱空間。
如果找不到變數 LintCode
,它將放棄查詢並引發一個 NameError
異常:NameError: name 'LintCode' is not defined。
名稱空間的生命週期:
名稱空間的生命週期取決於物件的作用域,如果物件執行完成,則該名稱空間的生命週期就結束。
因此,我們無法從外部名稱空間訪問內部名稱空間的物件。
print(chr) # 訪問內建名稱 chr chr = 'chr' # chr 是全域性名稱 print(chr) # 訪問全域性名稱 chr def outer_function(): chr = 'char' print(chr) # 訪問區域性名稱 chr outer_var = 'out_var' print(outer_var) # 無法從外部名稱空間訪問內部名稱空間的物件 # print(inner_var) def inner_function(): inner_var = 'inner_var' # 可以從內部名稱空間訪問外部名稱空間的物件 print(outer_var) print(inner_var) inner_function() outer_function()
作用域
A scope is a textual region of a Python program where a namespace is directly accessible.
作用域是一個程式碼區域,是一個名稱空間可以直接引用的區域。
在一個 Python 程式中,直接訪問一個變數,會從內到外依次訪問所有的作用域直到找到,否則會報未定義的錯誤。
Python 中,程式的變數並不是在哪個位置都可以訪問的,訪問許可權決定於這個變數是在哪裡賦值的。
變數的作用域決定了在哪一部分程式可以訪問哪個特定的變數名稱。Python 的作用域一共有4種,分別是:
- L(Local):最內層,包含區域性變數,比如一個函式/方法內部。
-
E(Enclosing):包含了非區域性(non-local)也非全域性(non-global)的變數。比如兩個巢狀函式,一個函式(或類)
A
裡面又包含了一個函式B
,那麼對於B
中的名稱來說A
中的作用域就為 non-local。 - G(Global):當前指令碼的最外層,比如當前模組的全域性變數。
- B(Built-in): 包含了內建的變數/關鍵字等,最後被搜尋。
規則順序: L –> E –> G –> B。
在區域性找不到,便會去區域性外的區域性找(例如閉包),再找不到就會去全域性找,再者去內建中找。
global_count = 0 # 全域性作用域
def outer():
outer_count = 1 # 閉包函式外的函式中
def inner():
inner_count = 2 # 區域性作用域
內建作用域是通過一個名為 builtin
的標準模組來實現的,但是這個變數名自身並沒有放入內建作用域內,所以必須匯入這個檔案才能夠使用它。在 Python 3 中,可以使用以下的程式碼來檢視到底預定義了哪些變數:
>>> import builtins
>>> dir(builtins)
Python 中只有模組(module
),類(class
)以及函式(def
、lambda
)才會引入新的作用域,其它的程式碼塊(如 if
/elif
/else
/、try
/except
、for
/while
等)是不會引入新的作用域的,也就是說這些語句內定義的變數,外部也可以訪問,如下程式碼:
>>> if True:
... msg = 'I am from LintCode'
...
>>> msg
'I am from LintCode'
>>>
例項中 msg
變數定義在 if
語句塊中,但外部還是可以訪問的。
如果將 msg
定義在函式中,則它就是區域性變數,外部不能訪問:
>>> def test():
... msg_inner = 'I am from LintCode'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
>>>
從報錯的資訊上看,說明了 msg_inner
未定義,無法使用,因為它是區域性變數,只有在函式內可以使用。
區域性變數與全域性變數
定義在函式內部的變數擁有一個區域性作用域,定義在函式外的擁有全域性作用域。
區域性變數只能在其被宣告的函式內部訪問,而全域性變數可以在整個程式範圍內訪問。呼叫函式時,所有在函式內宣告的變數名稱都將被加入到作用域中。如下例項:
#!/usr/bin/python3
total = 0 # 這是一個全域性變數
# 可寫函式說明
def sum(arg1, arg2):
#返回2個引數的和.
total = arg1 + arg2 # total在這裡是區域性變數.
print ("函式內是區域性變數 : ", total)
return total
# 呼叫sum函式
sum(10, 20)
print("函式外是全域性變數 : ", total)
global
和 nonlocal
關鍵字
當內部作用域想修改外部作用域的變數時,就要用到 global
和 nonlocal
關鍵字了。
global
關鍵字修飾變數後標識該變數是全域性變數,對該變數進行修改就是修改全域性變數。
global
關鍵字可以用在任何地方,包括最上層函式中和巢狀函式中,即使之前未定義該變數,global
修飾後也可以直接使用
num = 1
def function():
global num # 需要使用 global 關鍵字宣告
print(num)
num = 123
print(num)
function()
print(num)
如果要修改巢狀作用域(enclosing 作用域,外層非全域性作用域)中的變數則需要 nonlocal
關鍵字了。
nonlocal
關鍵字修飾變數後標識該變數是上一級函式中的區域性變數,如果上一級函式中不存在該區域性變數,nonlocal
位置會發生錯誤(最上層的函式使用 nonlocal
修飾變數必定會報錯)。
def outer():
num = 10
def inner():
nonlocal num # nonlocal關鍵字宣告
num = 100
print(num)
inner()
print(num)
outer()
可變引數傳遞——在 python 中,strings, tuples, 和 numbers 是不可更改的物件,而 list,dict 等則是可以修改的物件。
不使用可變引數的傳統方式
我們以數學題為例子,給定一組數字 a,b … z ,請計算 sum = a * a + b * b + .....+ z * z
要定義出這個函式,我們必須確定輸入的引數。由於引數個數不確定,我們首先想到可以把a, b, …, z作為一個 list 或 tuple 傳進來,這樣,函式可以定義如下:
def cout(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
但是呼叫的時候,需要先組裝出一個 list 或 tuple
使用列表傳遞引數:
def cout(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
print(cout([1, 2, 3]))
使用可變引數
如果利用可變引數,呼叫函式的方式可以簡化成這樣:
def cout(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
print(cout(1, 2, 3))
定義可變引數和定義一個list或tuple引數相比,僅僅在引數前面加了一個 * 號。在函式內部,引數 numbers 接收到的是一個 tuple,因此,函式程式碼完全不變。
但是,呼叫該函式時,可以傳入任意個引數,包括0個引數。
不可變引數傳遞
請看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函式 person
除了必選引數 name
和 age
外,還接受關鍵字引數 **kw
。在呼叫該函式時,可以只傳入必選引數:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Michael', 30)
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Bob', 35, city='Beijing')
#name: Bob age: 35 other: {'city': 'Beijing'}
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Adam', 45, gender='M', job='Engineer')
#name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
關鍵字引數有什麼用?它可以擴充套件函式的功能。比如,在 person
函式裡,我們保證能接收到 name
和 age
這兩個引數,但是,如果呼叫者願意提供更多的引數,我們也能收到。試想你正在做一個使用者註冊的功能,除了使用者名稱和年齡是必填項外,其他都是可選項,利用關鍵字引數來定義這個函式就能滿足註冊的需求。
和可變引數類似,也可以先組裝出一個 dict
,然後,把該 dict
轉換為關鍵字引數傳進去:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, city=extra['city'], job=extra['job'])
#name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
簡化版:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
#name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
請你設計一個函式 print_avg
,這個函式接收多個關鍵字引數作為學生的資訊,接收多個數字引數作為這個學生多次考試的成績,請從學生資訊中提取出學生的 student_name
,student_age
,然後求出這個學生多次考試的平均成績 Average
(保留兩位小數),返回一個字串,格式如下:
name: student_name, age: student_age, avg: Average # Please write your code here def print_avg(*args,**kwargs): name=kwargs['student_name'] age=kwargs['student_age'] avg=sum(args)/len(args) return f'name: {name}, age: {age}, avg: {avg:.2f}' #return 'name : {name}, age : {age} ,avg :{avg}'.format(name,age,avg)
預設引數
def 函式名(...,形參名,形參名=預設值): 程式碼塊
注意,在使用此格式定義函式時,指定有預設值的形式引數必須在所有沒預設值引數的最後,否則會產生語法錯誤。
為引數指定預設值是非常有用的方式。呼叫函式時,可以使用比定義時更少的引數,例如:
def ask_ok(prompt, retries=4, reminder='Please try again!'): while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retries < 0: raise ValueError('invalid user response') print(reminder)
該函式可以用以下方式呼叫:
- 只給出必選實參:
ask_ok('Do you really want to quit?')
- 給出一個可選實參:
ask_ok('OK to overwrite the file?', 2)
- 給出所有實參:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
本例還使用了關鍵字 [object Object],用於確認序列中是否包含某個值。
預設值在 定義 作用域裡的函式定義中求值,所以:
i = 5def f(arg=i): print(arg)
i = 6 f() #5
重要警告: 預設值只計算一次。預設值為列表、字典或類例項等可變物件時,會產生與該規則不同的結果。例如,下面的函式會累積後續呼叫時傳遞的引數:
def f(a, L=[]): L.append(a) return L print(f(1)) #[1] print(f(2)) #[1,2] print(f(3)) #[1,2,3]
關鍵字引數與特殊引數
關鍵字引數
kwarg=value
形式的 關鍵字引數 也可以用於呼叫函式。函式示例如下:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'): print("-- This parrot wouldn't", action, end=' ') print("if you put", voltage, "volts through it.") print("-- Lovely plumage, the", type) print("-- It's", state, "!")
該函式接受一個必選引數(voltage
)和三個可選引數(state
, action
和 type
)。該函式可用下列方式呼叫:
parrot(1000) # 1 positional argument parrot(voltage=1000) # 1 keyword argument parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments parrot('a million', 'bereft of life', 'jump') # 3 positional arguments parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
以下呼叫函式的方式都無效:
parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument parrot(110, voltage=220) # duplicate value for the same argument parrot(actor='John Cleese') # unknown keyword argument
函式呼叫時,關鍵字引數必須跟在位置引數後面。所有傳遞的關鍵字引數都必須匹配一個函式接受的引數(比如,actor
不是函式 parrot
的有效引數),
關鍵字引數的順序並不重要。這也包括必選引數,(比如,parrot(voltage=1000)
也有效)。不能對同一個引數多次賦值。
最後一個形參為 **name
形式時,接收一個字典,該字典包含與函式中已定義形參對應之外的所有關鍵字引數。
**name
形參可以與 *name
形參(下一小節介紹)組合使用(*name
必須在 **name
前面), *name
形參接收一個元組,該元組包含形參列表之外的位置引數。
例如,可以定義下面這樣的函式:
def cheeseshop(kind, *arguments, **keywords): print("-- Do you have any", kind, "?") print("-- I'm sorry, we're all out of", kind) for arg in arguments: print(arg) print("-" * 40) for kw in keywords: print(kw, ":", keywords[kw])
def cheeseshop(kind, *arguments, **keywords): print("-- Do you have any", kind, "?") print("-- I'm sorry, we're all out of", kind) for arg in arguments: print(arg) print("-" * 40) for kw in keywords: print(kw, ":", keywords[kw]) cheeseshop("Limburger", "It's very runny, sir.", "It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")
特殊引數
預設情況下,引數可以按位置或顯式關鍵字傳遞給 Python 函式。為了讓程式碼易讀、高效,最好限制引數的傳遞方式,這樣,開發者只需檢視函式定義,即可確定引數項是僅按位置、按位置或關鍵字,還是僅按關鍵字傳遞。
函式定義如下:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- | | | | Positional or keyword | | - Keyword only -- Positional only
/
和 *
是可選的。這些符號表明形參如何把引數值傳遞給函式:位置、位置或關鍵字、關鍵字。關鍵字形參也叫作命名形參。
1. 位置或關鍵字引數
函式定義中未使用 /
和 *
時,引數可以按位置或關鍵字傳遞給函式。
2. 僅位置引數
此處再介紹一些細節,特定形參可以標記為 僅限位置。僅限位置 時,形參的順序很重要,且這些形參不能用關鍵字傳遞。僅限位置形參應放在 /
(正斜槓)前。/
用於在邏輯上分割僅限位置形參與其它形參。如果函式定義中沒有 /
,則表示沒有僅限位置形參。
/
後可以是 位置或關鍵字 或 僅限關鍵字 形參。
3. 僅限關鍵字引數
把形參標記為 僅限關鍵字,表明必須以關鍵字引數形式傳遞該形參,應在引數列表中第一個 僅限關鍵字 形參前新增 *
。