i9bet58 - Giới thiệu

Game B29 Game Bài 888B,twin68 game bài nổ hũ kwin

Cách thực hiện tải plugin trong chương trình Python (tải mô-đun động, phát triển theo kiến trúc plugin)

Trong thế giới lập trình ngày nay, việc mở rộng tính năng của phần mềm thông qua các plugin hoặc mô-đun phụ là một xu hướng phổ biến. Nhiều công cụ như PyCharm, VSCode đều hỗ trợ thêm plugin để mở rộng chức năng. Trò chơi nổi tiếng như Minecraft, Terraria hay Don’t Starve cũng cho phép người dùng tùy chỉnh trải nghiệm bằng cách thêm mod.

Python, với định dạng module chính là .py (hoặc .pyd), mang lại sự tiện lợi đáng kể khi làm việc với các mô-đun và plugin. Vậy làm thế nào để tạo ra một hệ thống có khả năng quét và tải plugin trong Python?

Trong bài viết này, chúng ta sẽ cùng tìm hiểu cách:

  1. Tải mô-đun một cách động.
  2. Nhập mô-đun từ các vị trí cụ thể (tương đối hoặc tuyệt đối).
  3. Khiến chương trình có thể tìm thấy các mô-đun cần tải.

Phần 1: Làm thế nào để tải mô-đun một cách động

1.1 Sử dụng từ khóa import (Không khả thi)

Phương pháp sử dụng từ khóa importfrom là cách phổ biến nhất để nhập mô-đun trong Python. Tuy nhiên, tên mô-đun sau từ khóa không thể là biến mà phải là hằng số đã được xác định trước. Điều này khiến việc sử dụng các công cụ đóng gói như PyInstaller hoặc Nuitka trở nên khó khăn hơn nếu bạn cố gắng để trống tên mô-đun sau từ khóa.

1.2 Sử dụng hàm __import__ (Không khuyến khích)

Hàm __import__ thực chất là phiên bản gốc của import, nhưng Python không khuyến khích sử dụng nó. Thay vào đó, Python khuyến nghị sử dụng importlib.import_module.

1.3 Sử dụng mô-đun importlib

Mô-đun importlib cung cấp một cách đơn giản và hiệu quả để tải mô-đun động. Hàm importlib.import_module(name, package=None) chỉ yêu cầu hai tham số: tên mô-đun và tên gói (nếu có). Bạn cũng có thể sử dụng cú pháp tương đối như ".aaa.aaa" khi chỉ định tham số package.

Lưu ý rằng khi sử dụng đường dẫn tương đối, hãy chú ý đến vị trí chạy của tệp thực thi (như .exe) vì đây sẽ là điểm khởi đầu cho đường dẫn tương đối.


Phần 2: Nhập mô-đun từ vị trí cụ thể

Giả sử cấu trúc thư mục của dự án như sau:

1
2
3
4
5
6
7
src
├── package
│   ├── module1.py
│   └── module2.py
├── inside
│   └── __main__2.py
└── __main__1.py

2.1 Nhập mô-đun từ cùng cấp hoặc cấp dưới

Khi tệp __main__1.py là điểm nhập:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import importlib

# Nhập module1 từ gói "package"
module1 = importlib.import_module("package.module1")

# Nhập module2 từ gói "package" với cú pháp tương đối
module2 = importlib.import_module(".module2", package="package")

# Nhập __main__2 từ gói "inside"
__main__2 = importlib.import_module("inside.__main__2")

2.2 Nhập mô-đun từ cấp trên

Khi tệp __main__2.py là điểm nhập:

2.2.1 Sử dụng từ khóa from ... import (Không giải quyết được)

Nếu thử nhập từ cấp trên như sau:

1
from ..package import module1

Bạn sẽ gặp lỗi ImportError: attempted relative import with no known parent package. Nguyên nhân là do __main__2.py không thuộc về một gói cụ thể mà là chương trình chính.

2.2.2 Sử dụng sys.path

sys.path hoạt động giống như biến môi trường trong Windows, chứa danh sách các vị trí mà Python sẽ tìm kiếm các gói. Để nhập mô-đun từ cấp trên, bạn chỉ cần thêm đường dẫn cấp trên vào danh sách sys.path:

