2.19. การเขียนโมดูล

ไฟล์ .py ใดๆ ก็คือโมดูล การแบ่งสคริปต์ที่กำลังเติบโตออกเป็นหลายไฟล์ทำให้แต่ละไฟล์สั้นลงและช่วยให้ helper ทั่วไปสามารถแบ่งปันระหว่างสคริปต์ได้

2.19.1. การแบ่งสคริปต์

ดึงกลุ่มฟังก์ชันที่เกี่ยวข้องออกมาในไฟล์ของมันเอง:

camera_utils.py

def banner():
    print("OpenMV")

def label(text):
    return "[" + text + "]"

main.py

import camera_utils

camera_utils.banner()
print(camera_utils.label("ready"))

ผลลัพธ์:

OpenMV
[ready]

สองไฟล์อยู่เคียงข้างกันในไดเรกทอรีเดียวกัน เมื่อ main.py ทำงาน import camera_utils จะอ่าน camera_utils.py ครั้งเดียว ดำเนินการ statement ระดับบนสุด และผูกอ็อบเจ็กต์โมดูลที่ได้กับชื่อ camera_utils ใน main import camera_utils ครั้งที่สองจากที่ไหนก็ตามจะคืนอ็อบเจ็กต์เดียวกัน -- โมดูลถูก cache หลังจากโหลดครั้งแรก

ชื่อของโมดูลมาจากชื่อไฟล์ ดังนั้น camera_utils.py จะถูก import เป็น import camera_utils

2.19.2. โมดูลหลายไฟล์ (packages)

โมดูลยังสามารถเป็น ไดเรกทอรี ของไฟล์แทนที่จะเป็นไฟล์ .py เดียว ชื่อของไดเรกทอรีกลายเป็นชื่อโมดูล และไฟล์ภายในคือ submodules ของมัน:

camera_utils/
    __init__.py
    text.py
    timing.py

__init__.py คือไฟล์ที่ทำงานเมื่อ package เองถูก import; มันสามารถว่างเปล่า หรือสามารถ re-export ชื่อที่เลือกจาก submodules Submodules ถูกเข้าถึงด้วยชื่อแบบจุด:

import camera_utils.text
from camera_utils.timing import elapsed

camera_utils.text.label("ready")

ภายใน package, submodules สามารถเข้าถึงกันและกันได้ทั้งด้วยชื่อแบบจุดแบบเต็ม หรือด้วย relative import ที่ใช้จุดนำหน้าเพื่อหมายถึง "package นี้":

camera_utils/timing.py

from . import text             # the sibling submodule

def stamp(value):
    return text.label(str(value))

เพื่อดึงชื่อเฉพาะแทน ให้ระบุชื่อหลัง sibling แบบจุด:

from .text import label

def stamp(value):
    return label(str(value))

Relative imports ทำให้ package พึ่งพาตัวเองได้: การเปลี่ยนชื่อไดเรกทอรี package ไม่จำเป็นต้องแก้ไขทุก submodule

ใช้ package เมื่อไฟล์เดียวเติบโตเกินขนาดที่สะดวก หรือเมื่อชุดของโมดูลที่เกี่ยวข้องเป็นของกันและกันภายใต้ namespace เดียว สำหรับสคริปต์ประจำวันไฟล์ .py เดียวก็เพียงพอแล้ว

2.19.3. การป้องกันด้วย __name__

ทุกโมดูลมีชื่อในตัว __name__ ค่าของมันขึ้นอยู่กับวิธีที่ไฟล์ถูกใช้งาน:

  • เมื่อไฟล์ รันโดยตรง __name__ จะถูกตั้งค่าเป็น string "__main__"

  • เมื่อไฟล์ ถูก import โดยสคริปต์อื่น __name__ จะถูกตั้งค่าเป็นชื่อโมดูล -- ชื่อไฟล์ไม่รวม .py

idiom ที่ใช้สิ่งนี้คือ:

label_util.py

def label(text):
    return "[" + text + "]"

if __name__ == "__main__":
    print(label("self-test"))

ผลลัพธ์ (เมื่อรันโดยตรง):

[self-test]

เมื่อไฟล์เดียวกัน ถูก import แทน __name__ จะถูกตั้งค่าเป็นชื่อโมดูล ดังนั้น block if จะถูกข้ามและไม่มีอะไรเพิ่มเติมทำงาน:

>>> import label_util
>>> label_util.__name__
'label_util'

ใช้รูปแบบนี้เพื่อแนบการทดสอบอย่างเร็วหรือ demo ไปกับไฟล์ library โดยไม่รบกวนสคริปต์ที่ import มัน