Mô-đun (Modules)

Khi bạn thoát khỏi trình thông dịch Python và mở lại, tất cả các định nghĩa (hàm, biến) mà bạn đã tạo sẽ bị mất. Vì vậy, nếu muốn viết một chương trình dài hơn, bạn nên sử dụng trình soạn thảo văn bản để tạo tệp mã nguồn và chạy nó như một tập lệnh (script). Khi chương trình trở nên lớn hơn, bạn có thể muốn chia nó thành nhiều tệp để dễ dàng bảo trì hơn. Bạn cũng có thể muốn sử dụng lại một hàm hữu ích trong nhiều chương trình mà không cần sao chép lại định nghĩa của nó.

Để hỗ trợ điều này, Python cho phép đặt các định nghĩa vào một tệp riêng biệt và sử dụng chúng trong các tập lệnh hoặc trong chế độ tương tác của trình thông dịch. Tệp này được gọi là mô-đun (module). Các định nghĩa từ mô-đun có thể được import vào các mô-đun khác hoặc vào mô-đun chính (__main__), nơi chứa các biến của tập lệnh đang chạy.

Một mô-đun là một tệp chứa các định nghĩa và câu lệnh Python. Tên tệp là tên mô-đun kèm theo phần mở rộng .py. Bên trong mô-đun, tên của mô-đun được lưu trong biến toàn cục __name__.

Ví dụ, hãy tạo một tệp fibo.py với nội dung sau:

# Mô-đun số Fibonacci

def fib(n):    # In dãy Fibonacci nhỏ hơn n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

def fib2(n):   # Trả về danh sách các số Fibonacci nhỏ hơn n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a + b
    return result

Bây giờ, mở trình thông dịch Python và import mô-đun này bằng lệnh:

>>> import fibo

Điều này không thêm trực tiếp các tên hàm vào không gian tên hiện tại mà chỉ thêm tên mô-đun fibo. Bạn có thể truy cập các hàm bằng cách gọi:

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

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

>>> fibo.__name__
'fibo'

Nếu bạn sử dụng một hàm thường xuyên, bạn có thể gán nó vào một tên cục bộ:

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

6.1. Tìm hiểu thêm về mô-đun

Một mô-đun có thể chứa cả các câu lệnh thực thi và các định nghĩa hàm. Các câu lệnh này được dùng để khởi tạo mô-đun và chỉ được thực thi một lần duy nhất khi mô-đun được import lần đầu tiên.

Mỗi mô-đun có không gian tên riêng, được sử dụng làm không gian tên toàn cục cho tất cả các hàm trong mô-đun. Điều này giúp tránh xung đột biến toàn cục giữa các mô-đun.

Mô-đun có thể import các mô-đun khác, và thông thường các lệnh import được đặt ở đầu mô-đun.

Có thể import trực tiếp các thành phần từ mô-đun:

>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Hoặc import tất cả các tên từ mô-đun (ngoại trừ những tên bắt đầu bằng _):

>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Tuy nhiên, cách import toàn bộ (from module import *) thường không được khuyến khích vì có thể làm mã khó đọc hơn.

Có thể đổi tên mô-đun khi import:

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

Tương tự, đổi tên một hàm khi import:

>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Lưu ý: Mỗi mô-đun chỉ được import một lần duy nhất trong mỗi phiên làm việc của trình thông dịch. Nếu cần nạp lại một mô-đun sau khi đã sửa đổi, sử dụng importlib.reload():

>>> import importlib
>>> importlib.reload(fibo)

6.1.1. Chạy mô-đun như một tập lệnh

Khi chạy một mô-đun Python bằng dòng lệnh:

python fibo.py <tham_số>

Mã trong mô-đun sẽ được thực thi, giống như khi import, nhưng biến __name__ sẽ được đặt thành "__main__". Vì vậy, có thể thêm đoạn mã sau vào cuối mô-đun để cho phép mô-đun vừa có thể import vừa có thể chạy độc lập:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

Ví dụ:

$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34

Nếu mô-đun được import, đoạn mã này sẽ không được thực thi:

>>> import fibo
>>> # Không có gì xảy ra

