Xây dựng kiến ​​trúc plugin tối thiểu trong Python


190

Tôi có một ứng dụng, được viết bằng Python, được sử dụng bởi một đối tượng khá kỹ thuật (các nhà khoa học).

Tôi đang tìm kiếm một cách tốt để làm cho ứng dụng có thể mở rộng bởi người dùng, tức là kiến ​​trúc tập lệnh / plugin.

Tôi đang tìm kiếm một cái gì đó cực kỳ nhẹ . Hầu hết các tập lệnh hoặc plugin sẽ không được phát triển và phân phối bởi bên thứ ba và được cài đặt, nhưng sẽ là thứ gì đó được người dùng đánh cắp trong vài phút để tự động hóa tác vụ lặp lại, thêm hỗ trợ cho định dạng tệp, v.v. Vì vậy, các plugin nên có mã soạn sẵn tối thiểu tuyệt đối và không yêu cầu 'cài đặt' ngoài việc sao chép vào thư mục (vì vậy một số thứ như các điểm nhập setuptools hoặc kiến ​​trúc plugin Zope có vẻ quá nhiều.)

Có bất kỳ hệ thống như thế này đã có sẵn, hoặc bất kỳ dự án nào thực hiện một kế hoạch tương tự mà tôi nên xem xét cho ý tưởng / cảm hứng?

Câu trả lời:


150

Về cơ bản, thư mục của tôi là một thư mục có tên "plugin" mà ứng dụng chính có thể thăm dò và sau đó sử dụng imp.load_module để chọn tệp, tìm kiếm một điểm nhập cảnh nổi tiếng có thể với các thông số cấu hình cấp mô-đun và đi từ đó. Tôi sử dụng công cụ giám sát tệp cho một mức độ năng động nhất định trong đó các plugin đang hoạt động, nhưng đó là một công cụ tốt để có.

Tất nhiên, bất kỳ yêu cầu nào đi kèm với câu nói "Tôi không cần [điều lớn, phức tạp] X; tôi chỉ muốn thứ gì đó nhẹ" có nguy cơ thực hiện lại yêu cầu X một lần phát hiện ra. Nhưng điều đó không có nghĩa là bạn không thể có niềm vui khi làm điều đó :)


26
Cảm ơn rất nhiều! Tôi đã viết một hướng dẫn nhỏ dựa trên bài đăng của bạn: lkubfox.wordpress.com/2012/10/02/writer-a-python-plugin-api
MiJyn

9
Các impmô-đun đã được phản đối ủng hộ importlibbắt đầu từ python 3.4
b0fh

1
Trong nhiều trường hợp sử dụng, bạn có thể sử dụng importlib.import_module để thay thế imp.load_module.
Chris Arndt

58

module_example.py:

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py:

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

Nó chắc chắn là "tối thiểu", nó hoàn toàn không có lỗi kiểm tra, có thể là vô số vấn đề bảo mật, nó không linh hoạt - nhưng nó sẽ cho bạn thấy một hệ thống plugin trong Python có thể đơn giản như thế nào ..

Bạn có thể muốn nhìn vào imp mô-đun quá, mặc dù bạn có thể làm rất nhiều với chỉ __import__, os.listdirvà một số chuỗi thao tác.


4
Tôi nghĩ bạn có thể muốn thay đổi def call_plugin(name, *args)để def call_plugin(name, *args, **kwargs), và sau đó plugin.plugin_main(*args)đếnplugin.plugin_main(*args, **kwargs)
Ron Klein

12
Trong python 3, impkhông được ủng hộimportlib
Adam Baxter


25

Trong khi câu hỏi đó thực sự thú vị, tôi nghĩ nó khá khó trả lời, không có thêm chi tiết. Đây là loại ứng dụng gì? Nó có GUI không? Nó có phải là một công cụ dòng lệnh? Một tập hợp các kịch bản? Một chương trình với một điểm vào duy nhất, v.v ...

