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



ภาษา python เบื้องต้น บทที่ ๓๔: การสร้างมอดูล
เขียนเมื่อ 2016/05/01 15:04
ในบทที่ ๑๕ ได้พูดถึงเรื่องการเรียกใช้มอดูลไปแล้ว ได้พูดถึงไปว่ามอดูลคืออะไร ทำงานยังไง ประกอบไปด้วยอะไร

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



รู้จักกับมอดูลให้มากขึ้นอีกสักหน่อย
ดังที่ได้กล่าวไปแล้วว่ามอดูลนั้นมีทั้งมอดูลพื้นฐานที่มีติดตัวอยู่แล้วแต่แรก เช่น math, sys, os, random และอีกชนิดคือมอดูลเพิ่มเติมที่ถูกสร้างโดยคนที่ไม่ได้เกี่ยวข้องกับกลุ่ม ที่พัฒนาภาษาไพธอนโดยตรง

มอดูลเสริมเพิ่มเติมที่นิยมกันมากเช่น numpy, scipy, matplotlib เป็นต้น โดยทั่วไปแล้วมีโครงสร้างที่ซับซ้อน ถูกสร้างขึ้นโดยกลุ่มคนหรืออาจเป็นบริษัทใหญ่ซึ่งมีความเชี่ยวชาญเฉพาะทาง เป็นอย่างดี

อย่างไรก็ตาม ความจริงแล้วมอดูลเป็นสิ่งที่สร้างขึ้นได้อย่างง่ายๆ แค่คนเดียวก็สามารถสร้างขึ้นเองได้ เพราะที่จริงแค่เขียนโปรแกรมด้วยภาษาไพธอนสร้างไฟล์ .py ขึ้นมาอันหนึ่ง เพียงเท่านี้ก็สามารถกลายเป็นมอดูลได้แล้ว

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

แต่ไฟล์โปรแกรมธรรมดานี้จะกลายมาเป็นมอดูลเมื่อมันถูกเรียกให้ทำงานโดยใช้คำสั่ง import เข้ามาในโปรแกรมอื่น

เพียงแต่ว่ามอดูลมีวิธีการสร้างอยู่หลากหลาย อาจถูกเขียนขึ้นด้วยภาษาไพธอนเอง หรือใช้ภาษาอื่นเช่นภาษาซี หรือมีโปรแกรมอื่นมาประกอบด้วยก็เป็นได้

ในที่นี้จะกล่าวถึงเฉพาะการสร้างมอดูลอย่างง่าย คือการสร้างมอดูลโดยเขียนโค้ดภาษาไพธอนด้วยความรู้แค่เท่าที่ได้กล่าวไปในบทที่ผ่านๆมา

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

มอดูลที่ประกอบขึ้นจากหลายๆไฟล์มารวมอยู่ในโฟลเดอร์หนึ่ง (และอาจมีโฟลเดอร์ย่อยในนั้นอีก) จะเรียกว่าเป็นแพ็กเกจ (package)

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



การเตรียมไฟล์สำหรับทำเป็นมอดูล
ไฟล์ที่ต้องการจะใช้เป็นมอดูลนั้นไม่ใช่ว่าวางไว้ที่ไหนในเครื่องก็สามารถเรียก ใช้ได้ทันที เพราะจำเป็นจะต้องถูกวางไว้ในโฟลเดอร์ที่อยู่ในพาธ (path) ที่ถูกกำหนดไว้

พาธที่วางมอดูลได้นั้นมีอยู่หลายแห่ง สามารถดูได้โดยจะอยู่ในตัวแปรที่ชื่อว่า path ของมอดูล sys
import sys
print(sys.path)

ผลที่ได้ก็คือลิสต์ของพาธทั้งหมดที่เก็บมอดูล

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

อย่างไรก็ตามมีวิธีที่ง่ายกว่านั้น เพราะลิสต์ sys.path สามารถเพิ่มสมาชิกเข้าไปได้ หมายความว่าถ้าเราใส่พาธของโฟลเดอร์ที่ต้องการจะใช้ลงไป โฟลเดอร์นั้นก็จะกลายเป็นพาธสำหรับใส่มอดูลได้

การเพิ่มสมาชิกในลิสต์ก็เหมือนลิสต์ทั่วไป สามารถใช้คำสั่ง append เช่น
sys.path.append('C:\Program Files\nana')

เท่านี้โฟลเดอร์ nana ก็จะสามารถเก็บไฟล์ที่จะใช้เป็นมอดูลได้แล้ว

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

ตัวอย่างเช่น ลองสร้างไฟล์ momo.py แล้วพิมพ์โค้ดสั้นๆตามนี้ลงไปแล้วเซฟ
print('มอดูล momo ได้ถูกเรียกใช้แล้ว')

เท่านี้ก็จะได้มอดูลที่ชื่อว่า momo ชื่อของโมดูลก็จะตรงกับชื่อของไฟล์ (โดยไม่รวม .py) ซึ่งชื่อนี้เป็นชื่อที่จะต้องใช้เวลาที่ import ในโปรแกรมอื่น

จากนั้นเขียนอีกไฟล์ชื่ออะไรก็ได้แล้วเซฟไว้ในโฟลเดอร์เดียวกัน
import momo

จากนั้นก็รันไฟล์นี้ ผลลัพธ์ที่ได้ก็คือมีข้อความขึ้นว่า
มอดูล momo ได้ถูกเรียกใช้แล้ว

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

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

เช่นเวลาเราสั่ง import math จะพบว่าไม่มีอะไรเกิดขึ้น แต่หลังจากนั้นเราก็สามารถใช้ฟังก์ชันและตัวแปรต่างๆในมอดูลนี้ได้ทันที เช่นตัวแปร math.py ฟังก์ชัน marh.sin() นั่นเป็นเพราะว่าภายในมอดูลนี้ได้มีการนิยามตัวแปรและฟังก์ชันไว้นั่นเอง



การสร้างตัวแปรและฟังก์ชันภายในมอดูล
เราสามารถประกาศตัวแปรขึ้นมาภายในมอดูลได้โดยใช้วิธีเหมือนการประกาศตัวแปรทั่วไป

ฟังก์ชันก็สามารถประกาศได้เช่นเดียวกับฟังก์ชันทั่วไป นั่นคือใช้ def

ตัวอย่าง ลองพิมพ์ใน momo.py ว่า
akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'
def phim_akson_thai(tuathi):
    print(akson_thai[tuathi-1])

จากนั้นพิมพ์ตามนี้ในอีกไฟล์ที่อยู่ในโฟลเดอร์เดียวกันแล้วรัน
import momo
print(momo.akson_thai)
momo.phim_akson_thai(7)

ก็จะได้
กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ

จะเห็นว่าเวลาที่เรียกใช้ตัวแปรหรือฟังก์ชันที่มาจากมอดูลจะต้องมีชื่อมอดู ลนั้นนำแล้วตามด้วยจุดแล้วค่อยต่อด้วยชื่อตัวแปรหรือฟังก์ชันนั้น ซึ่งก็เป็นในทำนองเดียวกับที่เราต้องพิมพ์ math.pi และ math.sin เพื่อเรียกใช้ pi และ sin ที่อยู่ในมอดูล math นั่นเอง

และในทำนองเดียวกันก็สามารถใช้ from เพื่อที่จะใช้ตัวแปรและฟังก์ชันจากมอดูลนั้นได้โดยไม่ต้องนำด้วยชื่อมอดูล
from momo import *
print(akson_thai)
phim_akson_thai(7)



