φυβλαςのβλογ
บล็อกของ phyblas



ภาษา python เบื้องต้น บทที่ ๓๕: การทำมอดูลเป็นแพ็กเกจ
เขียนเมื่อ 2016/05/01 15:55
แก้ไขล่าสุด 2024/02/22 11:10
 

หลังจากที่ในบทที่แล้วได้เรียนรู้วิธีการสร้างมอดูลจากไฟล์ .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('หากคุณเห็นข้อความนี้แสดงว่ากำลังรันโปรแกรมนี้โดยตรง')



สรุปเนื้อหา

ในบทนี้ได้พูดถึงพื้นฐานการสร้างมอดูลที่เป็นแพ็กเกจ เพียงเท่านี้เราก็สามารถสร้างมอดูลของตัวเองขึ้นมาได้แล้ว และสามารถนำไปแจกจ่ายให้คนอื่นใช้ได้ด้วย



อ้างอิง




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文