Module và package trong Python

    2

    Module và package là những cấp độ quản lý code cao hơn trong Python. Module cho phép lưu trữ hàm (và code khác) trên các file riêng rẽ để sau tái sử dụng trong các file và dự án khác. Package cho phép nhóm các module lại với nhau. Sử dụng module và package giúp bạn dễ dàng quản lý code trong những chương trình lớn cũng như tái sử dụng code về sau.

    Ví dụ sử dụng module trong Python

    Hãy bắt đầu với một ví dụ minh họa. Tạo hai file my_math.pymain.py trong cùng một thư mục và viết code như sau:

    print('--- start of my_math ---')
    PI = 3.14159
    E = 2.71828
    message = 'My math module'
    def sum(start, *numbers):
    	'''Calculate the sum of unlimited number
    	Params:
    		start:int/float, the start sum
    		*numbers:int/float, the numbers to sum up
    	Return: int/float
    	'''
    	for x in numbers:
    		start += x
    	return start
    def sum_range(start, stop, step=1):
    	'''	Calculate the sum of intergers
    	Params:
    		start:int, start range number
    		stop:int, stop range number
    		step:int, the step between value
    	Returns: int
    	'''
    	sum = 0
    	for i in range(start, stop, step):
    		sum += i
    	return sum
    def fact(n):
    	'''Calculate the factorial of n
    	Params:
    		n:int
    	Return: int
    	'''
    	p = 1
    	for i in range(1, n + 1):
    		p *= i
    	return p
    print('--- start of my_math ---')
    import my_math
    print(my_math.message)
    sum = my_math.sum(0, 1, 2, 3, 4)
    sum_range = my_math.sum_range(1, 10)
    fact = my_math.fact(3)
    print('sum = ', sum)
    print('sum_range = ', sum_range)
    print('fact = ', fact)
    print('Pi = ', my_math.PI)
    print('e = ', my_math.E)	

    Bạn cần chạy chương trình từ file main.py. Kết quả thu được như sau:

    File my_math.py là một file script Python hoàn toàn bình thường. Trong file này định nghĩa một số hàm tính toán (sum, sum_range, fact) và một số biến (PI, E, greeting). Điểm khác biệt của file này là chỉ chứa định nghĩa hàm nhưng không sử dụng (gọi) hàm.

    Trong file main.py chúng ta sử dụng các hàm đã xây dựng từ my_math.py.

    Để ý lệnh import my_math ở đầu file. Đây là lệnh yêu cầu Python tải nội dung của file my_math.py khi chạy file script main.py.

    my_mathmain là hai module trong Python.

    Module trong Python

    Trong Python, bất kỳ file script (file có đuôi py) đều được gọi là một module. Khi bạn tạo một file code Python mới, bạn đang tạo một module. Như vậy, ngay từ những bài học đầu tiên bạn đã xây dựng module Python.

    Tên module là tên file (bỏ đi phần mở rộng py). Trong ví dụ ở phần trên, Module my_math viết trong file my_math.py. Module main viết trong file main.py.

    Mỗi module đều có thể được tải và thực thi bởi Python interpreter. Như ở trên, bạn có thể chạy module my_math và main riêng rẽ vì chúng đều là các file code Python hợp lệ.

    Tuy nhiên, nếu bạn chạy module my_math thì sẽ không ra kết quả gì vì trong module này chúng ta chỉ định nghĩa các hàm và biến chứ không hề sử dụng chúng. Đây là điểm khác biệt giữa my_math và main: trong my_math chỉ chứa khai báo (hàm/biến), trong main chứa lời gọi.

    Như vậy, cách thức xây dựng và sử dụng làm cho các module khác biệt nhau. Có những module được xây dựng ra với vai trò cung cấp thư viện hàm cho module khác sử dụng. Có những module sử dụng hàm định nghĩa trong các module khác.

    Để sử dụng hàm/biến/kiểu dữ liệu định nghĩa trong file/module khác, Python sử dụng lệnh import. Có hai kiểu viết lệnh import khác nhau:

    1. import <module 1>, <module 2>, <module 3> ...
    2. from <module> import <tên 1>, <tên 2>, ...

    Sử dụng module với import …

    Trong ví dụ minh họa chúng ta đã sử dụng lối viết này:

    import my_math

    Cách viết này sẽ tải toàn bộ module my_math, gần giống hệt như việc copy nội dung của my_math.py đặt vào vị trí của lệnh import my_math và chạy chương trình. Điều này có nghĩa là tất cả các lệnh trong module đều được thực hiện khi import.

    Bạn có thể thấy điều này khi cặp lệnh print() viết ở đầu và cuối module my_math đều được thực hiện.

    Cách này sẽ chỉ tải module khi gặp lệnh import lần đầu tiên. Khi gặp lệnh import lần thứ hai, Python sẽ không tải module nữa. Bạn có thể thấy rất rõ điều này khi cặp lệnh print() ở đầu và cuối module my_math chỉ được gọi một lần duy nhất trước khi bắt đầu module main mặc dù chúng ta viết lệnh import my_math hai lần.

    Hãy để ý cách chúng ta gọi hàm và sử dụng biến (được khai báo trong module my_math):

    sum = my_math.sum(0, 1, 2, 3, 4)
    sum_range = my_math.sum_range(1, 10)
    fact = my_math.fact(3)
    print('Pi = ', my_math.PI)
    print('e = ', my_math.E)
    print(my_math.message)

    Tức là, thay vì viết trực tiếp tên hàm/biến, bạn phải cung cấp thêm tên module my_math. Cấu trúc chung để sử dụng một hàm/biến trong module khác là tên_module.tên_hàm()tên_module.tên_biến.

    Bạn có thể sử dụng alias trong lệnh import như sau:

    import my_math as mm
    

    Tên mm trong lệnh trên được gọi là alias (tên giả) của my_math. Khi này thay vì sử dụng tên module my_math bạn có thể sử dụng alias mm như sau:

    print(mm.message)
    print(mm.sum(1, 2, 3, 4, 5))
    print(mm.E)

    Nghĩa là bạn có thể sử dụng alias thay cho tên module. Bạn có thể đồng thời sử dụng cả tên module lẫn alias.

    Alias rất tiện lợi khi tên module quá dài hoặc tên module trùng lặp với một hàm/biến có sẵn của Python.

    Sử dụng module với from … import …

    Giờ hãy xóa (hoặc comment) toàn bộ code của main.py và viết lại code mới như sau:

    from my_math import sum_range, fact, message
    print(message)
    print('sum_range = ', sum_range(1, 100, 2))
    print('5! = ', fact(5))

    Ở đây chúng ta gặp cách sử dụng thứ hai của import: from my_math import sum_range, fact, message.

    Khi sử dụng cấu trúc from .. import bạn có thể thấy rằng Python vẫn import toàn bộ code của module my_math (như trường hợp sử dụng import my_math). Điều này thể hiện qua việc hai lệnh print() ở đầu và cuối module my_math đều được thực hiện ở vị trí gọi from .. import.

    Tuy nhiên, giờ đây bạn không thể sử dụng được tất cả các hàm/biến của module my_math như trước được nữa. Giờ bạn chỉ có thể sử dụng hàm sum_range(), fact(), và biến message.

    Điều đặc biệt là bạn có thể sử dụng tên ngắn gọn của các đối tượng này (không cần tên module), giống hệt như khi các đối tượng này được khai báo trực tiếp trong module main:

    print(message)
    print('sum_range = ', sum_range(1, 100, 2))
    print('5! = ', fact(5))

    Bạn cũng có thể chỉ định alias cho từng tên gọi trong lệnh from .. import như sau:

    from my_math import sum_range as sr, fact as f, message as msg
    print(msg)
    print('sum_range = ', sr(1, 100, 2))
    print('5! = ', f(5))

    Ở đây khi import chúng ta chỉ định alias sr cho hàm sum_range(), f cho hàm fact(), msg cho biến message. Trong code sau đó chúng ta có thể sử dụng alias thay cho tên thật.

    Lưu ý, nếu bạn đã đặt alias thì không thể sử dụng tên thật của đối tượng được nữa. Tức là không thể đồng thời sử dụng alias và tên thật.

    Bạn cũng có thể sử dụng cách import như sau:

    from my_math import *

    Tuy nhiên đây là cách thức không được khuyến khích. Nó dễ dàng làm rối không gian tên của module hiện tại nếu có quá nhiều tên gọi được import.

    Về hiệu suất tổng thể thì cả hai cách import gần như là tương đương nhau. Sự khác biệt chỉ nằm ở cách sử dụng các đối tượng từ module.
    from..import tiện lợi hơn nếu bạn chỉ có nhu cầu sử dụng một vài đối tượng từ module. Nó không làm rối không gian tên bằng các hàm không được sử dụng.
    Nếu cần sử dụng rất nhiều hàm từ module, bạn nên sử dụng import (kết hợp với alias). Nó giúp bạn phân biệt rõ hàm của module.

    Thực thi module

    Khi xây dựng một module bạn có thể phải dự phòng hai tình huống:

    1. Module đó được thực thi trực tiếp (chạy module đó ở chế độ kịch bản)
    2. Module được import vào một module khác, tạm gọi là thực thi gián tiếp (khi được import, toàn bộ code của module được thực thi 1 lần).

    Mỗi module có một thuộc tính đặc biệt tên là __name__. Lưu ý có hai dấu _ trước và sau name. Bạn có thể thử in ra qua lệnh print(__name__).

    Khi một module được thực thi trực tiếp, __name__ sẽ được gán giá trị '__main__'. Nếu khi thực thi một module mà __name__ == '__main__‘ thì đây là module chủ của chương trình.

    Khi một module được thực thi gián tiếp, __name__ của nó sẽ có giá trị là tên module (cũng là tên file bỏ đi phần mở rộng py).

    Với đặc thù trên, khi xây dựng module trong Python mà cần chạy ở cả hai kiểu (trực tiếp và gián tiếp) người ta thường sử dụng pattern sau đây:

    1. Mỗi module sẽ có một hàm main() chứa những lệnh cần thực thi theo kiểu trực tiếp;
    2. Kiểm tra nếu __name__ == '__main__' thì thực thi main().
    def main():
    	'Thực thi các logic của chương trình'
    	# code cần chạy
    if __name__ == '__main__':
    	main()

    Khi chạy gián tiếp qua import __name__ sẽ không thể có giá trị __main__, vì vậy logic của main() sẽ không thực thi.

    Khi một module được chạy gián tiếp qua import, Python cũng thực hiện lưu tạm bản dịch bytecode của nó trong thư mục __pycache__. Thư mục __pycache__ nằm trong cùng thư mục với module được import. Ví dụ, với module my_math, khi được import, Python sẽ tạo ra file __pycache__/my_math.cpython-38.pyc (nếu bạn sử dụng Python 3.8). Nếu bạn có nhiều phiên bản Python khác nhau, khi chạy với phiến bản nào thì file pyc sẽ có tên tương ứng.

    Nhắc lại: khi chạy một script, Python sẽ dịch nó thành bytecode trung gian. Chương trình máy ảo của Python sẽ tiếp tục dịch bytecode thành mã máy để thực thi trên từng hệ điều hành.

    File bytecode của Python có phần mở rộng là pyc.

    Khi import một module cũng xảy ra quá trình dịch bytecode như vậy. Tuy nhiên, Python sẽ lưu lại lại file pyc để lần sau không cần dịch lại. Việc lưu trữ file pyc giúp giảm thời gian tải một chương trình.

    Lưu ý, khi trực tiếp thực thi một module Python sẽ không lưu lại file bytecode.

    File bytecode không thực thi nhanh hơn. Nó chỉ giảm thời gian tải ứng dụng.

    Vì lý do này, khi xây dựng chương trình bằng Python bạn nên tận dụng việc xây dựng và import module, thay vì code trực tiếp trong một module chính lớn. Toàn bộ code nên đưa về các module và để module chính đơn giản nhất có thể.

    Vấn đề đường dẫn khi sử dụng module

    Bạn có thể ý thấy khi import module bạn không hề chỉ định đường dẫn đến file script. Python thực hiện việc tìm kiếm file module tự động theo các thư mục lưu trong biến sys.path.

    Biến sys.path là một danh sách lưu những đường dẫn được đăng ký trong Python. Bạn có thể xem nội dung của sys.path như sau:

    >>> import sys
    >>> sys.path # đây là một list
    ['.', 'E:\\OneDrive\\TuHocICT\\Learn Python\\PythonApps\\HelloPython', 'F:\\Users\\thang\\AppData\\Local\\Programs\\Python\\Python38\\python38.zip', 'F:\\Users\\thang\\AppData\\Local\\Programs\\Python\\Python38\\DLLs', 'F:\\Users\\thang\\AppData\\Local\\Programs\\Python\\Python38\\lib', 'F:\\Users\\thang\\AppData\\Local\\Programs\\Python\\Python38', 'F:\\Users\\thang\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages']
    >>> for p in sys.path:
    ...     print(p)
    ... 
    .
    E:\OneDrive\TuHocICT\Learn Python\PythonApps\HelloPython
    F:\Users\thang\AppData\Local\Programs\Python\Python38\python38.zip
    F:\Users\thang\AppData\Local\Programs\Python\Python38\DLLs
    F:\Users\thang\AppData\Local\Programs\Python\Python38\lib
    F:\Users\thang\AppData\Local\Programs\Python\Python38
    F:\Users\thang\AppData\Local\Programs\Python\Python38\lib\site-packages

    Bạn có thể để ý ngay đường dẫn đầu tiên chính là thư mục làm việc (thư mục hiện hành) nơi bạn đặt file module chính. Ví dụ, nếu bạn chạy module main.py và trong main.py có lệnh import my_math thì Python trước hết sẽ tìm kiếm my_math.py trong thư mục chứa main.py.

    Nếu không tìm thấy file tương ứng, Python sẽ lần lượt tìm trong các thư mục còn lại.

    Để ý một tình huống khác. Giả sử bạn có module mysql nằm trong file mysql.py nằm trong thư mục con modules\database\ của thư mục hiện hành (tức là đường đẫn tương đối tới file là modules\database\mysql.py).

    Để import module mysql bạn cần viết như sau: import modules.database.mysql. Tức là bạn cần chỉ định cấu trúc đường dẫn tương đối tới module. Sự khác biệt ở chỗ các phần của đường dẫn được phân tách bởi dấu chấm. Python sẽ tự động ghép đường dẫn này với các đường dẫn lưu trong sys.path.

    Nếu trong trường hợp các file module của bạn nằm ở một nơi khác (không nằm trong danh sách sys.path mặc định), bạn phải có cách chỉ định đường dẫn để Python có thể tìm thấy. Cách đơn giản nhất như sau:

    import sys
    sys.path.append('-- new path --') # thêm thư mục mới vào sys.path
    # giờ sẽ import module
    import a_module

    Logic ở đây rất đơn giản. Do sys.path chỉ là một list, bạn có thể tự thêm một đường dẫn mới vào sys.path. Khi đó Python sẽ tìm kiếm cả trong đường dẫn bạn thêm vào.

    Ví dụ:

    >>> import sys
    >>> sys.path.append('E:\OneDrive\TuHocICT\Learn Python\Modules')
    >>> import sample_module
    hello world from sample module
    >>> 

    Sử dụng package trong Python

    Trong Python, một số module trong cùng thư mục (và thư mục con) có thể kết hợp lại để tạo ra một package. Package giúp đơn giản hóa hơn nữa việc sử dụng nhiều module có liên quan.

    Hãy cùng thực hiện ví dụ sau:

    Tạo thư mục my_package. Trong thư mục này tạo hai file module1.py và module2.py với code như sau:

    print('This is the module 1')
    def func1():
    	print('Module 1 - func 1')
    print('This is the module 2')
    def func2():
    	print('Module 2 - func 2')
    

    Nếu dừng lại ở đây bạn sẽ có hai module riêng rẽ. Khi sử dụng bạn cần import từng module.

    Giờ hãy tạo file __init__.py trong thư mục my_package. Lưu ý có hai ký tự _ ở trước và sau init. Viết code như sau cho __init__.py:

    from my_package.module1 import *
    from my_package.module2 import *
    #hoặc cũng có thể import như thế này:
    #import my_package.module1
    #import my_package.module2

    Đây là hai lệnh import thông thường mà bạn đã học ở phần trên. Bạn có thể sử dụng kiểu import nào cũng được. Tuy nhiên cần lưu ý về cách viết tên module: my_package.module1. Bạn cần chỉ định tên thư mục, nếu không Python sẽ không tìm được module tương ứng.

    Tên file __init__.py có ý nghĩa đặc thù trong Python. Nếu Python nhìn thấy thư mục nào có file với tên gọi __init__.py, nó sẽ tự động coi thư mục này là một package, chứ không còn là thư mục bình thường nữa.

    Bên ngoài thư mục package hãy tạo module main.py và viết code như sau:

    import my_package as mp
    mp.module1.func1()
    mp.module2.func2()
    from my_package import *
    module1.func1();

    Bạn có thể để ý thấy ngay rằng lệnh import giờ không hoạt động với từng module nữa mà là với tên thư mục my_package.

    Tất cả các thư mục chứa file __init__.py sẽ trở thành một package. Tên thư mục trở thành tên package và có thể sử dụng cùng lệnh import hoặc from/import.

    Khi import một package bạn sẽ đồng thời import tất cả các module của nó (được chỉ định trong __init__.py).

    Nếu sử dụng lệnh import <tên_package>, bạn truy cập vào các hàm của từng module qua cú pháp <tên_package>.<tên_module>.<tên_hàm>().

    Nếu sử dụng from <tên_package> import <tên_hàm>, bạn có thể sử dụng tên ngắn gọn của hàm như bình thường.

    Lưu ý: trong __init__.py và trong module sử dụng package nên dùng cùng một cách import. Ví dụ, cùng dùng import hoặc cùng dùng from .. import.

    Kết luận

    Trong bài học này chúng ta đã học chi tiết cách sử dụng module trong Python:

    • Mỗi file script nào của Python là một module.
    • Trong mỗi module bạn có thể sử dụng hàm và chạy code viết trong một module khác thông qua lệnh import hoặc from .. import.
    • Python tự động tìm kiếm file module dựa trên danh sách thư mục trong sys.path.
    • Hai lối sử dụng import và from .. import chủ yếu khác biệt về cách sử dụng tên hàm trong code chứ không khác biệt về hiệu suất.
    • Các module trong cùng một thư mục có thể kết hợp thành một package.
    • Một thư mục sẽ trở thành package nếu nó chứa file __init__.py (với các lệnh import).

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Theo dõi
    Thông báo của
    guest

    2 Thảo luận
    Cũ nhất
    Mới nhất
    Phản hồi nội tuyến
    Xem tất cả bình luận
    Kynam

    Dòng 42 của my_math.py hình như sửa lại thành end of my_math mới đúng nhỉ?

    acquybloxfruit

    cảm ơn anh đã chỉ cho em ạ