Điều này hữu ích khi muốn cung cấp một giao diện dòng lệnh cho mô-đun hoặc để kiểm tra nó bằng cách chạy như một tập lệnh độc lập.

6.1. Tìm hiểu thêm về Mô-đun (More on Modules)

Một mô-đun có thể chứa cả các câu lệnh thực thi lẫn các định nghĩa hàm. Những câu lệnh này được thiết kế để khởi tạo mô-đun và chỉ được thực thi một lần duy nhất khi tên mô-đun xuất hiện lần đầu tiên trong một câu lệnh import. (Chúng cũng được chạy nếu tệp được thực thi như một tập lệnh.)

Mỗi mô-đun có không gian tên riêng của nó, được sử dụng như không gian tên toàn cục cho tất cả các hàm được định nghĩa bên trong mô-đun. Do đó, tác giả của một mô-đun có thể sử dụng các biến toàn cục trong mô-đun mà không cần lo lắng về việc vô tình xung đột với các biến toàn cục của người dùng. Mặt khác, nếu bạn biết chính xác mình đang làm gì, bạn có thể truy cập các biến toàn cục của mô-đun bằng cùng một cách mà bạn gọi các hàm của nó, thông qua cú pháp modname.itemname.

Mô-đun có thể import các mô-đun khác. Thông thường, các lệnh import được đặt ở đầu của mô-đun (hoặc tập lệnh). Tên của các mô-đun được nhập, nếu được đặt ở cấp cao nhất trong mô-đun (bên ngoài bất kỳ hàm hoặc lớp nào), sẽ được thêm vào không gian tên toàn cục của mô-đun đó.

Có một biến thể của câu lệnh import cho phép nhập trực tiếp các tên từ một mô-đun vào không gian tên của mô-đun nhập:

>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Cách này không đưa tên mô-đun gốc (fibo) vào không gian tên cục bộ, do đó trong ví dụ trên, fibo không được định nghĩa.

Một biến thể khác là nhập tất cả các tên mà một mô-đun định nghĩa:

>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Lệnh này nhập tất cả các tên ngoại trừ những tên bắt đầu bằng dấu gạch dưới (_). Trong hầu hết các trường hợp, lập trình viên Python không sử dụng cách này vì nó có thể đưa vào trình thông dịch một tập hợp tên không xác định, có thể che khuất các định nghĩa đã có.

Lưu ý rằng thông thường, việc sử dụng from module import * không được khuyến khích, vì nó thường dẫn đến mã nguồn khó đọc. Tuy nhiên, trong các phiên làm việc tương tác, điều này có thể hữu ích để giảm bớt việc nhập lệnh.

Nếu tên mô-đun đi kèm với từ khóa as, thì tên theo sau as sẽ được liên kết trực tiếp với mô-đun được nhập:

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

Cách này về bản chất vẫn tương đương với import fibo, nhưng tên mô-đun sẽ có thể truy cập dưới tên mới fib.

Tương tự, có thể sử dụng as với from:

>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Lưu ý

Vì lý do hiệu suất, mỗi mô-đun chỉ được nhập một lần duy nhất trong mỗi phiên làm việc của trình thông dịch. Do đó, nếu bạn thay đổi mã trong mô-đun và muốn thử nghiệm lại, bạn cần phải khởi động lại trình thông dịch – hoặc nếu chỉ muốn tải lại một mô-đun, bạn có thể sử dụng importlib.reload():

>>> import importlib
>>> importlib.reload(fibo)

6.1.1. Chạy mô-đun như một tập lệnh (Executing Modules as Scripts)

Khi chạy một mô-đun Python bằng dòng lệnh:

python fibo.py <các_đối_số>

Mã trong mô-đun sẽ được thực thi giống như khi nó được nhập, nhưng biến __name__ sẽ được đặt thành "__main__". Điều này có nghĩa là nếu bạn thêm đoạn mã sau vào cuối mô-đun:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

thì tệp có thể được sử dụng vừa như một tập lệnh chạy độc lập, vừa như một mô-đun có thể nhập. Mã xử lý dòng lệnh chỉ chạy khi tệp được thực thi trực tiếp:

$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34

Nếu mô-đun được nhập vào một chương trình khác, mã trên sẽ không chạy:

