ใน
บทที่ ๑๕ ได้พูดถึงเรื่องการเรียกใช้มอดูลไปแล้ว ได้พูดถึงไปว่ามอดูลคืออะไร ทำงานยังไง ประกอบไปด้วยอะไร
หลังจากที่ได้เรียนรู้การใช้มอดูลที่ติดตัวโปรแกรมมารวมถึงมอดูลที่คนอื่นสร้างขึ้นไปแล้ว สำหรับในบทนี้ได้เวลาที่จะเริ่มลองสร้างมอดูลขึ้นมาเองบ้าง
รู้จักกับมอดูลให้มากขึ้นอีกสักหน่อย ดังที่ได้กล่าวไปแล้วว่ามอดูลนั้นมีทั้งมอดูลพื้นฐานที่มีติดตัวอยู่แล้วแต่แรก เช่น
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.pi
ฟังก์ชัน
math.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.11 เมื่อไฟล์ชื่อ
momo.py ถูกเรียกใช้ในฐานะมอดูล จะเกิดโฟล์เดอร์
__pycache__ ขึ้นและมีไฟล์ชื่อ
momo.cpython-311.pyc อยู่ในนั้น
ไฟล์
.pyc นี้เป็นไฟล์
.py ที่ผ่านการคอมไพล์แล้ว ที่มันถูกสร้างขึ้นมาก็เพื่อว่าเวลาที่มีการเรียกใช้ใหม่อีกครั้งจะไม่ต้อง มีการคอมไพล์ใหม่แต่เรียกใช้ไฟล์
.pyc นี้โดยตรงเลย ซึ่งจะทำให้เรียกใช้ได้เร็วขึ้น
ขอเน้นว่าที่ว่าเร็วขึ้นในที่นี้ไม่ได้หมายถึงทำให้คำสั่งในมอดูลทำงานเร็วขึ้น เพียงแต่จะทำให้เร็วในตอนที่โหลดมอดูลใช้เท่านั้น แต่เมื่อโหลดมาแล้วการใช้ฟังก์ชันหรือคลาสต่างๆจากมอดูลนั้นก็ไม่ได้เร็วขึ้น
ตราบใดที่ไม่มีการแก้ไขไฟล์
.py ไป เวลาที่มอดูลนั้นถูกเรียกใช้ก็จะไม่มีการอ่านไฟล์
.py ซ้ำแต่จะไปอ่าน
.pyc โดยตรง แต่หากมีการแก้ไฟล์
.py เมื่อไหร่จึงจะมีการอ่านไฟล์
.py ซ้ำเพื่อคอมไพล์และสร้าง
.pyc ขึ้นมาใหม่
การโหลดมอดูลใหม่ หากเราเรียกใช้มอดูลผ่านเชลโต้ตอบโดยไม่ได้เรียกใช้จากไฟล์โดยตรง (คือใช้อินเทอร์พรีเตอร์ แทนที่จะใช้อีดิเตอร์เขียนแล้วสั่งรันไฟล์) กรณีแบบนี้หากมีการ
import
มอดูลอะไรไปครั้งหนึ่งแล้ว มอดูลนั้นจะไม่สามารถ
import
ซ้ำได้อีก
นั่นหมายความว่าต่อให้มีการแก้ไขไฟล์มอดูลนั้นไปแล้วกด
import
ซ้ำ ความเปลี่ยนแปลงนั้นก็จะไม่แสดงผลให้เห็น
หากต้องการให้มีการ
reload
จะต้องใช้ฟังก์ชัน
reload
ซึ่งอยู่ในมอดูล
importlib
import importlib
importlib.reload(momo)
มอดูล
importlib
นั้นเป็นมอดูลที่เก็บฟังก์ชันต่างๆที่เกี่ยวกับการเรียกใช้มอดูล ยังมีฟังก์ชันอีกหลายอันที่ใช้ประโยชน์ได้
การ
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(\'\'\'"\'(ö)\'"\'\'\')') # ได้ "'(ö)'"
สรุปเนื้อหา ในบทนี้ได้พูดถึงการใช้ไฟล์มาสร้างเป็นมอดูลแล้ว ซึ่งสามารถทำได้ไม่ยาก ตรงไปตรงมา
แต่มอดูลโดยมากมักไม่ได้สร้างจากไฟล์เดียวแต่ประกอบขึ้นจากไฟล์เป็นจำนวนมาก ซึ่งเรียกว่าการรวมเป็นแพ็กเกจ เนื้อหาตรงนี้จะพูดถึงต่อในบทหน้า
อ้างอิง