ข้อยกเว้นในการใช้ from import *
กฎการตั้งชื่อมอดูลและฟังก์ชันในมอดูลนั้นก็คล้ายกับการตั้งชื่อตัวแปรหรือ ฟังก์ชันหรือคลาสทั่วไป คือจะใช้อักษรภาษาอะไรก็ได้แต่ห้ามขึ้นต้นด้วยตัวเลขหรือใช้สัญลักษณ์พิเศษ นอกเหนือจากขีดล่าง _

อย่างไรก็ตามการตั้งชื่อตัวแปรหรือฟังก์ชันให้ ขึ้นต้นด้วยขีดล่าง _ นั้นมีนัยสำคัญบางอย่าง ซึ่งจะเกิดขึ้นเมื่อใช้ from และดอกจัน * เพื่อเรียกใช้มอดูล

โดยทั่วไปหากใช้ from และดอกจัน *
from ชื่อมอดูล import *

แบบนี้จะเป็นการนำเข้าตัวแปรและฟังก์ชันทั้งหมดที่ประกาศในมอดูลนั้น

แต่มีข้อยกเว้นอยู่เล็กน้อย นั่นคือชื่อตัวแปรและฟังก์ชันที่ขึ้นต้นด้วย _ จะไม่ถูกอ่าน

ลองแก้ใน momo.py เป็น
_akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'

แล้วลองรัน
from momo import *
print(_akson_thai)

ก็จะได้ว่า
NameError: name '_akson_thai' is not defined

การตั้งชื่อให้ขึ้นต้นด้วย _ นั้นจึงมีนัยสำคัญในกรณีนี้ จะเป็นชื่อฟังก์ชันหรือชื่อคลาสก็มีผลเหมือนกัน

แต่หากเปลี่ยนเป็นพิมพ์ชื่อของตัวแปรลงไปแทนที่จะใช้ดอกจัน *
from momo import _akson_thai
print(_akson_thai)

หรือ
import momo
print(momo._akson_thai)

แบบนี้ก็จะสามารถใช้ตัวแปรนี้ได้ตามปกติ

อนึ่ง แม้ว่าตัวแปรที่ชึ้นต้นด้วย _ จะไม่ถูกอ่านเมื่อถูกดึงเข้ามาในมอดูล แต่ก็สามารถเรียกใช้ผ่านฟังก์ชันภายในมอดูลนั้นได้ตามปกติ

เช่นแก้ใน momo.py เป็น
_akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'
def phim_akson_thai(tuathi):
    print(_akson_thai[tuathi-1])

จากนั้นรัน
import momo
momo.phim_akson_thai(7)

ฟังก์ชัน phim_akson_thai ก็จะไปเรียกใช้ตัวแปร _akson_thai มาแสดงผลได้ตามปกติ เพราะมอดูลก็เหมือนเป็นโปรแกรมหนึ่ง การทำงานภายในตัวมอดูลนั้นไม่ต่างจากการทำงานโดยการรันโดยตรง ชื่อตัวแปรในนั้นก็ต้องไม่เติมชื่อมอดูลนำหน้าด้วย



การสร้างคลาสภายในมอดูล
คลาสก็เช่นเดียวกับตัวแปรและฟังก์ชัน สามารถสร้างขึ้นมาใช้ได้

ตัวอย่าง ลองสร้างไฟล์ชื่อ dudu.py ขึ้นมาพิมพ์ว่า
class Phimandao:
    dao = '*'
    def __init__(self,m,n):
        self.m = m
        self.n = n
    def phimdao(self):
        return '\n'.join([self.dao*self.n]*self.m)

จากนั้นก็สร้างไฟล์ที่อยู่ในโฟลเดอร์เดียวกัน พิมพ์ตามนี้แล้วรัน
import dudu
mudao = dudu.Phimandao(3,9)
print(mudao.phimdao())
mudao.dao = '8'
mudao.m = 2
mudao.n = 17
print(mudao.phimdao())

ผลที่ได้
*********
*********
*********
88888888888888888
88888888888888888



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

