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)

  • สามารถตั้งให้มีโค๊ดในการรันครั้งแรก (ครั้งเดียว) ที่ถูกอิมพอร์ตได้
  • เนื่องจากต้องอ้างอิงค่าในโมดูลผ่านชื่อโมดูล ในรูปของ modname.itername จึงไม่ต้องกังวลเรื่องชื่อตัวแปรในโมดูลจะซ้ำกับตัวแปรในโปรแกรมหลัก
  • โมดูลสามารถอิมพอร์ตโมดูลอื่นได้
  • เขียนรูปแบบการอิมพอร์ตได้หลากหลาย
    • เลือกอิมพอร์ตบางฟังก์ชั่น
      >>> from fibo import fib, fib2
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
    • อิมพอร์ตทุกฟังก์ชั่น ที่ไม่ใช่ฟังก์ชั่นท้องถิ่น (ฟังก์ชั่นท้องถิ่นจะถูกนำหน้าชื่อฟังก์ชั่นด้วยสัญลักษณ์ขีดเส้นใต้ '_' ) แบบละเลยชื่อโมดูล
      >>> from fibo import *
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1.1 การค้นหาพาธของโมดูล (The Module Search Path)
  • จะหาที่ไดเรคทอรี่ปัจจุบันก่อน
  • ถ้าไม่พบจะไปหาจากค่าในตัวแปรแวดล้อม PYTHONPATH
  • ตามด้วยพาธของไพธอนเอง สำหรับเดเบียนคือ /usr/lib/python2.4 (ตัวเลข 2.4 จะเปลี่ยนไปตามรุ่นไพธอนที่ใช้)

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

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

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

สำหรับเซียน

  • ถ้าเรียกใช้ไพธอนด้วยพารามิเตอร์ -O ไพธอนจะคอมไพล์ไฟล์ให้เล็ก โดยนามสกุลจะกลายเป็น .pyo แทน
  • ถ้าเรียกใช้ไพธอนด้วยพารามิเตอร์ -OO มีผลเหมือนอันแรกแต่จะลบข้อมูลที่เป็น docstring ออก
  • โปรแกรมไม่ได้รันเร็วขึ้น เพียงแต่ถูกโหลดได้เร็วขึ้น
  • ในการรันปกติ ไพธอนไม่ได้คอมไพล์ไฟล์ที่ถูกรัน แต่จะคอมไพล์เมื่อถูกอิมพอร์ต ดังนั้นถ้าจะเร่งความเร็วตอนถูกโหลด เราอาจแบ่งโปรแกรมหลักของเราให้เล็กลง แล้วไปอิมพอร์ตโมดูลที่เราแบ่งเอาไว้อีกทีนึง
  • ตอนรัน ถ้ามีไฟล์ .pyc หรือ .pyo อยุ่แล้ว ไม่จำเป็นต้องมีไฟล์ต้นฉบับ (นิยมนามสกุลเป็น .py) ดังนั้นหากไม่ต้องการแพร่ซอร์สโค๊ด อาจจ่ายเป็นไฟล์คอมไพล์เหล่านี้แทน
  • ใช้โมดูล compileall ในการคอมไพล์ไฟล์ทั้งไดเรคทอรี่


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

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

  • ตัวแปร sys.ps1 จะเก็บข้อความที่เป็นพร้อมต์หลัก (Primary prompt) และ sys.ps2 จะเก็บพร้อมต์ตาม (Secondary prompt) ตัวแปรทั้งสองจะเรียกใช้ได้ในหมวดโต้ตอบเท่านั้น
    >>> import sys
    >>> sys.ps1
    '>>> '
    
    >>> sys.ps2
    '... '
    
    >>> sys.ps1 = 'C> '
    C> print 'Yuck!'
    Yuck!
    C>
  • ตัวแปร sys.path เก็บพาธการค้นหาของระบบในรูปของลิสต์ ดังนั้นเราอาจเพิ่มพาธการค้น ได้โดยการเพิ่มหรือเปลียนค่า
    >>> import sys
    >>> sys.path.append('/ufs/guido/lib/python')


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__ ที่จะใช้บอกว่าแพกเกจนี้จะต้องโหลดโมดูลไหนบ้าง

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

  • แบบแรกคือ import ... [ as ... ]
    เรียกได้สองลักษณะ
    • ถ้าไม่มี as
      import Sound.Effects.echo

      เวลาอ้างถึงต้องอ้างแบบเต็ม ๆ

      Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
    • ถ้ามี as
      import Sound.Effects.echo as SEe

      อ้างถึงโดยใช้ชื่อย่อ

      SEe.echofilter(input, output, delay=0.7, atten=4)
  • แบบที่สองคือ from ... import ...
    from Sound.Effects import echo

    เวลาเรียกใช้ เรียกเฉพาะชื่อ โมดูล.ฟังก์ชั่น ที่เราอิมพอร์ตเข้ามา

    echo.echofilter(input, output, delay=0.7, atten=4)

    หรือถ้าอิมพอร์ตเจาะเฉพาะฟังก์ชั่น ก็เรียกเฉพาะฟังก์ชั่น

    from Sound.Effects.echo import echofilter
    echofilter(input, output, delay=0.7, atten=4)

หมายเหตุ

  • ระหว่างการอิมพอร์ต ถ้าเกิดข้อผิดพลาดขึ้นมา ไพธอนจะแจ้ง ImportError
  • อิมพอร์ตได้เฉพาะสิ่งที่มีตัวตนและสิ่งที่ยอมให้อิมพอร์ตได้เท่านั้น คือ แพกเกจหรือโมดูล ที่เหลือนอกจากนี้คือคลาส ฟังก์ชั่น หรือตัวแปร ไม่สามารถอิมพอร์ตได้
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

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

  • ถ้าอยู่ในระดับชั้นไดเรคทอรี่เดียวกัน เรียกได้โดยตรง โดยไม่ต้องมีชื่อแพกเกจนำหน้า
    เช่นจากตัวอย่าง โมดูล surround สามารถเรียกใช้โมดูล echo ได้โดยตรง
    import echo
    echo.echofilter(input, output, delay=0.7, atten=4)

    หรือ

    from echo import echofilter
    echofilter(input, output, delay=0.7, atten=4)
  • ถ้าอยู่ลึกลงไป สามารถเรียกผ่านจากระดับเดิมได้ทันที
  • นอกเหนือจากนี้ ต้องอ้างอิงแบบเต็มยศ เหมือนการเรียกจากโมดูลจากแพกเกจอื่น
  • *** เว้นแต่ไพธอนรุ่น 2.5 ขึ้นไป จะสามารถเรียกย้อนขึ้นได้ด้วย ตัวอย่างการเรียกคือ
    from . import echo
    from .. import Formats
    from ..Filters import equalizer
6.4.3 หนึ่งแพกเกจหลายไดเรคทอรี่ (Packages in Multiple Directories)

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