>>> import fibo
>>> # Không có gì được in ra

Cách làm này thường được sử dụng để cung cấp một giao diện dòng lệnh tiện lợi cho mô-đun hoặc để kiểm thử (chạy mô-đun như một tập lệnh để thực thi một bộ kiểm thử).

6.1.2. Đường Dẫn Tìm Kiếm Mô-đun (The Module Search Path)

Khi một mô-đun có tên spam được import, trình thông dịch trước tiên sẽ tìm kiếm một mô-đun tích hợp có tên đó. Các tên mô-đun tích hợp này được liệt kê trong sys.builtin_module_names. Nếu không tìm thấy, nó sẽ tìm kiếm một tệp có tên spam.py trong danh sách thư mục được xác định bởi biến sys.path. sys.path được khởi tạo từ các nguồn sau:

  • Thư mục chứa tập lệnh đầu vào (hoặc thư mục hiện tại nếu không có tệp nào được chỉ định).
  • PYTHONPATH (một danh sách các tên thư mục, có cú pháp giống biến PATH trong hệ điều hành).
  • Giá trị mặc định phụ thuộc vào hệ thống (thường bao gồm thư mục site-packages, được quản lý bởi mô-đun site).

Chi tiết hơn về cách khởi tạo đường dẫn tìm kiếm mô-đun có thể được tìm thấy trong Khởi tạo đường dẫn tìm kiếm sys.path (The initialization of the sys.path module search path).

Lưu ý:

Trên các hệ thống tệp hỗ trợ liên kết tượng trưng (symlink), thư mục chứa tập lệnh đầu vào được xác định sau khi theo liên kết tượng trưng. Nói cách khác, thư mục chứa liên kết tượng trưng không được thêm vào đường dẫn tìm kiếm mô-đun.

Sau khi khởi tạo, chương trình Python có thể sửa đổi sys.path. Thư mục chứa tập lệnh đang chạy được đặt ở đầu tiên trong đường dẫn tìm kiếm, trước đường dẫn thư viện tiêu chuẩn. Điều này có nghĩa là các tập lệnh trong thư mục đó sẽ được tải thay vì các mô-đun cùng tên trong thư viện tiêu chuẩn. Điều này có thể gây lỗi nếu việc thay thế không được chủ ý. Xem phần Mô-đun tiêu chuẩn (Standard Modules) để biết thêm thông tin.


6.1.3. Tệp Python Đã Biên Dịch (“Compiled” Python Files)

Để tăng tốc độ tải mô-đun, Python lưu trữ phiên bản đã biên dịch của từng mô-đun trong thư mục __pycache__ dưới dạng module.version.pyc, trong đó version mã hóa định dạng của tệp biên dịch, thường bao gồm số phiên bản Python. Ví dụ, trong phiên bản CPython 3.3, phiên bản biên dịch của spam.py sẽ được lưu trong __pycache__/spam.cpython-33.pyc. Quy ước đặt tên này cho phép các mô-đun đã biên dịch từ các phiên bản Python khác nhau cùng tồn tại.

Python kiểm tra ngày sửa đổi của mã nguồn so với phiên bản biên dịch để xác định xem nó có lỗi thời và cần được biên dịch lại hay không. Đây là một quá trình hoàn toàn tự động. Ngoài ra, các mô-đun đã biên dịch là nền tảng độc lập, vì vậy cùng một thư viện có thể được chia sẻ giữa các hệ thống có kiến trúc khác nhau.

Python không kiểm tra bộ nhớ cache trong hai trường hợp:

  • Khi mô-đun được tải trực tiếp từ dòng lệnh, nó luôn được biên dịch lại và không lưu trữ kết quả.
  • Khi không có tệp mã nguồn nào, Python không kiểm tra bộ nhớ cache. Để hỗ trợ phân phối mô-đun chỉ có phiên bản biên dịch (không có mã nguồn), tệp biên dịch phải nằm trong thư mục mã nguồn và không có tệp nguồn tương ứng.