Đưa ra ít thông tin tôi có, tôi sẽ trả lời một cách rất chung chung.

Điều gì có nghĩa là bạn phải thêm plugin?

  • Bạn có thể sẽ phải thêm một tệp cấu hình, trong đó sẽ liệt kê các đường dẫn / thư mục để tải.
  • Một cách khác là nói "bất kỳ tệp nào trong plugin / thư mục đó sẽ được tải", nhưng nó gây bất tiện khi yêu cầu người dùng của bạn di chuyển xung quanh các tệp.
  • Tùy chọn trung gian cuối cùng sẽ là yêu cầu tất cả các plugin nằm trong cùng một plugin / thư mục và sau đó kích hoạt / hủy kích hoạt chúng bằng các đường dẫn tương đối trong tệp cấu hình.

Trên thực tế mã / thiết kế thuần túy, bạn sẽ phải xác định rõ hành vi / hành động cụ thể nào bạn muốn người dùng của mình mở rộng. Xác định điểm vào chung / một nhóm các chức năng sẽ luôn bị ghi đè và xác định các nhóm trong các hành động này. Một khi điều này được thực hiện, nó sẽ dễ dàng mở rộng ứng dụng của bạn,

Ví dụ sử dụng hook , lấy cảm hứng từ MediaWiki (PHP, nhưng ngôn ngữ có thực sự quan trọng không?):

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

Một ví dụ khác, lấy cảm hứng từ đồng bóng. Ở đây, phần mở rộng chỉ thêm các lệnh để thực thi dòng lệnh hg , mở rộng hành vi.

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

Đối với cả hai phương pháp, bạn có thể cần khởi tạo chung và hoàn thiện cho tiện ích mở rộng của mình. Bạn có thể sử dụng một giao diện chung mà tất cả tiện ích mở rộng của bạn sẽ phải triển khai (phù hợp hơn với cách tiếp cận thứ hai; mercurial sử dụng reposetup (ui, repo) được gọi cho tất cả tiện ích mở rộng) hoặc sử dụng phương pháp tiếp cận kiểu móc nối, với hook.setup hook.

Nhưng một lần nữa, nếu bạn muốn câu trả lời hữu ích hơn, bạn sẽ phải thu hẹp câu hỏi của mình;)


11

Khung plugin đơn giản của Marty Allchin là cơ sở tôi sử dụng cho nhu cầu của riêng mình. Tôi thực sự khuyên bạn nên xem nó, tôi nghĩ rằng nó thực sự là một khởi đầu tốt nếu bạn muốn một cái gì đó đơn giản và dễ dàng hack. Bạn cũng có thể tìm thấy nó dưới dạng một Django Snippets .


Tôi đang cố gắng làm một cái gì đó như thế với cơ sở.
edomaur

Nó rất đặc trưng cho Django từ những gì tôi có thể nói.
Zoran Pavlovic

3
@ZoranPavlovic: hoàn toàn không, một số dòng Python tiêu chuẩn, bạn không phải sử dụng Django.
edomaur

11

Tôi là một nhà sinh vật học đã nghỉ hưu, người đã xử lý các micrograqph kỹ thuật số và thấy mình phải viết một gói xử lý và phân tích hình ảnh (không phải là một thư viện) để chạy trên máy SGi. Tôi đã viết mã bằng C và sử dụng Tcl cho ngôn ngữ kịch bản. GUI, như nó đã được thực hiện bằng Tk. Các lệnh xuất hiện trong Tcl có dạng "extensionName lệnhName arg0 arg1 ... param0 param1 ...", nghĩa là các từ và số được phân tách bằng dấu cách đơn giản. Khi Tcl thấy chuỗi con "extensionName", điều khiển được chuyển đến gói C. Đến lượt nó chạy lệnh thông qua một lexer / trình phân tích cú pháp (được thực hiện bằng lex / yacc) và sau đó gọi các thường trình C là cần thiết.

Các lệnh để vận hành gói có thể được chạy từng cái một thông qua một cửa sổ trong GUI, nhưng các công việc hàng loạt được thực hiện bằng cách chỉnh sửa các tệp văn bản là các tập lệnh Tcl hợp lệ; bạn chọn mẫu đã thực hiện loại thao tác cấp tệp mà bạn muốn thực hiện và sau đó chỉnh sửa một bản sao để chứa thư mục thực tế và tên tệp cộng với các lệnh gói. Nó làm việc như một say mê. Cho đến khi ...

1) Thế giới chuyển sang PC và 2) các tập lệnh dài hơn khoảng 500 dòng, khi khả năng tổ chức iffy của Tcl bắt đầu trở thành một sự bất tiện thực sự. Thời gian trôi qua ...

Tôi đã nghỉ hưu, Python được phát minh và nó trông giống như sự kế thừa hoàn hảo cho Tcl. Bây giờ, tôi chưa bao giờ thực hiện cổng, bởi vì tôi chưa bao giờ phải đối mặt với những thách thức khi biên dịch các chương trình C (khá lớn) trên PC, mở rộng Python bằng gói C và thực hiện GUI trong Python / Gt? / Tk? /? ? Tuy nhiên, ý tưởng cũ về việc có các kịch bản mẫu có thể chỉnh sửa dường như vẫn khả thi. Ngoài ra, việc nhập các lệnh gói ở dạng Python nguyên bản không phải là một gánh nặng quá lớn, vd:

packName.command (arg0, arg1, ..., param0, param1, ...)

Thêm một vài dấu chấm, dấu chấm và dấu phẩy, nhưng những dấu chấm đó không hiển thị.

Tôi nhớ rằng ai đó đã thực hiện các phiên bản lex và yacc trong Python (thử: http://www.dabeaz.com/ply/ ), vì vậy nếu những thứ đó vẫn cần thiết, chúng sẽ ở xung quanh.

Điểm mấu chốt của việc lan man này là dường như đối với tôi, bản thân Python là mặt trước "nhẹ" mong muốn mà các nhà khoa học có thể sử dụng. Tôi tò mò muốn biết lý do tại sao bạn nghĩ rằng nó không phải, và tôi có nghĩa là nghiêm túc.


được thêm vào sau: Ứng dụng gedit dự đoán các plugin được thêm vào và trang web của họ có lời giải thích rõ ràng nhất về quy trình plugin đơn giản mà tôi đã tìm thấy trong vài phút tìm kiếm xung quanh. Thử:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

Tôi vẫn muốn hiểu câu hỏi của bạn hơn. Tôi không rõ liệu bạn 1) có muốn các nhà khoa học có thể sử dụng ứng dụng (Python) của bạn khá đơn giản theo nhiều cách khác nhau hay 2) muốn cho phép các nhà khoa học thêm các khả năng mới vào ứng dụng của bạn. Lựa chọn số 1 là tình huống chúng tôi gặp phải với các hình ảnh và điều đó đã khiến chúng tôi sử dụng các tập lệnh chung mà chúng tôi đã sửa đổi cho phù hợp với nhu cầu của thời điểm này. Đây có phải là Lựa chọn số 2 dẫn bạn đến ý tưởng về plugin hay chính khía cạnh nào đó trong ứng dụng của bạn khiến việc ban hành lệnh cho nó không thể thực hiện được?


2
Sửa chữa liên kết thối: Hiện tại plugin Gedit - wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob

1
Đây là một bài viết hay, bởi vì nó cho thấy rõ ràng và chính xác sự may mắn của chúng ta đối với các nhà sinh học thời hiện đại. Đối với anh ấy / cô ấy, python là ngôn ngữ kịch bản mô-đun được sử dụng để cung cấp một số trừu tượng cho các nhà phát triển mô-đun để họ không cần phân tích mã C chính. Tuy nhiên, bây giờ, một vài nhà sinh học sẽ học C, thay vào đó làm mọi thứ bằng Python. Làm thế nào để chúng ta trừu tượng hóa sự phức tạp của các chương trình python chính của chúng ta khi viết các mô-đun? Trong 10 năm nữa, có lẽ các chương trình sẽ được viết bằng Emoji và các mô-đun sẽ chỉ là các tệp âm thanh chứa một loạt tiếng lẩm bẩm. Và có lẽ đó là OK.
JJ

