web-dev-qa-db-ja.com

Pythonでスクリプト間で変数を共有する方法は?

以下は動作しません

one.py

import shared
shared.value = 'Hello'
raw_input('A cheap way to keep process alive..')

two.py

import shared
print shared.value

次の2つのコマンドラインで実行します。

>>python one.py
>>python two.py

(2番目のものは属性エラーを受け取ります)。

これを実現する方法、つまり2つのスクリプト間で変数を共有する方法はありますか?

22
azarias

この問題についての私のメモをここに書き留めても大丈夫だと思います。

まず最初に、私はOPの例にとても感謝しています。それは私が始めた場所でもあります。sharedは組み込みのPythonモジュールであると思うまで、完全な例は [Tutor]モジュール間のグローバル変数?? です。

ただし、「スクリプト間で変数を共有する」(またはプロセス)を探したとき-Pythonスクリプトが他のPythonで定義された変数を使用する必要がある場合ソースファイル(ただし、必ずしも実行中のプロセスではありません)-主に他の2つの使用例に遭遇しました。

  • スクリプトは自身を複数の子プロセスに分岐し、同じPCで(おそらく複数のプロセッサで)並列に実行されます
  • スクリプトは他の複数の子プロセスを生成し、同じPCで(おそらく複数のプロセッサで)並列に実行されます

そのため、「シェア変数」と「プロセス間通信」(IPC)に関するほとんどのヒットは、これら2つのようなケースについて説明しています。ただし、どちらの場合でも、「子供」が通常参照する「親」を観察できます。

ただし、私が興味を持っているのは、同じスクリプトの複数の呼び出しを実行し、独立して実行し、それらの間でデータを共有することです(- Python:スクリプトの複数の呼び出しでオブジェクトインスタンスを共有する方法 ) 、シングルトン/シングルインスタンスモード。この種の問題は、上記の2つのケースでは実際には対処されません。代わりに、本質的にOPの例(2つのスクリプト間で変数を共有する)に減少します。

さて、Perlでこの問題を処理する場合、 IPC :: Shareable ;があります。 「変数を共有メモリに関連付けることができます」、「プロセス空間全体のデータの共通識別子として機能する整数または4文字の文字列[1]」を使用します。したがって、一時ファイルやネットワーク設定はありません。これは、自分の使用例に最適です。だから私はPythonで同じものを探していました。

ただし、 @ Drewferによる承認済みの回答 の注記:「の2つのインスタンスの外部のどこかに情報を保存せずに必要なことを実行することはできません。通訳";または言い換えると、ネットワーク/ソケットの設定を使用する必要があります-または一時ファイルを使用する必要があります(エルゴ、共有なしRAM for "完全に分離= pythonセッション ")。

さて、これらの考慮事項があっても、実用的な例(pickleを除く)を見つけるのは少し難しいです- mmap および multiprocessing のドキュメントでも。私はなんとか他の例を見つけることができました-ドキュメントが言及していないいくつかの落とし穴も説明しています:

これらの例のおかげで、私は本質的にmmapの例と同じように、 "synchronize a python dict "の例-BaseManagerを使用して(manager.start()を介してファイルパスアドレスを介して)共有リストで;サーバーとクライアントの両方が読み取りと書き込み(下記に貼り付け)。

  • multiprocessingマネージャーは、manager.start()またはserver.serve_forever() から起動できます。
    • serve_forever()ロック-start()はロックしません
    • multiprocessingには自動ロギング機能があります。start() edプロセスで正常に動作するようですが、serve_forever()のプロセスは無視されているようです
  • multiprocessingのアドレス指定は、IP(ソケット)または一時ファイル(おそらくパイプ?)パスにすることができます。 multiprocessing docs:
    • ほとんどの例ではmultiprocessing.Manager()を使用しています。これは単なる関数(notクラスのインスタンス化)であり、SyncManagerを返します。これは、BaseManagerの特別なサブクラスです。 start()を使用しますが、IPCの場合、notは独立して実行されたスクリプト間で使用されます。ここでは、ファイルパスが使用されます。
    • IPC=独立して実行されたスクリプト間でのその他の例serve_forever()アプローチ;ここではIP /ソケットアドレスが使用されます
    • アドレスが指定されていない場合、一時ファイルのパスが自動的に使用されます(これを確認する方法の例については 16.6.2.12。Logging を参照)

synchronize a python dict」投稿のすべての落とし穴に加えて、リストの場合には追加の落とし穴があります。その投稿ノート:

Dictのすべての操作は、dictの割り当てではなく、メソッドを使用して行う必要があります(syncdict ["blast"] = 2は、マルチプロセッシングがカスタムオブジェクトを共有する方法が原因で、惨めに失敗します)。