1
2
3
4
5
6
7
import sys
import os

# Thêm đường dẫn cấp trên vào sys.path
sys.path.append("..")  # Đường dẫn tương đối
# Hoặc sử dụng đường dẫn tuyệt đối
sys.path.append(os.path.join(sys.path[0], ".."))

2.2.3 Ví dụ thực tế

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import importlib
import sys

# Thêm cấp trên vào đường dẫn tìm kiếm
sys.path.append("..")

# Nhập các mô-đun từ cấp trên
module1 = importlib.import_module("package.module1")
module2 = importlib.import_module(".module2", package="package")
__main__1 = importlib.import_module("__main__1")

Phần 3: Khiến chương trình có thể tìm thấy các plugin

Có nhiều cách để quản lý và tìm kiếm các plugin. Python cung cấp các mô-đun tích hợp như os.pathpathlib để xử lý file và thư mục.

Quy tắc plugin đơn giản

Để dễ dàng triển khai, chúng ta sẽ áp dụng quy tắc sau: mọi tệp có đuôi .py nằm trong thư mục plugins đều được coi là plugin hợp lệ.

Quy trình cơ bản:

  1. Quét tất cả các tệp trong thư mục plugins.
  2. Lọc ra các tệp có đuôi .py.
  3. Chuyển đổi tên tệp thành định dạng phù hợp để truyền vào importlib.import_module().

Ví dụ mã nguồn:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import os

# Bước 1: Đọc toàn bộ nội dung trong thư mục plugins
things_in_plugin_dir = os.listdir(path)

# Bước 2: Lọc và chuyển đổi
def pick_module(name):
    if name.endswith(".py"):  # Kiểm tra đuôi tệp
        return name.split(".")[0]  # Lấy tên tệp không bao gồm phần mở rộng
    else:
        return ""  # Loại bỏ các tệp không phù hợp

files_in_plugin_dir = list(filter(None, map(pick_module, things_in_plugin_dir)))

Ví dụ hoàn chỉnh

Cấu trúc thư mục

1
2
3
4
5
6
src
├── plugins
│   ├── module1.py
│   └── module2.py
└── main_program
    └── __main__.py

Trong đó module1.pymodule2.py là hai plugin.

Nội dung module1.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
version = "v1.0.0"

def test():
    print("this is a test func")

class testClass():
    def __init__(self):
        print("this is a test class")

    def test(self):
        print("this is a test func in class")

Nội dung main_program/__main__.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os
import importlib
import traceback
from types import ModuleType

loaded_plugins = {}  # Lưu trữ các plugin đã tải

# Cài đặt đường dẫn và quy tắc
plugin_suffix = "py"
path = os.path.join("..", "plugins")

# Quét và lọc plugin
things_in_plugin_dir = os.listdir(path)
def pick_module(name):
    if name.endswith(plugin_suffix):
        return name.split(".")[0]
    else:
        return ""

files_in_plugin_dir = list(filter(None, map(pick_module, things_in_plugin_dir)))

# Tải plugin
for name in files_in_plugin_dir:
    try:
        loaded_plugins[name] = importlib.import_module(f"{name}")
    except ModuleNotFoundError:
        traceback.print_exc()
    except ImportError:
        traceback.print_exc()
    except Exception as e:
        print(f"A problem occurred: {e}")
        traceback.print_exc()

# Sử dụng plugin
if "module1" in loaded_plugins:
    loaded_plugins["module1"].test()  # Output: this is a test func
    instance = loaded_plugins["module1"].testClass()
    instance.test()  # Output: this is a test func in class
    print(loaded_plugins["module1"].version)  # Output: v1.0.0

Tổng kết

Bài viết đã trình bày cách triển khai hệ thống plugin trong Python bằng cách sử dụng importlib để tải mô-đun động và os.path để quản lý file/thư mục. Với phương pháp này, bạn có thể dễ dàng xây dựng các ứng dụng có khả năng mở rộng cao.


Hy vọng bài viết này hữu ích cho bạn!

Built with Hugo
Theme Stack thiết kế bởi Jimmy