10

Khi tôi tìm kiếm Python Decorators, đã tìm thấy một đoạn mã đơn giản nhưng hữu ích. Nó có thể không phù hợp với nhu cầu của bạn nhưng rất truyền cảm.

Scipy Advanced Python # Hệ thống đăng ký Plugin

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

Sử dụng:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

1
Lưu ý: Trong ví dụ này, WordProcessor.pluginkhông trả về bất cứ thứ gì ( None), vì vậy, nhập CleanMdashesExtensionlớp sau chỉ nhập None. Nếu các lớp plugin là hữu ích, hãy tạo .pluginphương thức lớp return plugin.
jkmacc

@jkmacc Bạn nói đúng. Tôi đã sửa đổi đoạn trích 13 ngày sau nhận xét của bạn. Cảm ơn bạn.
guneysus 17/03/2017

7

Tôi rất thích cuộc thảo luận thú vị về các kiến ​​trúc plugin khác nhau được đưa ra bởi Tiến sĩ Andre Roberge tại Pycon 2009. Ông đưa ra một cái nhìn tổng quan tốt về các cách khác nhau để thực hiện các plugin, bắt đầu từ một cái gì đó thực sự đơn giản.

Nó có sẵn dưới dạng podcast (phần thứ hai sau phần giải thích về việc vá khỉ) kèm theo một loạt sáu mục blog .

Tôi khuyên bạn nên lắng nghe nhanh trước khi đưa ra quyết định.


4

Tôi đến đây để tìm kiếm một kiến ​​trúc plugin tối thiểu và tìm thấy rất nhiều thứ mà tất cả dường như quá mức đối với tôi. Vì vậy, tôi đã triển khai các Plugin Python siêu đơn giản . Để sử dụng nó, bạn tạo một hoặc nhiều thư mục và thả một __init__.pytệp đặc biệt vào mỗi thư mục. Nhập các thư mục đó sẽ khiến tất cả các tệp Python khác được tải dưới dạng các mô hình con và tên của chúng sẽ được đặt trong __all__danh sách. Sau đó, tùy thuộc vào bạn để xác thực / khởi tạo / đăng ký các mô-đun đó. Có một ví dụ trong tệp README.


4

Trên thực tế setuptools hoạt động với một "thư mục plugin", như ví dụ sau được lấy từ tài liệu của dự án: http://peak.telecommunity.com/DevCenter/PkgResource#locating-plugins

Ví dụ sử dụng:

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

Về lâu dài, setuptools là một lựa chọn an toàn hơn nhiều vì nó có thể tải các plugin mà không có xung đột hoặc thiếu các yêu cầu.

Một lợi ích khác là các plugin có thể được mở rộng bằng cách sử dụng cùng một cơ chế, mà không cần các ứng dụng gốc phải quan tâm đến nó.


3

Là một cách tiếp cận khác với hệ thống plugin, Bạn có thể kiểm tra dự án Extend Me .

Ví dụ: hãy định nghĩa lớp đơn giản và phần mở rộng của nó

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

Và cố gắng sử dụng nó:

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

Và hiển thị những gì ẩn đằng sau hiện trường:

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

thư viện extend_me điều khiển quá trình tạo lớp thông qua siêu dữ liệu, ví dụ như ở trên, khi tạo phiên bản mới của MyCoolClasschúng ta có thể hiện của lớp mới là lớp con của cả hai MyCoolClassExtensionMyCoolClasscó chức năng của cả hai, nhờ vào tính kế thừa của Python

Để kiểm soát tốt hơn việc tạo lớp, có một số siêu dữ liệu được định nghĩa trong lib này:

  • ExtensibleType - cho phép mở rộng đơn giản bằng cách phân lớp

  • ExtensibleByHashType - tương tự như ExtensibleType, nhưng có khả năng xây dựng các phiên bản chuyên biệt của lớp, cho phép mở rộng toàn cầu lớp cơ sở và mở rộng các phiên bản chuyên biệt của lớp

Lib này được sử dụng trong OpenERP Proxy Project và dường như hoạt động đủ tốt!

Để biết ví dụ thực tế về việc sử dụng, hãy xem phần mở rộng 'Field_datetime' của OpenERP Proxy :

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

Recordđây là đối tượng mở rộng. RecordDateTimelà phần mở rộng.

Để bật tiện ích mở rộng, chỉ cần nhập mô-đun có chứa lớp mở rộng và (trong trường hợp ở trên) tất cả Recordcác đối tượng được tạo sau nó sẽ có lớp mở rộng trong các lớp cơ sở, do đó có tất cả chức năng của nó.

Ưu điểm chính của thư viện này là, mã vận hành các đối tượng có thể mở rộng, không cần biết về tiện ích mở rộng và tiện ích mở rộng có thể thay đổi mọi thứ trong các đối tượng mở rộng.


Tôi nghĩ bạn có nghĩa là khởi tạo từ lớp con, tức là my_cool_obj = MyCoolClassExtension1()thay vìmy_cool_obj = MyCoolClass()
pylang

không, lớp Extensible có __new__phương thức ghi đè , vì vậy nó tự động tìm tất cả các lớp con và xây dựng lớp mới, đó là lớp con của tất cả chúng, và trả về thể hiện mới của lớp được tạo này. Do đó, ứng dụng gốc không cần biết về tất cả các tiện ích mở rộng. Cách tiếp cận này hữu ích khi xây dựng thư viện, để cho phép người dùng cuối sửa đổi hoặc mở rộng dễ dàng hơn. trong ví dụ trên, MyCoolClass có thể được xác định trong thư viện và được sử dụng bởi nó và MyCoolClassExtension có thể được xác định bởi người dùng cuối.
FireMage

Một ví dụ nữa đã được thêm vào để trả lời
FireMage

3

setuptools có Entrypoint :

Điểm vào là một cách đơn giản để phân phối cho các đối tượng Quảng cáo của Python (như các hàm hoặc lớp) để các phân phối khác sử dụng. Các ứng dụng và khung mở rộng có thể tìm kiếm các điểm vào với một tên hoặc nhóm cụ thể, từ một bản phân phối cụ thể hoặc từ tất cả các bản phân phối hoạt động trên sys.path, sau đó kiểm tra hoặc tải các đối tượng được quảng cáo theo ý muốn.

AFAIK gói này luôn có sẵn nếu bạn sử dụng pip hoặc virtualenv.


2

Mở rộng câu trả lời của @ edomaur có thể tôi khuyên bạn nên xem qua Simple_plugins (phích cắm không biết xấu hổ), đây là một khung plugin đơn giản được lấy cảm hứng từ công việc của Marty Alchin .

Một ví dụ sử dụng ngắn dựa trên README của dự án:

# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
 'classes', 'class_to_id', 'id_to_instance']

# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])

# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])

>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
     <class '__main__.NotModified'>, <class '__main__.BadRequest'>,
     <class '__main__.MovedPermanently'>])

>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>

>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>

>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]

# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>

2

Tôi đã dành thời gian đọc chủ đề này trong khi tôi đang tìm kiếm một khung plugin trong Python bây giờ và sau đó. Tôi đã sử dụng một số nhưng có những thiếu sót với họ. Dưới đây là những gì tôi đưa ra cho sự xem xét kỹ lưỡng của bạn trong năm 2017, một hệ thống quản lý plugin miễn phí, kết nối lỏng lẻo: Tải cho tôi sau . Dưới đây là hướng dẫn về cách sử dụng nó.