dict['key']の取得と設定の回避策は、dictパブリックメソッドgetおよびupdateを使用することです。問題は、list[index]に代わるものとしてそのようなパブリックメソッドがないことです。したがって、共有リストの場合、さらに__getitem__および__setitem__メソッド(listに対してプライベート)をexposedとして登録する必要があります。つまり、listのすべてのパブリックメソッドを再登録する必要があります同様に:/

まあ、それらは最も重要なものだったと思います。これらは2つのスクリプトです。これらは別々の端末で実行することができます(サーバーが先)。 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(ソケット用)を持っています。ネットワークポートや一時ファイルなどについて心配する必要がないので、これは私を幸せにしていると思います:)

その他の関連質問:

44
sdaau

インタプリタの2つのインスタンスの外部のどこかに情報を保存しないと、思い通りのことができなくなります。
必要な単純な変数の場合は、スクリプト1で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"]
20
Drewfer

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

ここでやろうとしていること(Pythonモジュールを別のpythonインタプリタに共有する)に共有状態を保存する)は機能しません。

モジュール内の値は、あるモジュールによって更新されてから、別のモジュールによって読み取られますが、これは同じPythonインタープリター内にある必要があります。ここで実行しているように見えるのは、実際には一種のインタープロセスです。通信。これは、2つのプロセス間のソケット通信を介して実現できますが、ここでの作業に期待するものよりもはるかに簡単です。

6
Gabriel Reid

比較的単純なmmapファイルを使用できます。 shared.pyを使用して、共通の定数を保存できます。次のコードは、さまざまなpythonインタプリタ\スクリプト\プロセス

shared.py:

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

*「グローバル」は、グローバル名の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
AmitE

multiprocessing モジュールを使用することをお勧めします。コマンドラインから2つのスクリプトを実行することはできませんが、2つの別々のプロセスが互いに簡単に対話することができます。

ドキュメントの例から:

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

変数をある種の永続ファイルに保存する必要があります。正確なニーズに応じて、これを行うモジュールがいくつかあります。

PickleおよびcPickleモジュールは、ほとんどのpythonオブジェクトをファイルに保存およびロードできます。

Shelveモジュールは、pythonオブジェクトをディクショナリのような構造で格納できます(舞台裏でピクルスを使用)。

Dbm/bsddb/dbhash/gdmモジュールは、文字列変数を辞書のような構造で格納できます。

Sqlite3モジュールは、軽量のSQLデータベースにデータを格納できます。

これらのほとんどの最大の問題は、異なるプロセス間で同期されないことです。あるプロセスが値を読み取っているときに別のプロセスがデータストアに書き込んでいると、不正なデータまたはデータの破損が発生する可能性があります。これを回避するには、独自のファイルロックメカニズムを作成するか、本格的なデータベースを使用する必要があります。

3
Dave Kirby

テキストファイルまたは環境変数を使用します。 2つは別々に実行されるため、実行しようとしていることを実際に実行することはできません。

1
David Brunelle

この例では、最初のスクリプトが最後まで実行され、次に2番目のスクリプトが実行されます。つまり、何らかの永続的な状態が必要です。他の回答では、テキストファイルまたはPythonのpickleモジュールの使用を提案しています。個人的に私は怠惰で、pickleを使用できるときはテキストファイルを使用しません。独自のテキストファイル形式を解析するパーサーを作成する必要があるのはなぜですか?

pickleの代わりに、jsonモジュールを使用してJSONとして保存することもできます。 JSONはシンプルで一般的な標準であるため、データをPython以外のプログラムと共有する場合は、これが望ましい場合があります。 Pythonにjsonがない場合は、 simplejson を取得します。

ニーズがpickleまたはjsonを超える場合-実際に2つのPythonプログラムを同時に実行し、永続的な状態変数を更新するリアルタイム- SQLite データベースを使用することをお勧めします。ORMを使用してデータベースを抽象化すると、非常に簡単です。SQLiteとPythonの場合は Autumn ORM をお勧めします。

1
steveha

Redisを使用して動的変数を共有する:

script_one.py

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

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

でる:

1
2
3
4
5
...

依存関係:

$ pip install redis
$ apt-get install redis-server
0
Benyamin Jafari

変数をグローバルにすることでこの問題を解決することもできます

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

別々に実行する2つのスクリプト間で共有データの読み取りと変更を実行したい場合は、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")             

[〜#〜] edit [〜#〜]queue.get(False) を使用してデータ受信時のブロックを回避できることがわかりました。この方法では、キューが空かどうかを最初に確認する必要はありません。パイプを使用する場合、これは不可能です。

0
onofricamila