Python 将函数与运行时隔离

Python 将函数与运行时隔离,python,plugins,isolation,Python,Plugins,Isolation,我不知道该怎么称呼,如果有人能想出更好的标题,让我知道,我会重新命名这个问题 这不是现实生活中的例子,但若我得到了这种情况的解决方案,我将在更大的项目中使用它。假设我必须像下面描述的那样做,我不能改变想法,我需要找到适合它的解决方案。目前,我不允许展示任何原始项目的细节 创意: 所以,假设我正在基于YAPSY插件创建类似cron的东西。我希望将插件存储在某个目录中,有时我的守护进程会收集该目录中的所有插件,调用它们的方法,然后再休眠一段时间。这些插件应该能够访问一些单例,这将存储一些数据,例如插

我不知道该怎么称呼,如果有人能想出更好的标题,让我知道,我会重新命名这个问题

这不是现实生活中的例子,但若我得到了这种情况的解决方案,我将在更大的项目中使用它。假设我必须像下面描述的那样做,我不能改变想法,我需要找到适合它的解决方案。目前,我不允许展示任何原始项目的细节

创意:

所以,假设我正在基于YAPSY插件创建类似cron的东西。我希望将插件存储在某个目录中,有时我的守护进程会收集该目录中的所有插件,调用它们的方法,然后再休眠一段时间。这些插件应该能够访问一些单例,这将存储一些数据,例如插件使用的URL等。我也有TCP服务器在同一进程中运行,这将修改这个单例,所以我可以自定义运行时的行为。TCP服务器应该具有对singleton的读/写访问权限,插件应该具有只读访问权限。可能有许多单例(我的意思是,许多类作为单例运行,而不是一个类的多个实例,duh)可供插件读取并可由TCP服务器修改。在调用插件方法之前,我们会生成一些值来调用它们,并且只能在它们所在的过程中生成它们

问题:

如何授予插件的只读访问权限?我想要完全隔离,这样如果
singleton
有字段
x
,这是对象,那么
singleton.x
(比如
singleton.x.y
)的字段对于插件也应该是只读的。只读意味着插件应该能够修改这些字段,但它不会对运行时的其余部分产生任何影响,所以当插件方法返回时,singleton(及其字段,及其字段等)应该与运行插件方法之前相同,所以它不是真正的只读。此外,插件可能以并发方式运行,并释放GIL一段时间(它们可能有IO操作,或者只使用time.sleep()

--编辑--

解决方案必须是多平台的,并且至少可以在Linux、Windows和MacOS上运行

--/编辑--

方法:

  • 我可以尝试用单例方法检查堆栈,看看是否有调用方是插件,若有,则存储任何修改字段的原始值。然后,在plugin方法调用之后,我将使用函数restore(),它将singleton恢复到运行plugin之前的状态

  • 我可以尝试在另一个进程中运行plugins方法,使用多进程,将所有单例传递给子进程(很容易做到,使用元类跟踪所有单例,并在新进程中重建它们,或者显式地将单例存储在某处)

  • 我可以尝试将
    globals()
    locals()
    包装成一些
    dict
    s,这样做的技巧类似于第(1)点(恢复原始值),或者深度复制所有全局和局部变量,并使用
    exec
    ,使用插件代码方法(不是字符串,我知道这是不安全的)

  • 为什么上述方法不起作用?

    (1) 当前位置堆栈检查通常是错误的,我认为在这种情况下它是非常错误的。此外,每次调用后恢复变量可能会非常昂贵,因为插件会做很多修改。此外,插件方法可能以并发方式运行,因此每次发布GIL时我都需要恢复原始值,并且在获取GIL时恢复插件范围内的值——这会造成很大的伤害(你甚至可以想象实现这一点吗?目前,我不能,对此我并不感到抱歉)

    (2) :YAPSY插件不可拾取,因此我无法将它们发送到子进程

    (3) :
    exec()
    不会执行带有自由变量的代码,它看不到调用它的范围,所以我需要找到插件函数的所有自由变量(我会使用运行时生成的包装器,如下所示:

    def no_arg_plugin_call():
        plugin.method(something, from_, locals_)
    
    然后传递
    no_args\u plugin\u call.\uuu code\uuu
    )并将它们存储在包装的
    locals()
    中。此外,整个环境的深度复制成本与(1)中的一样高

    PS.我所说的“字段”是指“属性”,因为(不幸的是)我是在Java和类似的环境中长大的


    PPS.如果你听说过任何类似于YAPSY的插件系统(它必须具备所有的功能,并且是轻量级的),并且会生成可拾取的实例,那对我来说就足够了;)

    这样如何:您使用fork,然后子进程可以对任何对象执行它想要的任何操作。它不会影响父级。下面是一个例子:

    我写了两个插件,它们从调用者那里得到一个包含相关信息的对象,打印一条消息,然后修改它们得到的内容——但这不会影响原始副本。这是第一个:

    import yapsy.IPlugin
    
    class SayHi( yapsy.IPlugin.IPlugin ):
        def doSomething( self, infoForPlugins ):
            print( "plugin SayHi got: %s" % infoForPlugins[ 'SayHi' ] )
            old = infoForPlugins[ 'SayHi' ]
            infoForPlugins[ 'SayHi' ] = 'want to say "hello" instead of "%s"' % old
            print( "plugin SayHi changed info into: %s" % infoForPlugins[ 'SayHi' ] )
    
    这是第二个,几乎相同的:

    import yapsy.IPlugin
    
    class SayBye( yapsy.IPlugin.IPlugin ):
        def doSomething( self, infoForPlugins ):
            print( "plugin SayBye got: %s" % infoForPlugins[ 'SayBye' ] )
            old = infoForPlugins[ 'SayBye' ]
            infoForPlugins[ 'SayBye' ] = "I don't like saying %s!!!" % old
            print( "plugin SayBye changed info into: %s" % infoForPlugins[ 'SayBye' ] )
    
    下面是cron-thingy代码,以及一个允许您动态修改信息的服务器(我在本例中使用UDP使其最小化,但您可以使用任何您想要的机制)


    与其分叉流程,不如使用代理对象“分叉”单例


    换句话说,当将对单例的引用交给插件时,用一个代理对象包装该引用,该代理对象截取所有属性get,以返回其他单例的代理,并截取所有属性集,以防止实际单例的变异。

    这与(2)的想法类似,可能会起作用,但是这个解决方案必须是多平台的,并且os.fork()仅限于unix。很抱歉,我忘了提到这个问题。没有考虑多平台的角度:(为什么不拥有一个大字典或其他数据存储(类似于我的infoForPlugins),为每个插件深度复制它,并给插件一个副本?插件可以做它喜欢的复制,下一个不需要
    import yapsy.PluginManager
    import os
    import logging
    import time
    import threading
    import socket
    
    logging.basicConfig( level = logging.DEBUG )
    
    class _UDPServer( threading.Thread ):
        def __init__( self, infoForPlugins ):
            threading.Thread.__init__( self )
            self._infoForPlugins = infoForPlugins
            self.daemon = True
            self._sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
            self._sock.bind( ( '', 2222 ) )
    
        def run( self ):
            while True:
                packet = self._sock.recv( 4096 )
                key, value = packet.split( ':' )
                self._infoForPlugins[ key ] = value
    
    class CronThingy( object ):
        def __init__( self ):
            self._infoForPlugins = { 'SayHi': 'hi there', 'SayBye': 'bye bye' }
            self._pluginManager = yapsy.PluginManager.PluginManager()
            self._pluginManager.setPluginPlaces( [ 'plugins' ] )
            self._pluginManager.collectPlugins()
            _UDPServer( self._infoForPlugins ).start()
    
        def go( self ):
            while True:
                logging.info( 'info before run: %s' % self._infoForPlugins )
                self._runPlugins()
                time.sleep( 1 )
                logging.info( 'info after run: %s' % self._infoForPlugins )
    
        def _runPlugins( self ):
            for plugin in self._pluginManager.getAllPlugins():
                if os.fork() == 0:
                    plugin.plugin_object.doSomething( self._infoForPlugins )
                    quit()
    
    if __name__ == '__main__':
        CronThingy().go()