2013-02-12 58 views
5

我正在用Python 2.7模塊的形式構建一個非常基礎的平臺。該模塊具有讀取評估打印循環,其中輸入的用戶命令映射到函數調用。由於我正在試圖爲我的平臺構建插件模塊,所以函數調用將從我的主模塊到任意插件模塊。我希望一個插件生成器能夠指定他想要觸發他的函數的命令,所以我一直在尋找一種Pythonic方法來遠程輸入主模塊中的command-> function dict中的映射插件模塊。在編譯時提供函數元數據的大多數Pythonic方法?

我已經看了幾件事情:

  1. 方法名稱解析:主模塊將導入插件模塊 和掃描它匹配某種格式,方法名。例如,對於 示例,它可能會將download_file_command(file)方法作爲「下載文件」 - > download_file_command添加到其 字典中。但是,獲取簡潔易用的命令名稱(例如「dl」)要求功能的名稱也較短,這不利於代碼 的可讀性。它還要求插件開發者符合 的精確命名格式。

  2. 跨模塊的裝飾:裝飾會讓 插件開發者的名字,無論他希望他的功能和簡單的 添加類似@ Main.register(「DL」),但他們必然 要求我都修改另一個模塊的名稱空間,並在主模塊中保持全局狀態 。我知道這很糟糕。

  3. 相同模塊裝飾:使用與上述相同的邏輯,I可以添加一個 裝飾,其將所述函數的名稱,以一些命令名稱 - >函數映射本地的 插件模塊和檢索映射到主要模塊與 API調用。這要求某些方法總是存在或者繼承,但是 - 如果我對裝飾器的理解是正確的 - 函數只會在第一次運行時自行註冊,並且在隨後的其後 之後將不必要地重新註冊自身。

因此,我真正需要的是與註釋應觸發它的命令名稱的功能Python化的方式,並且這種方式不能是函數的名稱。當我導入模塊時,我需要能夠提取命令名 - >函數映射,並且在插件開發人員方面減少工作量是一大優勢。

感謝您的幫助,如果我的Python理解存在任何缺陷,我表示歉意。我對這門語言比較陌生。

+1

只是檢查你意識到這一點,但裝修(和函數定義)在運行時執行,Python做在編譯時的唯一的事情就是編譯代碼。 – Duncan 2013-02-12 09:55:17

+1

方法2沒有任何問題。讓他們執行類似'import plugin'的操作並執行'@ plugin.register('my_method_name')\ ndef somefunc_meeting_an_api_spec():...' – 2013-02-12 09:57:08

+0

您可能想看看[plac](http://pypi.python.org/pypi/plac)就是這樣做的。 – georg 2013-02-12 09:59:30

回答

4

用戶定義的函數可以具有任意屬性。所以你可以指定插件函數有一個具有特定名稱的屬性。例如:

那麼,你的模塊中,你可以建立這樣的映射:

import inspect #from standard library 

import plugin 

mapping = {} 

for v in plugin.__dict__.itervalues(): 
    if inspect.isfunction(v) and v.hasattr('command_name'): 
     mapping[v.command_name] = v 

要了解用戶定義的函數看the docs

+0

這正是我尋找的東西的類型! – mieubrisse 2013-02-12 19:38:43

+0

我意識到這個問題有點舊,但在這裏看到我的相關問題http://stackoverflow.com/questions/37192712/how-can-attributes-on-functions-survive-wrapping。您是否遇到使用存儲在函數中的屬性進行包裝的問題? – matthewatabet 2016-05-12 16:38:10

1

有兩個部分的插件系統:

  1. 發現插件
  2. 引發一些代碼執行在一個插件

在你的問題只地址的第二部分所提出的解決方案。

有很多同時實現根據您的要求如的方式,以使插件,他們可以在配置文件中指定的應用程序:

plugins = some_package.plugin_for_your_app 
    another_plugin_module 
    # ... 

爲了實現插件模塊的加載:

plugins = [importlib.import_module(name) for name in config.get("plugins")] 

爲了得到一本字典:command name -> function

commands = {name: func 
      for plugin in plugins 
      for name, func in plugin.get_commands().items()} 

插件作者可以使用任何方法來實現get_commands(),例如,使用前綴或裝飾器 - 只要get_commands()返回每個插件的命令字典,主應用程序就不會在意。

例如,some_plugin.py(完整的源):

def f(a, b): 
    return a + b 

def get_commands(): 
    return {"add": f, "multiply": lambda x,y: x*y} 

它定義兩個命令addmultiply

+0

謝謝你;我喜歡這個背後的設計解釋。這個解釋表明,實現get_commands()列表的超類可能是一個好主意,所以開發人員必須儘可能少地完成工作。 – mieubrisse 2013-02-12 19:47:44

+0

@mieubrisse:不需要課程。我已經添加了完整的插件示例。 – jfs 2013-02-12 21:16:42

4

建築物或站在任意屬性@ ericstalbot的答案的第一部分,你可能會發現使用像下面這樣的裝飾器很方便。

################################################################################ 
import functools 
def register(command_name): 
    def wrapped(fn): 
     @functools.wraps(fn) 
     def wrapped_f(*args, **kwargs): 
      return fn(*args, **kwargs) 
     wrapped_f.__doc__ += "(command=%s)" % command_name 
     wrapped_f.command_name = command_name 
     return wrapped_f 
    return wrapped 
################################################################################ 
@register('cp') 
def copy_all_the_files(*args, **kwargs): 
    """Copy many files.""" 
    print "copy_all_the_files:", args, kwargs 
################################################################################ 

print "Command Name: ", copy_all_the_files.command_name 
print "Docstring : ", copy_all_the_files.__doc__ 

copy_all_the_files("a", "b", keep=True) 

輸出運行時:

Command Name: cp 
Docstring : Copy many files.(command=cp) 
copy_all_the_files: ('a', 'b') {'keep': True} 
相關問題