如何在Python脚本间共享变量?
下面的代码无法正常工作
这是一个文件叫做 one.py
import shared
shared.value = 'Hello'
raw_input('A cheap way to keep process alive..')
这是另一个文件叫做 two.py
import shared
print shared.value
在两个命令行中运行如下:
>>python one.py
>>python two.py
(第二个命令会出现属性错误,这个是对的)。
有没有办法做到这一点,也就是在两个脚本之间共享一个变量呢?
14 个回答
sudo apt-get install memcached python-memcache
这是一个名为 one.py 的文件
import memcache
shared = memcache.Client(['127.0.0.1:11211'], debug=0)
shared.set('Value', 'Hello')
这是另一个名为 two.py 的文件
import memcache
shared = memcache.Client(['127.0.0.1:11211'], debug=0)
print shared.get('Value')
你想要的功能是无法实现的,因为你需要把信息存储在两个解释器实例之外的地方。
如果你只是想保存一些简单的变量,可以使用pickle模块把一个Python字典保存到文件中,然后在第二个脚本中重新加载这个文件。
下面是一个例子:
one.py
import pickle
shared = {"Foo":"Bar", "Parrot":"Dead"}
fp = open("shared.pkl","w")
pickle.dump(shared, fp)
two.py
import pickle
fp = open("shared.pkl")
shared = pickle.load(fp)
print shared["Foo"]
希望在这里记录一下我对这个问题的笔记。
首先,我非常感谢原帖中的例子,因为这也是我开始的地方——虽然一开始我以为 shared
是某个内置的 Python 模块,直到我在[Tutor] 模块间的全局变量??找到了一个完整的例子。
不过,当我寻找“在脚本之间共享变量”(或进程)时——除了 Python 脚本需要使用其他 Python 源文件中定义的变量(但不一定是正在运行的进程)——我主要遇到了另外两种用例:
- 一个脚本将自己分叉成多个子进程,这些子进程在同一台电脑上并行运行(可能在多个处理器上)
- 一个脚本生成多个其他子进程,这些子进程在同一台电脑上并行运行(可能在多个处理器上)
因此,大多数关于“共享变量”和“进程间通信”(IPC)的讨论都是围绕这两种情况;然而,在这两种情况下,都可以观察到一个“父进程”,而“子进程”通常会有一个引用。
然而,我感兴趣的是独立运行同一个脚本的多个实例,并在这些实例之间共享数据(就像Python: 如何在多个脚本调用中共享对象实例中所描述的),以单例/单实例模式运行。这个问题并没有被上述两种情况真正解决——相反,它基本上简化为原帖中的例子(在两个脚本之间共享变量)。
现在,在处理这个问题时,如果使用 Perl,有IPC::Shareable;它“允许你将变量绑定到共享内存”,使用“一个整数或四个字符的字符串[1]作为跨进程空间的数据的共同标识符”。因此,没有临时文件,也没有网络设置——这对我的用例来说非常好;所以我在寻找 Python 中的类似功能。
然而,正如@Drewfer 的接受回答所指出的:“如果不将信息存储在两个解释器实例之外的某个地方,你是无法做到你想要的”;换句话说:你要么必须使用网络/套接字设置,要么必须使用临时文件(因此,对于“完全独立的 Python 会话”没有共享内存)。
即使考虑到这些因素,找到有效的例子还是有点困难(除了 pickle
)——在mmap和multiprocessing的文档中也是如此。我找到了一些其他的例子——这些例子也描述了一些文档中没有提到的陷阱:
- 使用
mmap
:在两个不同脚本中工作的代码,见使用 mmap 在进程间共享 Python 数据 | schmichael 的博客- 演示了两个脚本如何更改共享值
- 注意这里创建了一个临时文件作为保存数据的存储——
mmap
只是访问这个临时文件的特殊接口
- 使用
multiprocessing
:有效代码在:- Python multiprocessing RemoteManager 在 multiprocessing.Process 下 -
SyncManager
(通过manager.start()
)与共享Queue
的有效示例;服务器写入,客户端读取(共享数据) - multiprocessing 模块和 pyro 的比较? -
BaseManager
(通过server.serve_forever()
)与共享自定义类的有效示例;服务器写入,客户端读取和写入 - 如何使用 multiprocessing 同步 Python 字典 - 这个回答很好地解释了
multiprocessing
的陷阱,并且是SyncManager
(通过manager.start()
)与共享字典的有效示例;服务器不做任何事,客户端读取和写入
- Python multiprocessing RemoteManager 在 multiprocessing.Process 下 -
感谢这些例子,我想出了一个例子,它基本上与 mmap
示例做的相同,采用了“同步 Python 字典”示例中的方法——使用 BaseManager
(通过 manager.start()
通过文件路径地址)与共享列表;服务器和客户端都可以读取和写入(见下文)。请注意:
multiprocessing
管理器可以通过manager.start()
或server.serve_forever()
启动serve_forever()
锁定 -start()
不会multiprocessing
中有自动日志记录功能:它似乎在start()
的进程中工作良好,但似乎忽略了那些使用serve_forever()
的进程
multiprocessing
中的地址指定可以是 IP(套接字)或临时文件(可能是管道?)路径;在multiprocessing
文档中:- 大多数示例使用
multiprocessing.Manager()
- 这只是一个函数(不是 类实例化),返回一个SyncManager
,这是BaseManager
的一个特殊子类;并使用start()
- 但 不 用于独立运行脚本之间的 IPC;这里使用的是文件路径 - 少数其他示例使用
serve_forever()
方法进行独立运行脚本之间的 IPC;这里使用的是 IP/套接字地址 - 如果没有指定地址,则会自动使用临时文件路径(见16.6.2.12. 日志记录,了解如何查看这个)
- 大多数示例使用
除了在“同步 Python 字典”帖子中的所有陷阱外,在列表的情况下还有其他陷阱。该帖子指出:
对字典的所有操作必须使用方法,而不是字典赋值(syncdict["blast"] = 2 会因为 multiprocessing 共享自定义对象的方式而失败)
解决 dict['key']
获取和设置的问题,是使用 dict
的公共方法 get
和 update
。问题是,对于 list[index]
并没有这样的公共方法;因此,对于共享列表,我们还必须将 __getitem__
和 __setitem__
方法(这些是 list
的私有方法)注册为 exposed
,这意味着我们还必须重新注册所有 list
的公共方法 :/
好吧,我认为这些是最关键的内容;这就是两个脚本——它们可以在不同的终端中运行(先运行服务器);注意是在 Linux 上使用 Python 2.7 开发的:
a.py
(服务器):
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
syncarr = []
def get_arr():
return syncarr
def main():
# print dir([]) # cannot do `exposed = dir([])`!! manually:
MyListManager.register("syncarr", get_arr, exposed=['__getitem__', '__setitem__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'])
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.start()
# we don't use the same name as `syncarr` here (although we could);
# just to see that `syncarr_tmp` is actually <AutoProxy[syncarr] object>
# so we also have to expose `__str__` method in order to print its list values!
syncarr_tmp = manager.syncarr()
print("syncarr (master):", syncarr, "syncarr_tmp:", syncarr_tmp)
print("syncarr initial:", syncarr_tmp.__str__())
syncarr_tmp.append(140)
syncarr_tmp.append("hello")
print("syncarr set:", str(syncarr_tmp))
raw_input('Now run b.py and press ENTER')
print
print 'Changing [0]'
syncarr_tmp.__setitem__(0, 250)
print 'Changing [1]'
syncarr_tmp.__setitem__(1, "foo")
new_i = raw_input('Enter a new int value for [0]: ')
syncarr_tmp.__setitem__(0, int(new_i))
raw_input("Press any key (NOT Ctrl-C!) to kill server (but kill client first)".center(50, "-"))
manager.shutdown()
if __name__ == '__main__':
main()
b.py
(客户端)
import time
import multiprocessing
import multiprocessing.managers
import logging
logger = multiprocessing.log_to_stderr()
logger.setLevel(logging.INFO)
class MyListManager(multiprocessing.managers.BaseManager):
pass
MyListManager.register("syncarr")
def main():
manager = MyListManager(address=('/tmp/mypipe'), authkey='')
manager.connect()
syncarr = manager.syncarr()
print "arr = %s" % (dir(syncarr))
# note here we need not bother with __str__
# syncarr can be printed as a list without a problem:
print "List at start:", syncarr
print "Changing from client"
syncarr.append(30)
print "List now:", syncarr
o0 = None
o1 = None
while 1:
new_0 = syncarr.__getitem__(0) # syncarr[0]
new_1 = syncarr.__getitem__(1) # syncarr[1]
if o0 != new_0 or o1 != new_1:
print 'o0: %s => %s' % (str(o0), str(new_0))
print 'o1: %s => %s' % (str(o1), str(new_1))
print "List is:", syncarr
print 'Press Ctrl-C to exit'
o0 = new_0
o1 = new_1
time.sleep(1)
if __name__ == '__main__':
main()
最后补充一下,在 Linux 上会创建 /tmp/mypipe
——但大小为 0 字节,属性为 srwxr-xr-x
(用于套接字);我想这让我很高兴,因为我不需要担心网络端口,也不需要担心临时文件 :)
其他相关问题: