National Yang Ming Chiao Tung University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

陽明交大 AQI 69 24°C PM2.5 26
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 15

匿名函數與變數可見度

lambda、函數當參數,以及區域與全域變數
Interaction實作 25 minute(s)

Learning Objectives

  • 使用匿名函數(lambda),並把函數當作引數傳給另一個函數
  • 命名空間(namespace)與變數的可見度(scope)
  • 區域變數(local variables)的用法
  • 全域變數(global variables)的用法與 global 關鍵字

這一課要回答的兩個問題

  1. 能不能不取名字,直接寫一個小函數? 有些函數只有一行、只用一次,例如「排序時依照分數」。 為它特地寫 def 顯得冗長,這時可以用 lambda。
  2. 同一個名字,在函數裡和函數外是同一個變數嗎? 上一課看到函數裡的 myvar 和外面的 myvar 各在自己的框架裡。這一課要正式說明 Python 怎麼決定「這個名字指的是哪一個變數」,也就是變數的可見度(scope)。

Lambda 運算

lambda 又稱為匿名(anonymous)函數:

lambda 參數1, 參數2, ...: 運算式
  • 可以接收多個引數,但只能有一個運算式,運算式的結果就是傳回值(不必寫 return)。
  • 語意上它只是正規函數定義的語法糖(syntactic sugar):功能一樣,寫法更簡潔。 這個詞由英國電腦科學家 Peter Landin 提出,指不影響語言功能、但讓程式設計師更方便的語法。

用變數參考 lambda 函數

第 2 行把一個 lambda 函數指派給 sum_data,之後就能像一般函數一樣呼叫。播放時注意:呼叫 lambda 時,右邊也會出現一個框架,名稱顯示為 <lambda>,因為它本身沒有名字。

把函數當作引數

在 Python 裡,函數也是物件,可以像數字、字串一樣當作引數傳遞。operate_on 不知道也不在乎 func 是哪個函數,只負責呼叫它。看第 9 行傳入有名字的 sum_data、第 11 行直接傳入 lambda。

lambda 當作 sorted() 與 max() 的 key

key 參數接收一個函數,sorted() 會對每個元素呼叫它,再依照傳回的結果排序。數數看第 3 行的 lambda 被呼叫了幾次:每個學生一次。

想一想:lambda 與函數引數

1.

下面的程式會印出什麼?

f = lambda x, y: x * y + 1
print(f(3, 4))
2.

下面的程式會印出什麼?

def apply(func, v):
    return func(func(v))

print(apply(lambda n: n * 2, 3))
3.

下面的程式會印出什麼?

words = ["pear", "fig", "banana"]
print(sorted(words, key=lambda w: len(w)))
第 1 次作答

命名空間與變數的可見度

變數是對應到物件的名稱。Python 把「名稱與物件的對應」存放在一種資料結構裡,稱為命名空間(namespace)。

  • 每個函數被呼叫時,都有自己的區域命名空間;在函數之外的名稱,放在模組的全域命名空間。
  • 在函數內被指派的變數是區域變數,只能在那個函數裡存取,函數結束就消失。
  • 在函數外的變數是全域變數,整個程式(包含所有函數)都可以讀取。
  • Python 沒有「宣告變數」這件事(沒有 int x = 5 這種寫法)。只要函數裡有指派給某個名稱,它就自動被視為區域變數, 除非用 global 明確宣告。
  • 區域變數與全域變數同名時,函數裡以區域變數優先。

視覺化工具右邊的每一個框架,就是一個命名空間:最下面的模組框架是全域,上面的函數框架是區域。

區域變數蓋掉同名的全域變數

全域和 sum_data 裡都有一個 total。看第 5 行執行後:新的 total 出現在 sum_data 的框架,模組框架裡的 total 仍然是 0。函數結束後,區域的 total 跟著框架一起消失。

global 關鍵字

三個函數對 eggs 做了三件不同的事:ham() 只讀取、bacon() 指派(建立區域變數)、spam() 先宣告 global 再指派(修改全域變數)。預測一下三個 print 各印出什麼,再播放驗證。

LEGB:名稱的查找順序

函數裡還可以再定義函數。這時候 Python 找一個名稱的順序是由內往外的 LEGB:

順序 範圍 說明
L Local 目前這個函數的區域
E Enclosing 外層函數的區域(函數裡定義函數時才有)
G Global 模組的全域
B Built-in Python 內建的名稱,例如 print、len、range

找到就停;四層都找不到,就會引發 NameError。

動手查名稱:LEGB 階梯

點選上方的名稱,看 Python 從 Local 開始一層一層往外找,在哪一層找到。試試 x:Local 沒有,在 Enclosing 找到,所以不會用到全域的 x;再試試 z,四層都找不到會怎樣?

巢狀函數與 Enclosing

播放到第 8 行時,右邊會有三個框架:模組、outer、inner。inner 的框架裡只有 y,所以 x 要往外層 outer 找,印出的是 enclosing,而不是 global。

追蹤變數的可見度

1.

下面的程式會印出什麼?

n = 1
def f():
    n = 5
    return n

print(f(), n)
2.

下面的程式會印出什麼?

count = 0
def add():
    global count
    count += 1

add()
add()
print(count)
3.

把上一題的 global count 這一行刪掉,執行結果是什麼?

count = 0
def add():
    count += 1

add()
print(count)
第 1 次作答

實作:排名與計數器

完成兩個 TODO:(1) 用 sorted() 搭配 lambda,把 students 依分數由高到低排序;(2) 讓 record() 每被呼叫一次,全域變數 calls 就加 1。

你的輸出
預期輸出

重點精華回顧

  • lambda 參數: 運算式 建立匿名函數;只能有一個運算式,結果就是傳回值,是正規函數的語法糖。
  • 函數是物件,可以指派給變數、當作引數傳給別的函數;最常見的是 sorted()、max() 的 key。
  • 命名空間存放名稱與物件的對應;每次函數呼叫都有自己的區域命名空間。
  • 函數裡被指派的名稱是區域變數,只在函數裡有效;函數外的是全域變數,函數裡可以直接讀取。
  • 要在函數裡修改全域變數,必須先宣告 global 名稱;否則會建立同名的區域變數,或引發 UnboundLocalError。
  • 名稱查找順序 LEGB:Local → Enclosing → Global → Built-in,找到就停。
這一課完成了!
Next lesson:模組設計與使用
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。