Как обмениваться переменными между скриптами в 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

(второй получает ошибку атрибута, правильно).

Есть ли способ выполнить это, то есть поделиться переменной между двумя скриптами?

Ответы

Ответ 1

Надеюсь, что это хорошо, чтобы записать мои заметки об этой проблеме.

Прежде всего, я очень ценю пример в OP, потому что именно там я и начал - хотя это заставило меня думать, что shared - это какой-то встроенный модуль Python, пока я не нашел полный пример в [Tutor] Глобальные переменные между модулями?.

Однако, когда я искал "совместное использование переменных между скриптами" (или процессов) - помимо случая, когда Python script должен использовать переменные, определенные в других исходных файлах Python (но не обязательно запущенные процессы) - я в основном споткнулся в двух других случаях использования:

  • A script разворачивается в несколько дочерних процессов, которые затем запускаются параллельно (возможно, на нескольких процессорах) на одном ПК
  • A script генерирует несколько других дочерних процессов, которые затем запускаются параллельно (возможно, на нескольких процессорах) на одном ПК

Таким образом, большинство обращений относительно "общих переменных" и "межпроцессного общения" (IPC) обсуждают такие случаи, как эти два; однако в обоих случаях можно наблюдать "родителя", к которому "дети" обычно имеют ссылку.

В то же время я заинтересован в нескольких вызовах одного и того же script, запускался независимо и обменивался данными между ними (как в Python: как разделить объект экземпляр через несколько вызовов script) в режиме singleton/single instance. Эта проблема на самом деле не устранена вышеупомянутыми двумя случаями - вместо этого она существенно сводится к примеру в OP (обмен переменными через два сценария).

Теперь, имея дело с этой проблемой в Perl, есть IPC::Shareable; который "позволяет привязать переменную к общей памяти", используя "целое число или 4 символьную строку [1], которая служит общим идентификатором для данных в пространстве процесса". Таким образом, нет временных файлов, а также сетевых настроек, которые я считаю отличными для моего варианта использования; поэтому я искал то же самое в Python.

Однако, как принятый ответ от @Drewfer отмечает: "Вы не сможете делать то, что хотите, не сохраняя информацию где-то внешнюю по отношению к двум экземпляры переводчика"; или другими словами: либо вы должны использовать настройку сети/сокета, либо вам нужно использовать временные файлы (ergo, нет общей RAM для "полностью отдельных сеансов python" ).

Теперь даже с этими соображениями трудно найти рабочие примеры (кроме pickle) - также в документах для mmap и multiprocessing. Мне удалось найти другие примеры, которые также описывают некоторые подводные камни, о которых не говорится в документах:

Благодаря этим примерам я привел пример, который по существу делает то же самое, что и пример mmap, с подходами из примера "синхронизировать python dict" - используя BaseManager (через manager.start() через файл адрес пути) с общим списком; как для чтения, так и для чтения и записи на сервере и клиенте (вставка ниже). Обратите внимание:

  • Менеджеры
  • multiprocessing могут запускаться либо через manager.start(), либо server.serve_forever()
    • serve_forever() locks - start() не
    • В multiprocessing есть средство автозапуска: он отлично работает с процессами start() ed, но, похоже, игнорирует те, что serve_forever()
  • Спецификация адреса в multiprocessing может быть IP (сокет) или временным файлом (возможно, с каналом?); в multiprocessing docs:
    • В большинстве примеров используется multiprocessing.Manager() - это просто функция (не экземпляр класса), которая возвращает SyncManager, который является специальным подклассом BaseManager; и использует start() - но не для IPC между независимо запущенными скриптами; здесь используется путь к файлу
    • Несколько других примеров serve_forever() подход для IPC между сценариями с независимым запуском; здесь используется IP/адрес сокета
    • Если адрес не указан, то автоматически используется путь к временному файлу (см. 16.6.2.12. Регистрация для примера того, как см. это)

В дополнение ко всем ловушкам в записи "synchronize a python dict" в случае списка есть дополнительные. Это сообщение отмечает:

Все манипуляции с dict должны выполняться с помощью методов, а не диктовых назначений (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 (для сокета); Я думаю, это радует меня, так как мне не нужно беспокоиться о сетевых портах, а также о временных файлах как таковых :)

Другие связанные вопросы:

Ответ 2

Вы не сможете делать то, что хотите, не сохраняя информацию где-то внешнюю по отношению к двум экземплярам интерпретатора.
Если это просто простые переменные, которые вы хотите, вы можете легко сбрасывать питон python в файл с помощью модуля pickle в script one, а затем повторно загружать его в script два. Пример:

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

Ответ 3

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

Ответ 4

То, что вы пытаетесь сделать здесь (сохранение общего состояния в модуле Python над отдельными интерпретаторами python), не будет работать.

Значение в модуле может быть обновлено одним модулем, а затем прочитано другим модулем, но это должно быть в пределах одного интерпретатора Python. То, что вы, кажется, делаете здесь, на самом деле своего рода межпроцессное общение; это может быть достигнуто через сокетную связь между двумя процессами, но это значительно менее тривиально, чем то, что вы ожидаете иметь здесь.

Ответ 5

Вы можете использовать относительный простой файл mmap. Вы можете использовать shared.py для хранения общих констант. Следующий код будет работать с разными интерпретаторами Python\scripts\process

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

Ответ 6

Вам нужно сохранить переменную в каком-то постоянном файле. Для этого есть несколько модулей, в зависимости от вашей конкретной потребности.

Модуль pickle и cPickle может сохранять и загружать большинство объектов python в файл.

Модуль полки может хранить объекты python в словаре-подобной структуре (используя рассол за кулисами).

Модули dbm/bsddb/dbhash/gdm могут хранить строковые переменные в словаре-подобной структуре.

Модуль sqlite3 может хранить данные в легкой базе данных SQL.

Самая большая проблема с большинством из них в том, что они не синхронизируются в разных процессах - если один процесс считывает значение, а другой записывает в хранилище данных, тогда вы можете получить неправильное повреждение данных или данных. Чтобы обойти это, вам нужно будет написать свой собственный механизм блокировки файлов или использовать полномасштабную базу данных.

Ответ 7

Я бы посоветовал вам использовать модуль multiprocessing. Вы не можете запускать два сценария из командной строки, но вы можете иметь два отдельных процесса, которые легко могут разговаривать друг с другом.

Из примеров документа:

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

Используйте текстовые файлы или переменные среды. Поскольку эти два варианта выполняются отдельно, вы не можете делать то, что вы пытаетесь сделать.

Ответ 9

В вашем примере первый script запускается до завершения, а затем запускается второй script. Это означает, что вам нужно какое-то постоянное состояние. Другие ответы предложили использовать текстовые файлы или Python pickle. Лично я ленив, и я бы не использовал текстовый файл, когда мог использовать pickle; почему я должен писать парсер для анализа собственного формата текстового файла?

Вместо pickle вы также можете использовать модуль json для хранения его как JSON. Это может быть предпочтительнее, если вы хотите поделиться данными с программами, отличными от Python, поскольку JSON - простой и общий стандарт. Если ваш Python не имеет json, получите simplejson.

Если ваши потребности превышают pickle или json - скажем, вы действительно хотите, чтобы одновременно выполнялись две программы Python и обновляли постоянные переменные состояния в реальном времени - я предлагаю вам использовать SQLite. Используйте ORM для абстрагирования базы данных, и это очень просто. Для SQLite и Python я рекомендую Autumn ORM.

Ответ 10

Использование 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

Out:

1
2
3
4
5
...

Зависимости:

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

Ответ 11

Если вы хотите читать и изменять общие данные между двумя сценариями, которые выполняются отдельно, хорошим решением было бы воспользоваться модулем многопроцессорной обработки 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), чтобы избежать блокировки при получении данных. Таким образом, нет необходимости сначала проверять, пуста ли очередь. Это невозможно, если вы используете pipes.