如何在python中跨脚本共享变量?

时间:2009-12-01 21:38:54

标签: python variables share

以下不起作用

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

(第二个得到属性错误,这是正确的。)

有没有办法实现这一点,即在两个脚本之间共享一个变量?

12 个答案:

答案 0 :(得分:37)

希望能在这里记下关于这个问题的笔记。

首先,我很欣赏OP中的示例,因为这也是我开始的地方 - 尽管它让我觉得shared是一些内置的Python模块,直到我找到了一个完整的例子在[Tutor] Global Variables between Modules ??

然而,当我寻找"在脚本之间共享变量时#34; (或进程) - 除了Python脚本需要使用其他Python源文件中定义的变量(但不一定是运行进程)的情况 - 我经常偶然发现其他两个用例:

  • 脚本将自身分成多个子进程,然后在同一台PC上并行运行(可能在多个处理器上)
  • 脚本会生成多个其他子进程,然后在同一台PC上并行运行(可能在多个处理器上)

因此,大多数关于"共享变量"和#34;进程间通信" (IPC)讨论这两个案例;然而,在这两种情况下,人们都可以观察到父母"其中的孩子"通常都有参考。

然而,我感兴趣的是在单例/单实例模式下运行多个相同脚本的调用,独立运行以及在这些调用之间共享数据(如Python: how to share an object instance across multiple invocations of a script)。上述两种情况并未真正解决这类问题 - 相反,它本质上简化为OP中的示例(跨两个脚本共享变量)。

现在,在Perl中处理这个问题时,有IPC::Shareable;其中"允许您将变量绑定到共享内存",使用"整数或4字符串[1],用作跨进程空间的数据的公共标识符"。因此,没有临时文件,也没有网络设置 - 我认为这对我的用例很有用;所以我在Python中寻找相同的东西。

然而,正如accepted answer by @Drewfer注意到:" 如果不将信息存储在解释器的两个实例外部的某处,您将无法做您想做的事情&#34 ;;换句话说:要么必须使用网络/套接字设置 - 要么必须使用临时文件(ergo,没有共享RAM用于" 完全独立的python会话")

现在,即使考虑到这些因素,也很难找到工作示例(pickle除外) - 同样在mmapmultiprocessing的文档中也是如此。我设法找到了一些其他的例子 - 这些例子也描述了文档没有提到的一些陷阱:

感谢这些示例,我想出了一个示例,它基本上与mmap示例相同,其中来自" 的方法同步python dict &# 34;示例 - 使用BaseManager(通过manager.start()通过文件路径地址)和共享列表;服务器和客户端读写(粘贴在下面)。请注意:

  • multiprocessing经理可以通过manager.start()server.serve_forever()启动
    • serve_forever()锁定 - start()没有
    • multiprocessing中有自动日志工具:它似乎与start() ed进程一起正常工作 - 但似乎忽略了那些serve_forever()
  • multiprocessing中的地址规范可以是IP(套接字)或临时文件(可能是管道?)路径;在multiprocessing文档中:
    • 大多数示例使用multiprocessing.Manager() - 这只是一个函数( not 类实例化),它返回SyncManager,它是BaseManager的特殊子类;并在独立运行的脚本之间使用start() - 但用于IPC;这里使用了文件路径
    • 在独立运行的脚本之间,很少有其他示例serve_forever()用于IPC;这里使用IP /套接字地址
    • 如果未指定地址,则会自动使用临时文件路径(有关如何查看此内容的示例,请参阅16.6.2.12. Logging

除了" 同步一个python dict "中的所有陷阱。发布,如果是列表,还有其他的。该帖子指出:

  

dict的所有操作都必须使用方法而不是dict赋值(syncdict [" blast"] = 2会因多处理共享自定义对象的方式而失败)

dict['key']获取和设置的变通方法是使用dict公开方法getupdate。问题是没有这样的公共方法作为list[index]的替代方案;因此,对于共享列表,我们还必须将__getitem____setitem__方法(list专用)注册为exposed,这意味着我们还必须重新注册 - 注册list的所有公开方法以及:/

嗯,我认为这些是最关键的事情;这些是两个脚本 - 它们可以在不同的终端中运行(服务器优先);使用Python 2.7在Linux上开发的注释:

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(对于套接字);我想这让我感到高兴,因为我既不必担心网络端口,也不担心临时文件:)

其他相关问题:

答案 1 :(得分:17)

如果不将信息存储在解释器的两个实例外部的某个位置,您将无法执行所需的操作。
如果它只是你想要的简单变量,你可以轻松地将python dict转储到带有pickle模块的文件中,然后在脚本2中重新加载它。 例如:

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"]

