2.16. ขอบเขต¶
เมื่อ Python ค้นหาชื่อภายในฟังก์ชัน มันจะค้นหาตามลำดับ ขอบเขต ที่เฉพาะเจาะจง การเข้าใจลำดับนั้นอธิบายได้ว่าทำไมการกำหนดค่าบางครั้งบดบังชื่อภายนอก ทำไมบางครั้งแก้ไขชื่อเหล่านั้น และทำไมฟังก์ชันซ้อนกันจึงสามารถจำค่าจากที่ที่นิยามได้
การค้นหาชื่อเริ่มต้นที่ขอบเขต local ของฟังก์ชันและเดินออกไปยังขอบเขต module และ built-in จนกว่าจะพบตัวที่ตรงกัน¶
2.16.1. ขอบเขต local และ module¶
ชื่อที่กำหนดภายในฟังก์ชันเป็น local ของฟังก์ชันนั้นและหายไปเมื่อการเรียกสิ้นสุด:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
ชื่อที่กำหนดที่ระดับบนสุดของไฟล์ .py เป็น ระดับ module (บางครั้งเรียกว่า global) และมองเห็นได้ทุกที่ในไฟล์นั้น รวมถึงภายในฟังก์ชัน:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
การอ่านชื่อระดับ module จากภายในฟังก์ชันเป็นอัตโนมัติ การกำหนดค่า ให้กับชื่อนั้นจากภายในฟังก์ชันไม่ใช่ -- การกำหนดค่าสร้างตัวแปร local ใหม่ที่บดบังชื่อระดับ module ตลอดการเรียกที่เหลือ:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
counter ทางซ้ายทำให้ counter กลายเป็นชื่อ local ใน bump ดังนั้นการอ่านทางขวาจึงไม่มีค่าให้ค้นหา
2.16.1.1. คีย์เวิร์ด global¶
หากต้องการกำหนดค่าใหม่ให้กับชื่อระดับ module จากภายในฟังก์ชัน ให้ประกาศว่าเป็น global ก่อน:
counter = 0
def bump():
global counter
counter += 1
ใช้ global อย่างประหยัด ฟังก์ชันที่เปลี่ยนแปลง state ที่ซ่อนอยู่นั้นยากต่อการตรวจสอบกว่าฟังก์ชันที่รับค่าเป็นอาร์กิวเมนต์และคืนค่าใหม่ออกมา วิธีแก้ปกติสำหรับ "ต้องการแชร์ state" คือส่งอ็อบเจกต์ (list, dict, หรือ class instance) เป็นอาร์กิวเมนต์และเปลี่ยนแปลง มัน แทน
2.16.2. Lambda¶
lambda สร้างฟังก์ชันนิรนามขนาดเล็กในนิพจน์เดียว:
square = lambda x: x * x
square(7) # 49
มันเทียบเท่ากับ
def square(x):
return x * x
เนื้อหาของ lambda ต้องเป็นนิพจน์เดียว -- ไม่มีคำสั่ง ไม่มีหลายบรรทัด การใช้งานหลักคือการส่งฟังก์ชันขนาดเล็กเป็นอาร์กิวเมนต์ให้กับสิ่งที่รับฟังก์ชัน:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
เมื่อเนื้อหาขยายเกินหนึ่งนิพจน์ ให้เปลี่ยนไปใช้ def จริงๆ การตั้งชื่อฟังก์ชันด้วย def ยังทำให้มีชื่อใน traceback ซึ่ง lambda ไม่มี
2.16.3. Closure¶
ฟังก์ชันที่กำหนดภายในฟังก์ชันอื่นสามารถอ่านชื่อจากขอบเขตของฟังก์ชันที่ครอบ ฟังก์ชันภายใน จับ ชื่อเหล่านั้นและทำงานต่อไปได้แม้ว่าการเรียกภายนอกจะสิ้นสุดแล้ว:
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))
ผลลัพธ์:
105 110
add5 และ add10 เป็นสองฟังก์ชันที่แยกจากกัน แต่ละตัวจำ n ของตัวเอง ฟังก์ชันที่สร้างและคืนฟังก์ชันภายในที่ปรับแต่งแล้วในลักษณะนี้เรียกว่า closure มันเป็นเหตุผลหลักที่ภาษาต้องการฟังก์ชันซ้อนกัน -- วิธีการอบ state บางส่วนลงในค่าฟังก์ชันแล้วส่งค่านั้นออกไปเป็น callable เดียว
การอ่านชื่อที่ถูกจับเป็นอัตโนมัติ การ rebind ต้องใช้คีย์เวิร์ดเพิ่มเติม ตัวอย่างด้านล่างทำในสิ่งที่ดูถูกต้องแต่ล้มเหลว:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
การกำหนดค่าให้ count ภายใน tick ทำให้ count กลายเป็น local ของ tick เหมือนกับที่มันจะทำให้เป็น local ในฟังก์ชันระดับบนสุด คีย์เวิร์ด nonlocal บอก Python ว่า "ชื่อนี้อยู่ในฟังก์ชันที่ครอบ ให้ rebind ที่นั่น":
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
ผลลัพธ์:
1 2 3
nonlocal สำหรับขอบเขตฟังก์ชันที่ครอบเหมือนกับที่ global ใช้กับขอบเขต module โปรดทราบว่าการ เปลี่ยนแปลง อ็อบเจกต์ที่ถูกจับ (การเรียก some_list.append(...), some_dict[k] = v) ไม่ต้องใช้ nonlocal -- ชื่อไม่ถูก rebind แค่อ็อบเจกต์ที่มันชี้ไปถูกเปลี่ยนแปลง