Python 将函数与运行时隔离
我不知道该怎么称呼,如果有人能想出更好的标题,让我知道,我会重新命名这个问题 这不是现实生活中的例子,但若我得到了这种情况的解决方案,我将在更大的项目中使用它。假设我必须像下面描述的那样做,我不能改变想法,我需要找到适合它的解决方案。目前,我不允许展示任何原始项目的细节 创意: 所以,假设我正在基于YAPSY插件创建类似cron的东西。我希望将插件存储在某个目录中,有时我的守护进程会收集该目录中的所有插件,调用它们的方法,然后再休眠一段时间。这些插件应该能够访问一些单例,这将存储一些数据,例如插件使用的URL等。我也有TCP服务器在同一进程中运行,这将修改这个单例,所以我可以自定义运行时的行为。TCP服务器应该具有对singleton的读/写访问权限,插件应该具有只读访问权限。可能有许多单例(我的意思是,许多类作为单例运行,而不是一个类的多个实例,duh)可供插件读取并可由TCP服务器修改。在调用插件方法之前,我们会生成一些值来调用它们,并且只能在它们所在的过程中生成它们 问题: 如何授予插件的只读访问权限?我想要完全隔离,这样如果Python 将函数与运行时隔离,python,plugins,isolation,Python,Plugins,Isolation,我不知道该怎么称呼,如果有人能想出更好的标题,让我知道,我会重新命名这个问题 这不是现实生活中的例子,但若我得到了这种情况的解决方案,我将在更大的项目中使用它。假设我必须像下面描述的那样做,我不能改变想法,我需要找到适合它的解决方案。目前,我不允许展示任何原始项目的细节 创意: 所以,假设我正在基于YAPSY插件创建类似cron的东西。我希望将插件存储在某个目录中,有时我的守护进程会收集该目录中的所有插件,调用它们的方法,然后再休眠一段时间。这些插件应该能够访问一些单例,这将存储一些数据,例如插
singleton
有字段x
,这是对象,那么singleton.x
(比如singleton.x.y
)的字段对于插件也应该是只读的。只读意味着插件应该能够修改这些字段,但它不会对运行时的其余部分产生任何影响,所以当插件方法返回时,singleton(及其字段,及其字段等)应该与运行插件方法之前相同,所以它不是真正的只读。此外,插件可能以并发方式运行,并释放GIL一段时间(它们可能有IO操作,或者只使用time.sleep()
--编辑--
解决方案必须是多平台的,并且至少可以在Linux、Windows和MacOS上运行
--/编辑--
方法:
globals()
和locals()
包装成一些dict
s,这样做的技巧类似于第(1)点(恢复原始值),或者深度复制所有全局和局部变量,并使用exec
,使用插件代码方法(不是字符串,我知道这是不安全的)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()