如何在 python 中跨脚本共享变量?
- 2025-04-10 09:46:00
- admin 原创
- 18
问题描述:
以下不起作用
一.py
import shared
shared.value = 'Hello'
raw_input('A cheap way to keep process alive..')
二.py
import shared
print shared.value
在两个命令行上运行:
>>python one.py
>>python two.py
(第二个出现属性错误,这是正确的)。
有没有办法实现这一点,即在两个脚本之间共享一个变量?
解决方案 1:
希望我可以在这里记下我对这个问题的笔记。
首先,我非常欣赏 OP 中的例子,因为这也是我的起点 —— 尽管它让我以为shared
是一些内置的 Python 模块,直到我在[Tutor] 模块之间的全局变量??中找到一个完整的例子。
然而,当我寻找“在脚本(或进程)之间共享变量”时 - 除了 Python 脚本需要使用其他 Python 源文件中定义的变量(但不一定是正在运行的进程)的情况之外 - 我主要偶然发现了另外两个用例:
脚本将自身分叉为多个子进程,然后在同一台 PC 上并行运行(可能在多个处理器上)
一个脚本会生成多个其他子进程,然后这些子进程在同一台 PC 上并行运行(可能在多个处理器上)
因此,大多数关于“共享变量”和“进程间通信”(IPC)的文章都讨论了类似这两种情况;然而,在这两种情况下,我们都可以观察到“父级”,而“子级”通常会引用它。
然而,我感兴趣的是运行同一脚本的多个调用,独立运行,并在单例/单实例模式下在它们之间共享数据(如Python:如何在脚本的多个调用之间共享对象实例)。 上述两种情况并没有真正解决这种问题 - 相反,它本质上归结为 OP 中的示例(在两个脚本之间共享变量)。
现在,在 Perl 中处理这个问题时,可以使用IPC::Shareable;它“允许您将变量绑定到共享内存”,使用“整数或 4 个字符串[1] 作为跨进程空间数据的公共标识符”。因此,没有临时文件,也没有网络设置 - 我发现这对我的用例非常有用;所以我在 Python 中寻找同样的东西。
但是,正如@Drewfer 所接受的答案所指出的:“如果不将信息存储在解释器的两个实例之外的某个地方,你将无法做你想做的事情”;换句话说:要么你必须使用网络/套接字设置 - 要么你必须使用临时文件(因此,“完全独立的 python 会话”没有共享 RAM)。
现在,即使考虑到这些,也很难找到可行的示例(除了)——在mmap和multiprocessingpickle
的文档中也是如此。我设法找到了一些其他示例——它们也描述了文档中未提及的一些陷阱:
使用
mmap
:在两个不同的脚本中使用 mmap 在进程间共享 Python 数据的工作代码 | schmichael 的博客演示两个脚本如何改变共享值
请注意,这里创建了一个临时文件来存储已保存的数据 -
mmap
只是用于访问此临时文件的特殊接口
使用
multiprocessing
:工作代码位于:
+ Python 多处理 RemoteManager 在 multiprocessing.Process 下-共享的工作示例`SyncManager`(通过) ;服务器写入,客户端读取(共享数据)`manager.start()``Queue`
+ 多处理模块和 pyro 的比较?`BaseManager` - (通过)具有共享自定义类的工作示例`server.serve_forever()`;服务器写入,客户端读取和写入
+ 如何将 python 字典与多处理同步- 这个答案对陷阱有很好的解释,并且是(通过)共享字典`multiprocessing`的一个工作示例;服务器不执行任何操作,客户端读取和写入`SyncManager``manager.start()`
感谢这些示例,我想出了一个例子,它本质上与示例相同mmap
,使用“同步 Python 字典”示例中的方法 - 使用BaseManager
(manager.start()
通过文件路径地址)和共享列表;服务器和客户端都读取和写入(粘贴在下面)。请注意:
multiprocessing
经理可以通过manager.start()
或启动server.serve_forever()
serve_forever()
锁定 -start()
没有有自动记录功能
multiprocessing
:它似乎可以很好地与start()
ed 进程配合使用 - 但似乎忽略了那些serve_forever()
中的地址规范
multiprocessing
可以是 IP(套接字)或临时文件(可能是管道?)路径;在multiprocessing
文档中:
+ 大多数示例使用`multiprocessing.Manager()`- 这只是一个函数(*不是*类实例),它返回一个`SyncManager`,它是的一个特殊子类`BaseManager`;并使用`start()`- 但*不*用于独立运行的脚本之间的 IPC;这里使用了文件路径
+ 还有一些其他示例`serve_forever()`适用于独立运行的脚本之间的 IPC;这里使用 IP/套接字地址
+ 如果未指定地址,则会自动使用临时文件路径(请参阅16.6.2.12. 日志记录以了解如何查看此内容的示例)
除了“同步 Python 字典”文章中提到的所有陷阱之外,在列表的情况下还存在其他陷阱。该文章指出:
对字典的所有操作都必须通过方法来完成,而不是通过字典赋值(syncdict["blast"] = 2 将会因为多处理共享自定义对象的方式而失败)
获取和设置的解决方法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
(对于套接字);我想这让我很高兴,因为我既不必担心网络端口,也不必担心临时文件:)
其他相关问题:
Python:可以在两个独立的进程之间共享内存数据(非常好的解释)
高效的 Python 到 Python IPC
Python:将变量发送到另一个脚本
解决方案 2:
如果不将信息存储在解释器的两个实例之外的某个地方,您将无法执行所需的操作。
如果您只需要简单的变量,则可以轻松地将 Python 字典转储到脚本一中的 pickle 模块文件中,然后在脚本二中重新加载它。示例:
一.py
import pickle
shared = {"Foo":"Bar", "Parrot":"Dead"}
fp = open("shared.pkl","w")
pickle.dump(shared, fp)
二.py
import pickle
fp = open("shared.pkl")
shared = pickle.load(fp)
print shared["Foo"]
解决方案 3:
sudo apt-get install memcached python-memcache
一.py
import memcache
shared = memcache.Client(['127.0.0.1:11211'], debug=0)
shared.set('Value', 'Hello')
二.py
import memcache
shared = memcache.Client(['127.0.0.1:11211'], debug=0)
print shared.get('Value')
解决方案 4:
您在此处尝试执行的操作(通过单独的 Python 解释器将共享状态存储在 Python 模块中)将不起作用。
模块中的值可以由一个模块更新,然后由另一个模块读取,但这必须在同一个 Python 解释器中。您在这里似乎正在做的实际上是一种进程间通信;这可以通过两个进程之间的套接字通信来实现,但它比您期望在这里完成的工作要简单得多。
解决方案 5:
您可以使用相对简单的 mmap 文件。您可以使用 shared.py 来存储公共常量。以下代码将跨不同的 python 解释器 \ 脚本 \ 进程运行
共享.py:
MMAP_SIZE = 16*1024
MMAP_NAME = 'Global\\SHARED_MMAP_NAME'
“Global” 是 Windows 语法中全局名称
one.py:
from shared import MMAP_SIZE,MMAP_NAME
def write_to_mmap():
map_file = mmap.mmap(-1,MMAP_SIZE,tagname=MMAP_NAME,access=mmap.ACCESS_WRITE)
map_file.seek(0)
map_file.write('hello
')
ret = map_file.flush() != 0
if sys.platform.startswith('win'):
assert(ret != 0)
else:
assert(ret == 0)
two.py:
from shared import MMAP_SIZE,MMAP_NAME
def read_from_mmap():
map_file = mmap.mmap(-1,MMAP_SIZE,tagname=MMAP_NAME,access=mmap.ACCESS_READ)
map_file.seek(0)
data = map_file.readline().rstrip('
')
map_file.close()
print data
*此代码是为 Windows 编写的,Linux 可能需要稍作调整
更多信息请访问 - https://docs.python.org/2/library/mmap.html
解决方案 6:
通过以下方式共享动态变量Redis
:
脚本一
from redis import Redis
from time import sleep
cli = Redis('localhost')
shared_var = 1
while True:
cli.set('share_place', shared_var)
shared_var += 1
sleep(1)
在终端中运行script_one (一个进程):
$ python script_one.py
脚本_two.py
from redis import Redis
from time import sleep
cli = Redis('localhost')
while True:
print(int(cli.get('share_place')))
sleep(1)
在另一个终端(另一个进程)中运行script_two :
$ python script_two.py
出去:
1
2
3
4
5
...
依赖项:
$ pip install redis
$ apt-get install redis-server
解决方案 7:
我建议你使用多处理模块。虽然你不能从命令行运行两个脚本,但你可以轻松地让两个独立的进程相互通信。
从文档的例子:
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print q.get() # prints "[42, None, 'hello']"
p.join()
解决方案 8:
您需要将变量存储在某种持久文件中。有多个模块可以执行此操作,具体取决于您的确切需求。
pickle 和 cPickle 模块可以将大多数 python 对象保存和加载到文件中。
shelve 模块可以将 python 对象存储在类似字典的结构中(在后台使用 pickle)。
dbm/bsddb/dbhash/gdm 模块可以将字符串变量存储在类似字典的结构中。
sqlite3模块可以将数据存储在轻量级SQL数据库中。
其中大多数的最大问题是它们无法在不同的进程之间同步 - 如果一个进程读取一个值而另一个进程正在写入数据存储,那么您可能会得到不正确的数据或数据损坏。要解决这个问题,您需要编写自己的文件锁定机制或使用功能齐全的数据库。
解决方案 9:
如果您想读取和修改两个单独运行的脚本之间的共享数据,一个好的解决方案是利用 Python 多处理模块并使用Pipe() 或 Queue() (请参阅此处的区别)。这样您就可以同步脚本并避免并发和全局变量问题(例如,如果两个脚本都想同时修改一个变量会发生什么)。
使用管道/队列的最好的部分是你可以通过它们传递 python 对象。
如果尚未传递数据,还有一些方法可以避免等待数据(queue.empty()和pipeConn.poll())。
请参阅下面使用 Queue() 的示例:
# main.py
from multiprocessing import Process, Queue
from stage1 import Stage1
from stage2 import Stage2
s1= Stage1()
s2= Stage2()
# S1 to S2 communication
queueS1 = Queue() # s1.stage1() writes to queueS1
# S2 to S1 communication
queueS2 = Queue() # s2.stage2() writes to queueS2
# start s2 as another process
s2 = Process(target=s2.stage2, args=(queueS1, queueS2))
s2.daemon = True
s2.start() # Launch the stage2 process
s1.stage1(queueS1, queueS2) # start sending stuff from s1 to s2
s2.join() # wait till s2 daemon finishes
# stage1.py
import time
import random
class Stage1:
def stage1(self, queueS1, queueS2):
print("stage1")
lala = []
lis = [1, 2, 3, 4, 5]
for i in range(len(lis)):
# to avoid unnecessary waiting
if not queueS2.empty():
msg = queueS2.get() # get msg from s2
print("! ! ! stage1 RECEIVED from s2:", msg)
lala = [6, 7, 8] # now that a msg was received, further msgs will be different
time.sleep(1) # work
random.shuffle(lis)
queueS1.put(lis + lala)
queueS1.put('s1 is DONE')
# stage2.py
import time
class Stage2:
def stage2(self, queueS1, queueS2):
print("stage2")
while True:
msg = queueS1.get() # wait till there is a msg from s1
print("- - - stage2 RECEIVED from s1:", msg)
if msg == 's1 is DONE ':
break # ends loop
time.sleep(1) # work
queueS2.put("update lists")
编辑:刚刚发现您可以使用queue.get(False)来避免接收数据时堵塞。这样就不需要先检查队列是否为空。如果您使用管道,这是不可能的。
解决方案 10:
使用文本文件或环境变量。由于两者分开运行,因此您无法真正完成您想要做的事情。
解决方案 11:
在您的示例中,第一个脚本运行完成,然后第二个脚本运行。这意味着您需要某种持久状态。其他答案建议使用文本文件或 Python 的pickle
模块。就我个人而言,我很懒,如果我可以使用,我不会使用文本文件pickle
;为什么我要编写解析器来解析我自己的文本文件格式?
您也pickle
可以使用json
模块将其存储为 JSON。如果您想将数据共享给非 Python 程序,这可能是更好的选择,因为 JSON 是一个简单而通用的标准。如果您的 Python 没有json
,请获取simplejson。
如果您的需求超出了预期pickle
,或者json
说您实际上希望同时执行两个 Python 程序并实时更新持久状态变量,我建议您使用SQLite数据库。使用 ORM 抽象数据库,这非常简单。对于 SQLite 和 Python,我推荐使用Autumn ORM。
解决方案 12:
这种方法对我来说似乎很简单:
类共享类:
def __init__(self):
self.data = {}
def set_data(self, name, value):
self.data[name] = value
def get_data(self, name):
try:
return self.data[name]
except:
return "none"
def reset_data(self):
self.data = {}
共享类 = 共享类()
PS:您可以使用参数名称和值来设置数据,并且可以使用 get_data 方法访问该值,以下是示例:
设置数据
示例 1:
sharedClass.set_data("name","Jon Snow")
示例 2:
sharedClass.set_data("email"," jon@got.com ")\
获取数据
sharedClass.get_data(“电子邮件”)\
要重置整个状态,只需使用
共享类.重置数据()
它是一种从 json 对象(在本例中为 dict)访问数据的方法
希望这有帮助...
解决方案 13:
您可以使用python中的基本from
和import
函数将变量导入two.py
。例如:
from filename import variable
这样就可以从文件中导入变量了。(当然,你应该filename
用替换one.py
,并variable
用你想要共享到 的变量替换two.py
。)
解决方案 14:
您还可以通过将变量设为全局变量来解决此问题
python first.py
class Temp:
def __init__(self):
self.first = None
global var1
var1 = Temp()
var1.first = 1
print(var1.first)
python 第二.py
import first as One
print(One.var1.first)
扫码咨询,免费领取项目管理大礼包!