Một số mẹo dành cho lập trình viên chuyên sâu:

  • Có thể sử dụng tùy chọn -O hoặc -OO khi chạy Python để giảm kích thước của mô-đun đã biên dịch. Tùy chọn -O loại bỏ các câu lệnh assert, trong khi -OO loại bỏ cả assert và chuỗi tài liệu (__doc__). Vì một số chương trình có thể dựa vào chúng, nên chỉ sử dụng nếu bạn hiểu rõ ảnh hưởng của nó. Các mô-đun được “tối ưu hóa” có thẻ opt- và thường có kích thước nhỏ hơn. Các phiên bản Python tương lai có thể thay đổi cách tối ưu hóa hoạt động.
  • Một chương trình không chạy nhanh hơn khi được đọc từ tệp .pyc so với tệp .py. Điểm khác biệt duy nhất là tốc độ tải mô-đun từ .pyc nhanh hơn.
  • Mô-đun compileall có thể tạo tệp .pyc cho tất cả các mô-đun trong một thư mục.
  • Chi tiết hơn về quá trình này, bao gồm sơ đồ quyết định, có thể được tìm thấy trong PEP 3147.

6.2. Mô-đun Tiêu Chuẩn (Standard Modules)

Python đi kèm với một thư viện mô-đun tiêu chuẩn, được mô tả trong một tài liệu riêng, Python Library Reference (Tham chiếu Thư viện). Một số mô-đun được tích hợp vào trình thông dịch để cung cấp quyền truy cập vào các thao tác không thuộc lõi của ngôn ngữ nhưng vẫn quan trọng, chẳng hạn như gọi hệ thống (system calls). Bộ mô-đun tích hợp này phụ thuộc vào nền tảng. Ví dụ, mô-đun winreg chỉ có trên hệ điều hành Windows.

Một mô-đun đặc biệt cần chú ý là sys, mô-đun này được tích hợp vào mọi trình thông dịch Python. Các biến sys.ps1sys.ps2 xác định các chuỗi được sử dụng làm dấu nhắc chính và phụ trong chế độ tương tác:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Chạy thử!')
Chạy thử!
C>

Hai biến này chỉ được định nghĩa khi trình thông dịch ở chế độ tương tác.

Biến sys.path là một danh sách chuỗi xác định đường dẫn tìm kiếm mô-đun của trình thông dịch. Nó được khởi tạo với giá trị mặc định lấy từ biến môi trường PYTHONPATH hoặc từ một giá trị mặc định nếu PYTHONPATH không được thiết lập. Bạn có thể sửa đổi nó bằng các thao tác danh sách thông thường:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3. Hàm dir() trong Python

Hàm tích hợp dir() được sử dụng để tìm ra những tên nào được định nghĩa trong một mô-đun. Nó trả về một danh sách các chuỗi được sắp xếp:

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

>>> dir(sys)  
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

Không có đối số, dir() liệt kê các tên mà bạn đã định nghĩa trong phiên làm việc hiện tại:

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

Hàm dir() liệt kê tất cả các loại tên: biến, mô-đun, hàm, v.v.

Tuy nhiên, dir() không liệt kê các tên của các hàm và biến tích hợp sẵn. Nếu bạn muốn xem danh sách các tên tích hợp sẵn, chúng được định nghĩa trong mô-đun tiêu chuẩn builtins:

>>> import builtins
>>> dir(builtins)  
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
 'zip']

6.4. Gói (Packages)

Gói (packages) là một cách để tổ chức không gian tên (namespace) của mô-đun Python bằng cách sử dụng “tên mô-đun có dấu chấm” (dotted module names). Ví dụ, tên mô-đun A.B chỉ định một mô-đun con (submodule) có tên B nằm trong gói (package) có tên A. Cũng giống như việc sử dụng mô-đun giúp các tác giả tránh xung đột tên biến toàn cục, việc sử dụng tên mô-đun có dấu chấm giúp các tác giả của các gói lớn như NumPy hoặc Pillow tránh xung đột tên mô-đun.

Giả sử bạn muốn thiết kế một tập hợp các mô-đun (một gói) để xử lý đồng nhất các tệp âm thanh và dữ liệu âm thanh. Có rất nhiều định dạng tệp âm thanh khác nhau (thường được nhận dạng bằng phần mở rộng, ví dụ: .wav, .aiff, .au), do đó, bạn có thể cần tạo và duy trì một tập hợp ngày càng tăng các mô-đun để chuyển đổi giữa các định dạng tệp khác nhau. Ngoài ra, bạn có thể muốn thực hiện nhiều thao tác trên dữ liệu âm thanh (như trộn, thêm tiếng vọng, áp dụng bộ cân bằng, tạo hiệu ứng âm thanh giả lập stereo), do đó, bạn sẽ cần viết một loạt mô-đun để thực hiện các tác vụ này.

Dưới đây là một cấu trúc gói có thể có (được biểu diễn dưới dạng hệ thống tệp phân cấp):

sound/                          # Gói cấp cao nhất
    __init__.py                 # Khởi tạo gói âm thanh
    formats/                    # Gói con cho chuyển đổi định dạng tệp
        __init__.py
        wavread.py
        wavwrite.py
        aiffread.py
        aiffwrite.py
        auread.py
        auwrite.py
        ...
    effects/                    # Gói con cho hiệu ứng âm thanh
        __init__.py
        echo.py
        surround.py
        reverse.py
        ...
    filters/                    # Gói con cho bộ lọc
        __init__.py
        equalizer.py
        vocoder.py
        karaoke.py
        ...

Khi nhập gói (importing a package), Python tìm kiếm qua các thư mục trong sys.path để tìm thư mục con của gói.

Các tệp __init__.py bắt buộc để Python nhận diện thư mục chứa tệp này là một gói (trừ khi sử dụng gói không gian tên (namespace package), một tính năng nâng cao hơn). Điều này ngăn các thư mục có tên phổ biến như string vô tình che khuất các mô-đun hợp lệ xuất hiện sau đó trong đường dẫn tìm kiếm mô-đun. Trong trường hợp đơn giản nhất, __init__.py có thể là một tệp trống, nhưng nó cũng có thể thực thi mã khởi tạo cho gói hoặc thiết lập biến __all__ (sẽ được đề cập sau).

Cách nhập mô-đun trong gói

Người dùng có thể nhập mô-đun riêng lẻ từ gói, ví dụ:

import sound.effects.echo

Lệnh này tải mô-đun con sound.effects.echo. Để sử dụng, bạn phải tham chiếu nó bằng tên đầy đủ:

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

Một cách khác để nhập mô-đun con là:

from sound.effects import echo

Cách này cũng tải mô-đun con echo, nhưng giúp bạn sử dụng nó mà không cần tiền tố của gói:

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

Một biến thể khác là nhập trực tiếp hàm hoặc biến mong muốn từ mô-đun con:

from sound.effects.echo import echofilter

Cách này cũng tải mô-đun con echo, nhưng làm cho hàm echofilter() có thể truy cập trực tiếp:

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

Lưu ý rằng khi sử dụng from package import item, item có thể là một mô-đun con (subpackage hoặc submodule) hoặc một tên khác được định nghĩa trong gói, như một hàm, lớp hoặc biến. Câu lệnh import sẽ kiểm tra xem item có được định nghĩa trong gói hay không. Nếu không tìm thấy, nó giả định item là một mô-đun và cố gắng tải nó. Nếu thất bại, nó sẽ báo lỗi ImportError.

Ngược lại, khi sử dụng cú pháp import item.subitem.subsubitem, tất cả các item ngoại trừ phần tử cuối cùng phải là gói; phần tử cuối cùng có thể là một mô-đun hoặc một gói con, nhưng không thể là một lớp, hàm hoặc biến được định nghĩa trong phần tử trước đó.

6.4.1. Import * từ một Gói (*Importing * From a Package*)

Điều gì xảy ra khi người dùng viết:

from sound.effects import *

Lý tưởng nhất, chúng ta mong muốn lệnh này sẽ duyệt qua hệ thống tệp, tìm các mô-đun con có trong gói và nhập tất cả chúng. Tuy nhiên, quá trình này có thể mất nhiều thời gian và việc nhập tất cả mô-đun con có thể gây ra các tác dụng phụ không mong muốn nếu một số mô-đun chỉ nên được nhập khi cần thiết.

