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



ภาษา python เบื้องต้น บทที่ ๓๕: การทำมอดูลเป็นแพ็กเกจ
เขียนเมื่อ 2016/05/01 15:55
แก้ไขล่าสุด 2021/09/28 16:42
หลังจากที่ในบทที่แล้วได้เรียนรู้วิธีการสร้างมอดูลจากไฟล์ .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

สรุปลำดับไฟล์และโฟลเดอร์ตอนนี้
>> pika
-->> __init__.py
-->> momo.py
-->> dudu.py
-->> lele.py
-->> pikapika
---->> __init__.py
---->> momomo.py
---->> dududu.py
---->> lelele.py



กรณีแบบนี้สมมุติว่าต้องการจะเรียก dududu จาก momo ก็จะต้องพิมพ์ใน momo.py ว่า
from .pikapika import dududu

สังเกตว่าหน้า pikapika ต้องมีจุดด้วย หมายถึงการอ้างอิงโฟลเดอร์ที่ตัวเองอยู่แล้วเข้าไปในโฟลเดอร์ pikapika อีกที

และหากต้องการเรียก lele จาก dududu ต้องพิมพ์ใน dududu.py ว่า
from .. import lele

จุดสองจุด .. หมายถึงถอยหลังไปสองขั้น ในที่นี้ถอยออกไปจนถึงโฟลเดอร์ที่มี lele

หากยิ่งซ้อนโฟลเดอร์เยอะก็แค่ต้องเพิ่มจำนวนจุดแล้วไล่ลำดับเครือญาติกันให้ถูก ต้อง เท่านี้ก็จะเรียกซับมอดูลจากส่วนไหนก็ได้ภายในมอดูล

เพียงแต่ว่าไม่สามารถใช้จุดถอยหลังไปจนออกนอกโฟลเดอร์ของแพ็กเกจได้ เช่นหากพิมพ์ใน 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-35.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-35.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
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
maya
javascript
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

ไทย

日本語

中文