2

Bạn có thể sử dụng pluginlib .

Plugin rất dễ tạo và có thể được tải từ các gói, đường dẫn tệp hoặc điểm nhập khác.

Tạo một lớp cha mẹ plugin, xác định bất kỳ phương thức cần thiết nào:

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

Tạo một plugin bằng cách kế thừa một lớp cha:

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

Tải các plugin:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

1
Cảm ơn ví dụ. Tôi đã vật lộn với 1 câu hỏi. Vì bạn đã đề cập đến khả năng tải các plugin từ các gói khác nhau, có lẽ bạn đã nghĩ về nó. Tôi tự hỏi lớp cha mẹ nên cư trú ở đâu. Thông thường, bạn nên có nó trong gói của ứng dụng (có lẽ là một kho lưu trữ mã nguồn riêng), nhưng sau đó chúng ta sẽ kế thừa nó như thế nào tại cơ sở mã của plugin? Chúng ta có nhập toàn bộ ứng dụng cho việc này không? Hoặc có cần thiết phải có mã giao diện như lớp Parser hoặc các tóm tắt tương tự trong gói thứ 3 (sẽ là kho lưu trữ mã thứ 3) không?
JAponte

1
Lớp cha nên nằm trong cùng một cơ sở mã với ứng dụng, nhưng có lẽ trong mô-đun riêng của chúng. Vì vậy, đối với một gói được gọi foo, bạn có thể có một mô-đun được gọi là foo.parentsnơi bạn xác định các lớp cha. Sau đó, plugin của bạn, sẽ nhập khẩu foo.parents. Điều đó làm việc tốt cho hầu hết các trường hợp sử dụng. Vì bản thân 'foo' cũng được nhập, để tránh khả năng nhập vòng tròn, rất nhiều dự án để trống phần gốc của mô-đun và sử dụng __main__.pytệp hoặc điểm nhập để khởi chạy ứng dụng.
aviso

1

Tôi đã dành rất nhiều thời gian để cố gắng tìm hệ thống plugin nhỏ cho Python, phù hợp với nhu cầu của tôi. Nhưng sau đó tôi chỉ nghĩ, nếu đã có một gia tài, đó là điều tự nhiên và linh hoạt, tại sao không sử dụng nó.

Vấn đề duy nhất với việc sử dụng tính kế thừa cho các plugin là bạn không biết các lớp plugin cụ thể nhất (thấp nhất trên cây thừa kế) là gì.

Nhưng điều này có thể được giải quyết bằng siêu dữ liệu, theo dõi sự kế thừa của lớp cơ sở và có thể có thể xây dựng lớp, kế thừa từ hầu hết các plugin cụ thể ('Root được mở rộng' trên hình bên dưới)

nhập mô tả hình ảnh ở đây

Vì vậy, tôi đã đưa ra một giải pháp bằng cách mã hóa một siêu dữ liệu như vậy:

class PluginBaseMeta(type):
    def __new__(mcls, name, bases, namespace):
        cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
        if not hasattr(cls, '__pluginextensions__'):  # parent class
            cls.__pluginextensions__ = {cls}  # set reflects lowest plugins
            cls.__pluginroot__ = cls
            cls.__pluginiscachevalid__ = False
        else:  # subclass
            assert not set(namespace) & {'__pluginextensions__',
                                         '__pluginroot__'}     # only in parent
            exts = cls.__pluginextensions__
            exts.difference_update(set(bases))  # remove parents
            exts.add(cls)  # and add current
            cls.__pluginroot__.__pluginiscachevalid__ = False
        return cls

    @property
    def PluginExtended(cls):
        # After PluginExtended creation we'll have only 1 item in set
        # so this is used for caching, mainly not to create same PluginExtended
        if cls.__pluginroot__.__pluginiscachevalid__:
            return next(iter(cls.__pluginextensions__))  # only 1 item in set
        else:
            name = cls.__pluginroot__.__name__ + 'PluginExtended'
            extended = type(name, tuple(cls.__pluginextensions__), {})
            cls.__pluginroot__.__pluginiscachevalid__ = True