答案 2 :(得分:11)

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')

答案 3 :(得分:6)

你在这里尝试做什么(通过单独的python解释器在Python模块中存储共享状态)将不起作用。

模块中的值可以由一个模块更新,然后由另一个模块读取,但这必须在同一个Python解释器中。你在这里做的实际上是一种进程间通信;这可以通过两个进程之间的套接字通信来完成,但它远远不如你期望在这里工作那么简单。

答案 4 :(得分:5)

您可以使用相对简单的mmap文件。 您可以使用shared.py来存储公共常量。以下代码适用于不同的python解释器\ scripts \ processes

shared.py:

MMAP_SIZE = 16*1024 
MMAP_NAME = 'Global\\SHARED_MMAP_NAME'

*&#34; Global&#34;是全局名称的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\n')                                                                 
    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('\n')                                     
    map_file.close()                                                            
    print data                                                                  

*此代码是为windows编写的,linux可能需要很少的调整

更多信息 - https://docs.python.org/2/library/mmap.html

答案 5 :(得分:3)

您需要将变量存储在某种持久性文件中。根据您的具体需要,有几个模块可以执行此操作。

pickle和cPickle模块可以将大多数python对象保存并加载到文件中。

shelve模块可以将python对象存储在类似字典的结构中(在幕后使用pickle)。

dbm / bsddb / dbhash / gdm模块可以将字符串变量存储在类似字典的结构中。

sqlite3模块可以将数据存储在轻量级SQL数据库中。

其中大多数问题的最大问题是它们不会在不同进程之间同步 - 如果一个进程读取值而另一个进程正在写入数据存储区,那么您可能会收到不正确的数据或数据损坏。为了解决这个问题,您需要编写自己的文件锁定机制或使用完整的数据库。

答案 6 :(得分:3)

我建议你使用multiprocessing模块。您不能从命令行运行两个脚本,但是您可以让两个单独的进程轻松地相互通信。

来自doc的例子:

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()

答案 7 :(得分:1)

使用文本文件或环境变量。由于两者分开运行,你无法真正做到你想要做的事情。

答案 8 :(得分:1)

在您的示例中,第一个脚本运行完成,然后运行第二个脚本。这意味着你需要某种持久状态。其他答案建议使用文本文件或Python的pickle模块。就个人而言,我很懒,当我可以使用pickle时,我不会使用文本文件;为什么要编写解析器来解析我自己的文本文件格式?

您也可以使用pickle模块将其存储为JSON,而不是json。如果您想将数据共享到非Python程序,这可能是更好的选择,因为JSON是一个简单而通用的标准。如果您的Python没有json,请获取simplejson

如果您的需求超出picklejson - 假设您实际上希望同时执行两个Python程序并实时更新持久状态变量 - 我建议您使用SQLite数据库。使用ORM将数据库抽象出来,这非常简单。对于SQLite和Python,我建议Autumn ORM

答案 9 :(得分:0)

使用Redis共享动态变量:

script_one.py

from redis import Redis
form time import sleep

cli = Redis('localhost')
shared_var = 0

while True:
   cli.set('share_place', shared_var)
   share_var += 1
   sleep(1)

在终端(进程)中运行 script_one

$ python script_one.py

script_two.py

from time import sleep
from redis import Redis

cli = Redis('localhost')

while True:
    print(int(cli.get('share_place')))
    sleep(1)

在另一个终端(另一个进程)中运行 script_two

$ python script_two.py

出局:

0
1
2
3
4
...

依赖项:

$ pip install redis
$ apt-get install redis-server

答案 10 :(得分:0)

如果您想在两个分别运行的脚本之间读取和修改共享数据,一个好的解决方案是利用python多处理模块并使用Pipe() or a Queue()(请参见区别{{ 3}})。这样,您就可以同步脚本并避免有关并发性和全局变量的问题(例如,如果两个脚本都想同时修改变量,会发生什么情况)。

关于使用管道/队列的最好部分是您可以通过它们传递python对象。

还有一些方法可以避免在尚未传递数据的情况下等待数据(herequeue.empty())。

请参见下面使用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")             

编辑:刚刚发现您可以使用pipeConn.poll()来避免在接收数据时出现阻塞。这样,无需先检查队列是否为空。如果使用管道,则不可能。

答案 11 :(得分:-2)

您还可以通过将变量设置为全局变量来解决此问题

python first.py

class Temp:
    def __init__(self):
        self.first = None

global var1
var1 = Temp()
var1.first = 1
print(var1.first)

python second.py

import first as One
print(One.var1.first)