Cách duy nhất để kiểm soát điều này là tác giả gói phải cung cấp một danh sách rõ ràng về các mô-đun nên được nhập khi sử dụng from package import *. Điều này được thực hiện bằng cách định nghĩa danh sách __all__ trong tệp __init__.py của gói.

Ví dụ, tệp sound/effects/__init__.py có thể chứa:

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

Điều này có nghĩa là câu lệnh from sound.effects import * sẽ chỉ nhập ba mô-đun echo, surround, và reverse từ gói sound.effects.

Lưu ý:

  • Nếu có một tên cục bộ trong __init__.py trùng với tên mô-đun con, mô-đun con đó sẽ bị che khuất. Ví dụ:
__all__ = [
    "echo",      # ánh xạ tới tệp 'echo.py'
    "surround",  # ánh xạ tới tệp 'surround.py'
    "reverse",   # !!! nhưng bị che bởi hàm 'reverse' bên dưới !!!
]

def reverse(msg: str):  # Hàm này che khuất mô-đun 'reverse.py'
    return msg[::-1]
  • Nếu __all__ không được định nghĩa, câu lệnh from sound.effects import * không nhập tất cả các mô-đun con trong gói sound.effects. Thay vào đó, nó chỉ nhập các tên được định nghĩa trong __init__.py và những mô-đun con nào đã được nhập trước đó bằng các lệnh import khác.

Ví dụ:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

Trong ví dụ này, chỉ các mô-đun echosurround được nhập vào không gian tên hiện tại vì chúng đã được nhập trước khi câu lệnh from...import * được thực thi.

Dù một số mô-đun hỗ trợ xuất danh sách __all__, việc sử dụng import * không được khuyến khích trong mã nguồn sản xuất vì nó làm giảm tính rõ ràng và dễ đọc của mã.

Hãy nhớ rằng không có gì sai khi sử dụng:

from package import specific_submodule

Cách này được khuyến nghị hơn trừ khi bạn cần nhập nhiều mô-đun con có cùng tên từ các gói khác nhau.


6.4.2. Tham chiếu Nội bộ trong Gói (Intra-package References)

Khi một gói được chia thành nhiều gói con, bạn có thể sử dụng import tuyệt đối (absolute import) để tham chiếu đến các mô-đun con khác trong gói. Ví dụ, nếu mô-đun sound.filters.vocoder cần sử dụng mô-đun echo trong sound.effects, ta có thể viết:

from sound.effects import echo

Bạn cũng có thể sử dụng import tương đối (relative import) bằng cách sử dụng dấu chấm . để biểu thị gói hiện tại và .. để biểu thị gói cha. Ví dụ, từ mô-đun surround, bạn có thể viết:

from . import echo         # Nhập mô-đun 'echo' từ cùng gói
from .. import formats     # Nhập gói 'formats' từ cấp cha
from ..filters import equalizer  # Nhập mô-đun 'equalizer' từ gói 'filters'

Lưu ý:

  • Import tương đối dựa trên tên của mô-đun hiện tại. Vì mô-đun chính của chương trình luôn có tên "__main__", các mô-đun chính trong ứng dụng Python phải luôn sử dụng import tuyệt đối.

6.4.3. Gói Trong Nhiều Thư Mục (Packages in Multiple Directories)

Gói Python hỗ trợ một thuộc tính đặc biệt có tên __path__. Thuộc tính này được khởi tạo dưới dạng một chuỗi các đường dẫn thư mục, chứa tên thư mục chứa tệp __init__.py của gói trước khi mã trong tệp đó được thực thi.

Giá trị của __path__ có thể được sửa đổi; việc thay đổi giá trị này có ảnh hưởng đến cách Python tìm kiếm mô-đun và gói con bên trong gói hiện tại.

Mặc dù tính năng này không thường xuyên được sử dụng, nhưng nó có thể hữu ích khi bạn cần mở rộng tập hợp các mô-đun được tìm thấy trong một gói.


Ghi chú

[1] Thực tế, ngay cả các định nghĩa hàm cũng là một dạng “câu lệnh” (statement) được “thực thi”. Khi một hàm được định nghĩa ở cấp mô-đun, việc thực thi lệnh định nghĩa này sẽ thêm tên hàm vào không gian tên toàn cục của mô-đun.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top