6. มอดูล (Modules)

6.1 เพิ่มเติม (More on Modules)
6.2 มอดูลมาตรฐาน (Standard Modules)
6.3 ฟังก์ชัน dir() (The dir() Function)
6.4 แพกเกจ (Packages)

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

ใช้ประโยค import module_name ในการเรียกใช้

ชื่อมอดูลจะถูกเก็บไว้ในตัวแปรส่วนรวม (เฉพาะในมอดูล) ชื่อ __name__

สมมุติเราสร้างมอดูลเป็นไฟล์ชื่อ fibo.py (ให้อยู่ในไดเรกทอรีปัจจุบัน) มีเนื้อไฟล์ว่า

# Fibonacci numbers module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print b,
        a, b = b, a+b

def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

เริ่มต้นใช้งานว่า

>>> import fibo

เรียกใช้งานฟังก์ชันโดย

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

>>> fibo.__name__
'fibo'

เพื่อให้กระชับ เราสามารถกำหนดค่าตัวแปรแทนเมธอดในมอดูลได้

>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377


6.1 เพิ่มเติม (More on Modules)

6.1.1 การค้นหาพาธของมอดูล (The Module Search Path)

การค้นพาธที่ว่า สามารถดูได้จากตัวแปร sys.path ในไพธอน ซึ่งในทางปฏิบัติเราสามารถแก้ไขได้จากในโปรแกรม ทำให้การเขียนโปรแกรมมีความยืดหยุ่น

6.1.2 ไฟล์ที่ถูกแปลแล้ว (Compiled Python files)

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

สำหรับเซียน


6.2 มอดูลมาตรฐาน (Standard Modules)

ไพธอนมีมอดูลมาตรฐานเยอะมาก ดูได้จาก บรรณสารของไพธอน (Python Library Reference) มอดูลหลายตัวเป็นมอดูลของระบบ บางตัวอาจเรียกใช้ได้ในบางสถานะ

ตัวอย่างเช่น มอดูล sys ซึ่งเป็นมอดูลระบบ


6.3 ฟังก์ชัน dir() (The dir() Function)

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

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']

>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
 '__stdin__', '__stdout__', '_getframe', 'api_version', 'argv', 
 'builtin_module_names', 'byteorder', 'callstats', 'copyright',
 'displayhook', 'exc_clear', 'exc_info', 'exc_type', 'excepthook',
 'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
 'getrecursionlimit', 'getrefcount', 'hexversion', 'maxint', 'maxunicode',
 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache',
 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
 'version', 'version_info', 'warnoptions']

ใช้ dir() เฉย ๆ จะดูรายชื่อในสภาพแวดล้อมของปัจจุบัน

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__doc__', '__file__', '__name__', 'a', 'fib', 'fibo', 'sys']

เพื่อไม่ให้รกรุงรัง dir() จึงไม่ยอมดูฟังก์ชันบิลต์อิน (build-in function) ของไพธอนให้ แต่ถ้าเราอยากดู ต้องเรียกผ่านมอดูล __builtin__

>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FloatingPointError', 'FutureWarning', 'IOError', 'ImportError',
 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError', 
 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True',
 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
 'UserWarning', 'ValueError', 'Warning', 'WindowsError',
 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__',
 '__name__', 'abs', 'apply', 'basestring', 'bool', 'buffer',
 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile',
 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod',
 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex',
 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter',
 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min',
 'object', 'oct', 'open', 'ord', 'pow', 'property', 'quit', 'range',
 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set',
 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']


6.4 แพกเกจ (Packages)

เวลามีมอดูลที่เราสร้างขึ้นเยอะ เราจะจัดกลุ่มให้ไปรวมในไดเรกทอรีต่างหาก (ทำเหมือนเวลาเรามีไฟล์เยอะ ๆ แล้วเราจะจัดระเบียบไฟล์ ในระบบไฟล์เรียงไดเรกทอรีด้วย / เช่น dira/dirb แต่ไพธอนเรียงด้วย . เช่น pa.pb) ไพธอนเรียกวิธีจัดการกลุ่มมอดูลนี้ว่า แพกเกจ ซึ่งชื่อแพกเกจก็คือชื่อไดเรกทอรีนั่นเอง

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

Sound/                          Top-level package
      __init__.py               Initialize the sound package
      Formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      Effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      Filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

เมื่อแพกเกจเราถูกอิมพอร์ต ไพธอนก็จะค้นพาธจาก sys.path เมื่อพบแล้วก็จัดการคอมไพล์เพื่อจะถูกเรียกใช้ต่อไป

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

เวลาเรียกใช้ เราอาจเลือกเรียกเฉพาะมอดูลที่ต้องการก็ได้ ไม่จำเป็นต้องเรียกทั้งหมด

เรียกใช้ได้สองแบบ

หมายเหตุ

6.4.1 เข้าใจการอิมพอร์ต (Importing * From a Package)

เวลาถูกเรียกอิมพอร์ต ไพธอนใช้ตัวแปร __all__ ในไฟล์ __init__.py สำหรับระบุว่าในแพกเกจนี้จะต้องเรียกใช้งานมอดูลไหนบ้าง (แทนการดูจากชื่อไฟล์ในไดเรกทอรีเพราะมีข้อจำกัดมากสำหรับระบบปฏิบัติการที่หลากลาย)

เช่น ในไฟล์ Sounds/Effects/__init__.py อาจมีเนื้อไฟล์เป็น

__all__ = ["echo", "surround", "reverse"]

นั่นคือเมื่อไพธอนพบคำสั่งว่า from Sound.Effects import * เขาจะอิมพอร์ตมอดูลทั้งสามตัวเข้ามา

แต่ถ้าเราไม่ได้กำหนดค่าให้กับ __all__ เวลาไพธอนพบคำสั่ง from Sound.Effects import * เขาจะเพียงแค่รันไฟล์ __init__.py และรับรู้ว่ามีมอดูลอะไรในแพกเกจบ้างเท่านั้น (ไม่ได้คอมไพล์และอิมพอร์ตมอดูลเข้ามาใน namespace เพื่อเตรียมพร้อมจริง ๆ)

หากใช้ในรูปแบบของ from package import module ควรระวังเรื่องชื่อมอดูลหรือฟังก์ชันซ้ำ อาจทำให้เรียกใช้ผิด

6.4.2 การอ้างถึงกันระหว่างมอดูลในแพกเกจ Intra-package References

มีหลักอยู่ว่า

6.4.3 หนึ่งแพกเกจหลายไดเรกทอรี (Packages in Multiple Directories)

ต้องใส่ชื่อไดเรกทอรีที่บรรจุมอดูลย่อยไว้ในตัวแปรพิเศษชื่อ __path__ ซึ่งเป็นลิสต์ เก็บค่าพาธของมอดูลย่อยไว้