หลังจากที่ใน
บทที่แล้วได้เรียนรู้วิธีการสร้างมอดูลจากไฟล์
.py ตัวเดียวแล้ว คราวนี้มาพูดถึงการทำมอดูลซึ่งเป็นแพ็กเกจซึ่งประกอบขึ้นจากหลายไฟล์
การสร้างแพ็กเกจ การสร้างมอดูลแพ็กเกจทำได้โดยการสร้างโฟลเดอร์ขึ้นมาและใส่ไฟล์
.py ที่ต้องการรวมอยู่ในแพ็คเกจเอาไว้ในโฟลเดอร์นั้น
ในกรณีนี้ชื่อของมอดูลแพ็กเกจจะเป็นไปตามชื่อของโฟลเดอร์นี้ ส่วนชื่อของไฟล์
.py ในโฟลเดอร์จะกลายเป็นซับมอดูลภายในมอดูลนี้ไป
แต่นอกจากไฟล์ของ
.py ของโปรแกรมที่ต้องการใช้เป็นซับมอดูลแล้ว ภายในโฟลเดอร์ยังมีอีกสิ่งที่ขาดไม่ได้อยู่ นั่นก็คือไฟล์ที่ชื่อ
__init__.py __init__.py เป็นโปรแกรมศูนย์ควบคุมของมอดูล เป็นไฟล์ทีจะถูกเรียกใช้ให้ทำงานเมื่อมีการเรียกใช้มอดูลขึ้นขึ้นมา
ไฟล์
__init__.py นี้ขอแค่สร้างขึ้นมาให้มีอยู่ในโฟลเดอร์ก็พอ จะไม่เขียนโค้ดอะไรลงไปเลย สร้างไว้เฉยๆปล่อยว่างๆไว้ก็ไม่เป็นไร
ลองยกตัวอย่าง สร้างโฟลเดอร์ชื่อ
pika ขึ้นมา ภายในมีไฟล์ชื่อ
__init__.py,
momo.py,
dudu.py และ
lele.py แบบนี้เราจะได้มอดูลชื่อ
pika
ซึ่งมีซับมอดูล ๓ อันชื่อ
momo
,
dudu
และ
lele
จากสร้างไฟล์ที่ต้องการจะรันไว้ในโฟล์เดอร์เดียวกับที่โฟลเดอร์
pika ตั้งอยู (ไม่ใช่ในโฟลเดอร์
pika) แล้วพิมพ์
import pika
แบบนี้ก็จะเป็นการเรียกใช้มอดูล
pika
ขึ้นมา เพียงแต่ว่าไฟล์ที่จะถูกเรียกมีแค่
__init__.py ซึ่งอยู่ในโฟลเดอร์
pika เท่านั้น ส่วนไฟล์
momo.py,
dudu.py และ
lele.py จะยังไม่ถูกเรียก ดังนั้นยังไม่สามารถใช้อะไรในซับมอดูลเหล่านี้ได้
ดังนั้นจะต้องทำการ
import
แยกต่างหาก
import pika.momo
import pika.dudu
import pika.lele
เท่านี้ก็จะสามารถใช้ซับมอดูลทั้ง ๓ อันนี้ได้แล้ว
เพียงแต่ว่าโดยปกติแล้วเราจะเห็นว่าพวกแพ็กเกจมาตรฐานเวลาที่เราเรียกใช้ตัวมอดูล เราจะสามารถใช้มอดูลย่อยไปได้ด้วย แต่ก็ไม่ใช่ทุกมอดูลที่พอเรียกแล้วจะใช้ซับมอดูลได้ทันที
การจะทำให้พอเรียกใช้มอดูลที่เป็นแพ็กเกจแล้วสามารถใช้ซับมอดูลได้ทันทีโดยไม่ต้อง
import
ทีละอันนั้นจะต้องไปตั้ง
import
ภายใน
__init__.py การเรียกใช้มอดูลจากภายในมอดูลในแพ็กเกจเดียวกัน ไฟล์ที่อยู่ภายในมอดูลจะอ้างอิงถึงโฟลเดอร์ที่ตัวเองอยู่โดยจุด
.
ดังนั้นหากต้องการสั่ง
import
ไฟล์อื่นจากไฟล์
__init__.py จะต้องพิมพ์
from .
แล้วจึงตามด้วย
import
ถ้าพิมพ์ตามนี้ลงใน
__init__.py
from . import momo
from . import dudu
from . import lele
เพียงเท่านี้ก็แค่พิมพ์
import pika
ในไฟล์หลักที่รันก็จะสามารถเรียกใช้ซับมอดูลทั้งหมดได้
จะให้ซับมอดูลเรียกใช้ซับมอดูลด้วยกันก็ได้ เช่นลองลบ
__init__.py เหลือแค่
from . import momo
แล้วเขียนใน
momo.py ว่า
from . import dudu
และเขียนใน
dudu.py ว่า
from . import lele
เท่านี้พอ
import pika ก็จะเกิดการ
import ต่อกันเป็นทอดๆ ในที่สุดก็เป็นการเรียกใช้ทุกไฟล์
และจะพบว่าพอทำแบบนี้แล้วจะทำให้มีวิธีการอ้างถึง
lele
ได้ถึง ๓ แบบ คือ
pika.lele
pika.dudu.lele
pika.momo.dudu.lele
ทั้งหมดนี้ชี้มาที่
lele.py เหมือนกันหมด
ส่วน
dudu
ก็สามารถเรียกได้ ๒ แบบ คือ
pika.dudu
pika.momo.dudu
ที่เป็นแบบนี้ก็เพราะ
dudu
ถูกเรียกให้มาใช้ใน
momo
จึงสามารถเรียกผ่าน
momo
อีกต่อได้ และ
lele
ก็ถูกเรียกใช้ใน
dudu
จึงเรียกผ่าน
dudu
ได้ และเรียกผ่าน
dudu
ที่เรียกผ่าน
momo
อีกต่อได้
เรื่องจะยิ่งซับซ้อนไปอีกหากมีโฟลเดอร์ซ้อนอีก
ลองสร้างโฟล์เดอร์ชื่อ
pikapika ภายในโฟล์เดอร์
pika จากนั้นภายในนั้นก็สร้างไฟล์ชื่อ
__init__.py,
momomo.py,
dududu.py และ
lelele.py แบบนี้จะได้แพ็กเกจย่อยชื่อ
pikapika
ภายในแพ็กเกจหลัก
pika
ซึ่งภายในมีมอดูลย่อยชื่อ
momomo
,
dududu
และ
lelele
สรุปลำดับไฟล์และโฟลเดอร์ตอนนี้ กรณีแบบนี้สมมุติว่าต้องการจะเรียก
dududu
จาก
momo
ก็จะต้องพิมพ์ใน
momo.py ว่า
from .pikapika import dududu
สังเกตว่าหน้า
pikapika
ต้องมีจุดด้วย หมายถึงการอ้างอิงโฟลเดอร์ที่ตัวเองอยู่แล้วเข้าไปในโฟลเดอร์
pikapika อีกที
และหากต้องการเรียก
lele
จาก
dududu
ต้องพิมพ์ใน
dududu.py ว่า
from .. import lele
จุดสองจุด
..
หมายถึงถอยหลังไปสองขั้น ในที่นี้ถอยออกไปจนถึงโฟลเดอร์ที่มี
lele.py อยู่
หากยิ่งซ้อนโฟลเดอร์เยอะก็แค่ต้องเพิ่มจำนวนจุดแล้วไล่ลำดับเครือญาติกันให้ถูกต้อง เท่านี้ก็จะเรียกซับมอดูลจากส่วนไหนก็ได้ภายในมอดูล
เพียงแต่ว่าไม่สามารถใช้จุดถอยหลังไปจนออกนอกโฟลเดอร์ของแพ็กเกจได้ เช่นหากพิมพ์ใน
momo.py ว่า
from .. import lele
พอรันก็จะเกิดข้อผิดพลาดทันทีโดยขึ้นว่า
ValueError: attempted relative import beyond top-level package
ลำดับความสำคัญในกรณีที่ชื่อมีการซ้ำ กรณีที่มีไฟล์ที่ชื่อเหมือนกับแพ็กเกจอยู่ เวลาที่สั่ง
import
จะกลายเป็นการ
import
ตัวแพ็กเกจ เพราะลำดับความสำคัญมากกว่า
เช่น ถ้ามีไฟล์ชื่อ
pika.py อยู่พร้อมกับโฟลเดอร์ชื่อ
pika ซึ่งมีไฟล์ชื่อ
__init__.py อยู่ เวลาสั่ง
import pika
ไฟล์
__init__.py ในโฟลเดอร์
pika จะทำงาน แทนที่จะเป็นไฟล์
pika.py ส่วนกรณีที่ชื่อซับมอดูลซ้ำกับชื่อตัวแปรหรือฟังก์ช้นภายในมอดูล ลำดับความสำคัญของตัวแปรจะสูงกว่า
เช่น ถ้าหาก
__init__.py ในโฟลเดอร์
pika พิมพ์ว่า
momo = 'momotarou'
จากนั้นลองรัน
from pika import momo
สิ่งที่
import
เข้ามาจะเป็นตัวแปรชื่อ
momo
ไมใช่ตัวไฟล์ซับมอดูล
momo.py และถ้าหาก
__init__.py ในโฟลเดอร์
pika พิมพ์ว่า
pikapika = 'pikachu'
แล้วรัน
from pika import pikapika
จะได้ว่าสิ่งที่
import
มาได้ก็คือตัวแปรชื่อ
pikapika
ไม่ใช่ซับมอดูล
pikapika
ที่อยู่ในโฟลเดอร์ย่อย
แอตทริบิวต์ที่ติดตัวมอดูล เมื่อโหลดมอดูลมาเราจะพบว่ามีแอตทริบิวต์จำนวนหนึ่งที่ติดตัวมอดูลมาอยู่แล้วโดยที่เราไม่ได้ตั้งค่าเอาไว้ ได้แก่
__name__
ชื่อมอดูลหรือแพ็กเกจ
__file__
พาธและชื่อไฟล์ของมอดูลหรือแพ็กเกจ ถ้าใช้กับแพ็คเกจจะได้ __init__.py
__package__
ชื่อแพ็กเกจของมอดูลหรือแพ็คเกจ
__cached__
พาธและชื่อไฟล์ .pyc
__path__
พาธของแพ็กเกจ
__doc__
ด็อกสตริงของมอดูลหรือแพ็กเกจ
ทดลองใช้กับมอดูล
pika
และซับมอดูล
momo
import pika
print(pika.__name__) # ได้ pika
print(pika.__file__) # ได้ <พาธ>/pika/__init__.py
print(pika.__package__) # ได้ pika
print(pika.__cached__) # ได้ <พาธ>/pika/__pycache__/__init__.cpython-311.pyc
print(pika.__doc__) # ได้ None
print(pika.__path__) # ได้ ['<พาธ>/pika']
import pika.momo
print(pika.momo.__name__) # ได้ pika.momo
print(pika.momo.__file__) # ได้ <พาธ>/pika/momo.py
print(pika.momo.__package__) # ได้ pika
print(pika.momo.__cached__) # ได้ <พาธ>/pika/__pycache__/momo.cpython-311.pyc
print(pika.momo.__doc__) # ได้ None
import pika.pikapika
print(pika.pikapika.__file__) # ได้ <พาธ>/pika/pikapika/__init__.py
print(pika.pikapika.__package__) # ได้ pika.pikapika
print(pika.pikapika.__path__) # ได้ ['<พาธ>/pika/pikapika']
สำหรับ
__doc__
นั้นจะมาจากการใส่สายอักขระไว้ด้านบรรทัดบนสุดของไฟล์ สำหรับ
pika
นั้นต้องใส่ในไฟล์
pika/__init__.py แอตทริบิวต์
__path__
จะมีเฉพาะที่เป็นตัวแพ็กเกจเท่านั้น ดังนั้นจึงไม่สามารถใช้กับ
pika.momo ได้ แต่ใช้กับ
pika
และ
pika.pikapika
ได้
การทำให้โค้ดทำงานเฉพาะเมื่อรันโดยตรง หากใครเคยอ่านโค้ดที่คนอื่นลงตามเว็บอาจจะเจอ
if(__name__ == '__main__'):
อยู่บ่อยๆ
โดยทั่วไปแล้ว
__name__
เป็นแอตทริบิวต์ที่จะติดตัวไฟล์ที่ถูกรัน โดย
__name__
จะมีค่าเท่ากับ
__main__
เมื่อไฟล์นั้นถูกรันโดยตรง
แต่หากถูกรันในฐานะมอดูล
__name__
จะมีค่าเป็นชื่อมอดูลนั้น เช่นเราใช้มอดูล
pika
อยู่ ถ้าพิมพ์
print(__name__)
ใส่ใน
pika/__init__.py ก็จะได้
pika
ดังนั้นการพิมพ์
if(__name__ == '__main__'):
ลงไปจะมีความหมายว่าโค้ดต่อไปนี้จะทำงานเมื่อไฟล์นี้ถูกรันโดยตรงไม่ใช่ในฐานะมอดูล
บางครั้งเราอาจกำลังเขียนมอดูลอยู่แล้วต้องการจะเขียนโค้ดไว้เพื่อใช้เฉพาะเวลาที่รันตัวไฟล์นั้นเพื่อทดสอบโดยตรง กรณีแบบนี้ก็อาจใช้วิธีการเขียนแบบนี้ให้เป็นประโยชน์ได้
ตัวอย่าง
if(__name__ == '__main__'):
print('หากคุณเห็นข้อความนี้แสดงว่ากำลังรันโปรแกรมนี้โดยตรง')
สรุปเนื้อหา ในบทนี้ได้พูดถึงพื้นฐานการสร้างมอดูลที่เป็นแพ็กเกจ เพียงเท่านี้เราก็สามารถสร้างมอดูลของตัวเองขึ้นมาได้แล้ว และสามารถนำไปแจกจ่ายให้คนอื่นใช้ได้ด้วย
อ้างอิง