1. 程式人生 > 其它 >python變數與引數

python變數與引數

名稱空間與作用域

名稱空間提供了在專案中避免名字衝突的一種方法。各個名稱空間是獨立的,沒有任何關係的,所以一個名稱空間中不能有重名,但不同的名稱空間是可以重名而沒有任何影響。我們舉一個計算機系統中的例子,一個資料夾(目錄)中可以包含多個資料夾,每個資料夾中不能有相同的檔名,但不同資料夾中的檔案可以重名。

一般有三種名稱空間:

  • 內建名稱(built-in names),Python 語言內建的名稱,比如函式名 abschr 和異常名稱 BaseExceptionException 等等。
  • 全域性名稱(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()

作用域

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)以及函式(deflambda)才會引入新的作用域,其它的程式碼塊(如 if/elif/else/、try/exceptfor/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_namestudent_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 = 5
def 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)和三個可選引數(stateaction 和 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. 僅限關鍵字引數

把形參標記為 僅限關鍵字,表明必須以關鍵字引數形式傳遞該形參,應在引數列表中第一個 僅限關鍵字 形參前新增 *