ภาพนี้เป็นกรณีที่ใช้ไพธอน 3.5 ใน mac โดยใช้ cpython เมื่อไฟล์ชื่อ momo.py ถูกเรียกใช้ในฐานะมอดูล จะเกิดโฟล์เดอร์ __pycache__ ขึ้นและมีไฟล์ชื่อ momo.cpython-35.pyc อยู่ในนั้น



ไฟล์ .pyc นี้เป็นไฟล์ .py ที่ผ่านการคอมไพล์แล้ว ที่มันถูกสร้างขึ้นมาก็เพื่อว่าเวลาที่มีการเรียกใช้ใหม่อีกครั้งจะไม่ต้อง มีการคอมไพล์ใหม่แต่เรียกใช้ไฟล์ .pyc นี้โดยตรงเลย ซึ่งจะทำให้เรียกใช้ได้เร็วขึ้น

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

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



การโหลดมอดูลใหม่
หากเราเรียกใช้มอดูลผ่านเชลโต้ตอบโดยไม่ได้เรียกใช้จากไฟล์โดยตรง (คือใช้อินเทอร์พรีเตอร์ แทนที่จะใช้อีดิเตอร์เขียนแล้วสั่งรันไฟล์) กรณีแบบนี้หากมีการ import มอดูลอะไรไปครั้งหนึ่งแล้ว มอดูลนั้นจะไม่สามารถ import ซ้ำได้อีก

นั่นหมายความว่าต่อให้มีการแก้ไขไฟล์มอดูลนั้นไปแล้วกด import ซ้ำ ความเปลี่ยนแปลงนั้นก็จะไม่แสดงผลให้เห็น

หากต้องการให้มีการ reload จะต้องใช้ฟังก์ชัน reload ซึ่งอยู่ในมอดูล imp
import imp
imp.reload(momo)

มอดูล imp นั้นเป็นมอดูลที่เก็บฟังก์ชันต่างๆที่เกี่ยวกับการเรียกใช้มอดูล ยังมีฟังก์ชันอีกหลายอันที่ใช้ประโยชน์ได้

การ reload นั้นจำเป็นต้องใช้แค่ในกรณีที่ import ด้วยเชลเท่่านั้น ดังนั้นการแก้ปัญหาอาจทำได้ด้วยการ import จากการรันไฟล์



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

การนำโค้ดจากไฟล์อื่นมาใช้งานนอกจากการทำให้ไฟล์นั้นเป็นมอดูลแล้วยังมีอีกวิธีหนึ่ง นั่นคือใช้ฟังก์ชัน exec อ่านไฟล์

exec เป็นฟังก์ชันที่นำสายอักขระมาอ่านประมวลผลเป็นโค้ด
exec('print(1)\nprint(2)')

จะได้
1
2

ซึ่งผลเหมือนกับการที่เรารันโค้ด
print(1)
print(2)

ดังนั้นเราสามารถใช้ open เพื่อเปิดไฟล์ แล้วก็ใช้ exec เพื่อประมวลผลข้อความในไฟล์นั้นในฐานะโค้ด
f = open('momo.py',encoding='utf-8')
exec(f.read())

เพียงเท่านี้ก็จะได้ผลเหมือนเราก๊อปโค้ดจากไฟล์ momo.py มาแปะวางโดยที่ไม่ต้องทำให้ momo.py เป็นมอดูล

นอกจากฟังก์ชัน exec แล้วหากโค้ดมีแค่บรรทัดเดียวเราอาจใช้ฟังก์ชัน eval ได้ด้วย eval จะทำงานเหมือนกับ exec เพียงแต่ว่าจะอ่านโค้ดได้แค่บรรทัดเดียว การใช้มีข้อจำกัดกว่า exec มาก

ตัวอย่าง
eval('print(\'\'\'"\'(ö)\'"\'\'\')') # ได้ "'(ö)'"



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



อ้างอิง


<< บทที่แล้ว      บทถัดไป >>
หน้าสารบัญ


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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

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

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

ไทย

日本語

中文