from plumbum import local
Из объекта local
можно получать питонские объекты, представляющие внешние программы.
ls=local["ls"]
print(ls)
Их можно вызывать.
print(ls())
Они возвращают строки, которые можна присваивать переменным или ещё как-то использовать.
s=ls()
s.split()
Можно вызывать их с аргументами.
print(ls('-l','d1'))
Следующая строчка означает в точности то же самое, что
cat=local['cat']
grep=local['grep']
(модуль plumbum.cmd
использует чёрную магию для переопределения импорта из него).
from plumbum.cmd import cat,grep
Это объект, представляющий внешнюю программу с привязанными аргументами.
ll=ls['-l']
print(ll)
print(ll())
Из таких объектов можно строить цепочки.
chain=ll | grep['ipynb']
print(chain)
Цепочки можно вызывать.
print(chain())
Можно использовать перенаправления ввода-вывода.
!cat newtext.txt
chain=(grep['ab'] < 'newtext.txt') > 'text2.txt'
print(chain)
(скобки здесь обязательны)
chain()
print(cat('text2.txt'))
Если нужно послать текст в stdin
внешней программы, используется оператор <<
.
print((grep['ab'] << 'xxx\nabc\nyyy\n')())
Допустим, в локальной сети есть машина eeepc (192.168.0.105), доступная по ssh
.
ip='192.168.43.95'
from plumbum import SshMachine
eeepc=SshMachine(ip)
Теперь мы можем выполнять на ней команды.
eeepc_ls=eeepc['ls']
print(eeepc_ls)
print(eeepc_ls('rpyc'))
Можно строить цепочки из локальных и удалённых команд.
chain=eeepc_ls['rpyc'] | grep[r'\.py']
print(chain)
chain
print(chain())
eeepc_grep=eeepc['grep']
print((eeepc_grep['ab'] < 'newtext.txt')())
print((eeepc_grep['ab'] << 'xxx\nabc\nyyy\n')())
print((cat['newtext.txt'] | eeepc_grep['ab'])())
Теперь закроем связь с eeepc.
eeepc.close()
Сеанс работы с eeepc удобно записать в виде
with SshMachine('192.168.0.105') as eeeps:
eeepc['ls']()
# и так далее
Remote Python Call http://rpyc.sourceforge.net/ - очень простой пакет для организации распределённых вычислений. Он работает на любой платформе, где есть питон, так что Вы можете объединить в вычислительный кластер всё, что подвернётся под руку - Linux, Windows, Mac, хоть свой телефон.
На всех компьютерах, которые мы хотим использовать, запускаются rpyc
серверы. Клиент обращается к ним и поручает выполнить какую-нибудь работу. Есть два типа серверов - классический (или slave-сервер) и современный. Классический сервер может (по поручению клиента) делать всё, что может делать интерпретатор питон (от имени того пользователя, который запустил сервер). Он не производит аутентификацию клиента. Поэтому его можно запускать в защищённой локальной сети (возможно виртуальной), или он должен принимать соединения только с localhost
(а другие машины получают к нему доступ через ssh
туннели. В отличие от классического сервера (который поставляется в пакете RPyC
), современный сервер должен быть написан пользователем под конкретную задачу. Он предоставляет клиентам некоторый набор сервисов; клиенты могут вызывать их. Если эти сервисы безопасны, такой сервер может работать и в открытом интернете.
Есть более простой способ использования RPyC
, который даже не требует, чтобы этот пакет был установлен на всех машинах - достаточно иметь его на клиентской (локальной) машине. Установим ssh
связь с удалённой машиной.
eeepc=SshMachine(ip)
Функция DeployedServer
передаёт на eeepc
нужные исходные тексты, запускает там классический rpyc
сервер, принимающий соединения только с локальной машины, и создаёт ssh
туннель на eeepc
.
import rpyc
from rpyc.utils.zerodeploy import DeployedServer
server=DeployedServer(eeepc)
Теперь мы можем установить связь с этим сервером.
eee=server.classic_connect()
Теперь я могу выполнять различные действия на eeepc
:
eee.execute('n=2')
eee.eval('n+1')
Я могу использовать встроенные функции питона.
eee_file=eee.builtins.open('/home/grozin/rpyc/mymodule.py')
В отличие от простых неизменяемых объектов (чисел, строк и т.д.), которые передаются между машинами по значению, изменяемые объекты передаются по ссылке. То есть на eeepc
создался файловый объект; на локальной машине создалась сетевая ссылка на него - прокси-объект eee_file
. Мы можем производить над ним любые действия, доступные для файлового объекта. Все они переадресутся объекту на eeepc
.
print(eee_file.read())
eee_file.close()
Этот прокси-объект можно подставить в любую программу, ожидающую иметь файловый объект. По принципу утиной типизации этот объект - файл.
Я могу использовать функции и прочие объекты из библиотечных модулей питона на eeepc
:
eee_path=eee.modules.sys.path
print(eee_path)
eee_path.append('/home/grozin/rpyc')
eee_path
- это прокси-объект для sys.path
на eeepc
; любые изменения этого объекта сразу передаются туда. Теперь, расширив path
, я могу использовать файл из этой
директории на eeepc
:
eee_object=eee.modules.mymodule.MyClass(0)
eee_object.f(3)
eee_object
- это прокси-объект (сетевая ссылка) для объекта класса MyClass
на машине eeepc
. Его метод f
прибавляет 1 к аргументу; чтобы у нас была возможность моделировать длительные вычисления, он это делает за t
секунд, где t
- атрибут этого объекта.
eee_object.t=2
eee_object.f(4)
Теперь нам пришлось ждать 2 секунды.
Можно передавать удалённым функциям в качестве параметров любые объекты, в частности, локальные функции. Определим
def loc(n):
print('loc',n)
return n+1
Тогда
list(eee.builtins.map(loc,[1,2,3]))
То есть функция map на eeepc
на каждом шаге вызывает функцию loc
на локальной машине (callback).
Всё, что мы до сих пор обсуждали, несомненно, красиво - разные объекты могут жить на разных машинах, и единая программа работает с ними, не замечая этого. Но все эти операции синхронные - одна машина просит другую что-то сделать и ждёт, когда та вернёт ей результат. Для организации распределённых вычислений нужны асинхронные операции:
eee_object.t=10
async_f=rpyc.async(eee_object.f)
res=async_f(1)
res.ready
res.ready
res.ready
res.value
Это уже лучше. Клиент может время от времени спрашивать, готов ли результат, и когда он будет готов, забрать его. Если запросить res.value
когда результат ещё не готов, то клиент блокируется до момента, когда он будет готов:
res=async_f(2)
res.value
(после res.value
10 секунд ожидания, потом появляется ответ).
Но ещё лучше определить callback-функцию, которая будет вызвана на локальной машине, когда результат будет готов:
def callback(res):
print(res.value)
Эта функция может быть вызвана только в отдельном thread-е:
thr=rpyc.BgServingThread(eee)
res=async_f(3)
res.add_callback(callback)
n=1
n
n+1
Это печать из функции callback из другого thread-а. Теперь это thread можно и остановить.
thr.stop()
Например, на клиентской машине может работать графический пользовательский интерфейс (на питоне легко написать такой интерфейс, причём он будет работать на любой платформе - Linux, Windows, Mac - без малейших изменений в программе). Эта клиентская программа обращается к нескольким мощным серверам для проведения длинных вычислений, и регистрирует callback функции, которые, наприер, добавляют очередную точку на график.
Наконец, закроем связь с машиной eeepc:
eee.close()
Сеанс связи с rpyc
сервером удобно записывать как
with server.classic_connection() as eee:
eee.execute('n=2')
# и так далее
Наконец, закроем ssh
связь с eeepc.
eeepc.close()