return extended

Vì vậy, khi bạn có Root cơ sở, được tạo bằng siêu dữ liệu và có cây bổ trợ thừa hưởng từ nó, bạn có thể tự động nhận lớp, kế thừa từ các plugin cụ thể nhất bằng cách chỉ phân lớp:

class RootExtended(RootBase.PluginExtended):
    ... your code here ...

Cơ sở mã khá nhỏ (~ 30 dòng mã thuần) và linh hoạt như kế thừa cho phép.

Nếu bạn quan tâm, hãy tham gia @ https://github.com/thodnev/pluginlib


1

Bạn cũng có thể có một cái nhìn vào Groundwork .

Ý tưởng là xây dựng các ứng dụng xung quanh các thành phần có thể tái sử dụng, được gọi là các mẫu và plugin. Plugin là các lớp có nguồn gốc từ GwBasePattern. Đây là một ví dụ cơ bản:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): 
        pass

    def deactivate(self):
        pass

my_app = App(plugins=[MyPlugin])       # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it

Ngoài ra còn có các mẫu nâng cao hơn để xử lý ví dụ: giao diện dòng lệnh, các đối tượng báo hiệu hoặc chia sẻ.

Groundwork tìm thấy các plugin của nó bằng cách lập trình ràng buộc chúng với một ứng dụng như được hiển thị ở trên hoặc tự động thông qua setuptools. Các gói Python chứa plugin phải khai báo chúng bằng cách sử dụng một điểm nhập đặc biệt groundwork.plugin.

Dưới đây là các tài liệu .

Tuyên bố miễn trừ trách nhiệm : Tôi là một trong những tác giả của Groundwork.


0

Trong sản phẩm chăm sóc sức khỏe hiện tại của chúng tôi, chúng tôi có một kiến ​​trúc plugin được triển khai với lớp giao diện. Ngăn xếp công nghệ của chúng tôi là Django trên đầu Python cho API và Nuxtjs trên đầu trang của nodejs cho frontend.

Chúng tôi có một ứng dụng quản lý plugin được viết cho sản phẩm của chúng tôi, về cơ bản là gói pip và npm tuân thủ Django và Nuxtjs.

Để phát triển plugin mới (pip và npm), chúng tôi đã tạo trình quản lý plugin làm phụ thuộc.

Trong gói Pip: Với sự trợ giúp của setup.py, bạn có thể thêm điểm vào của plugin để làm điều gì đó với trình quản lý plugin (đăng ký, khởi tạo, v.v.) https://setuptools.readthedocs.io/en/latest/setuptools .html # tự động tạo tập lệnh

Trong gói npm: Tương tự như pip có các móc trong tập lệnh npm để xử lý cài đặt. https://docs.npmjs.com/misc/scripts

Usecase của chúng tôi:

nhóm phát triển plugin tách biệt với nhóm phát triển cốt lõi bây giờ. Phạm vi phát triển plugin là để tích hợp với các ứng dụng của bên thứ 3 được xác định trong bất kỳ danh mục nào của sản phẩm. Các giao diện plugin được phân loại ví dụ: - Fax, điện thoại, email ... vv trình quản lý plugin có thể được nâng cấp thành các danh mục mới.

Trong trường hợp của bạn: Có thể bạn có thể có một plugin được viết và tái sử dụng tương tự để thực hiện công cụ.

Nếu các nhà phát triển plugin cần sử dụng các đối tượng cốt lõi tái sử dụng mà đối tượng có thể được sử dụng bằng cách thực hiện một mức độ trừu tượng trong trình quản lý plugin để bất kỳ plugin nào cũng có thể kế thừa các phương thức đó.

Chỉ chia sẻ cách chúng tôi thực hiện trong sản phẩm của mình hy vọng nó sẽ cho một ý tưởng nhỏ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.