Підпроцеси, asyncio.

Основи Python и Django. -> Підпроцеси.

Підпроцеси. Мультизадачність.

Мультизадачність може бути забезпечена 3 техніками.

  1. Багатопоточність.

  2. Багатопроцесовість.

  3. Асинхронні дзвінки.

Щоразу, коли ви запускаєте програму Python з командного рядка, наприклад:

 python myprogramm.py

Операційна система створює новий процес, у якому відбувається робота вашої програми.

Будь-яка програма оперує трьома потоками: потік введення (input), потік виведення (output) та потік помилок (error).

Зазвичай input – клавіатура output та error – вікно терміналу.

Іноді в програмі потрібно запустити іншу програму так само, як з командного рядка, надавши йому окремий підпроцес, працюючий незалежно від основної програми.

Для цього можна використовувати вбудований модуль subprocess

Сигнатура його виклику така:

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

Як бачимо, при виклик ми можемо визначити поведінку з трьома потоками.

Першим параметром ми передаємо команду, яку хочемо виконати.

Це може бути список рядків, з яких буде сформовано команду. Зазвичай першим вказують програму, що виконується, а потім аргументи.

Останнім параметром ми вказуємо, чи буде наша команда виконуватись у контексті командного інтерпретатора shell.

Якщо він встановлений у True, то Python перед запуском вашої команди запустить інтерпретатор shell і в ньому вже виконає команду.

За такого підходу вам стануть доступними всі команди shell та змінні оточення, що робить запуск не зовсім безпечним.

Приклади виклику:

from subprocess import call

subprocess.call(['ls','l'],shell=True)

subprocess.call(['pwd'],shell=True)

Приклад уразливості:

filename = input("What file would you like to display?\n")
What file would you like to display?
on_existent; rm -rf / 
call("cat " + filename, shell=True) # Це може погано закінчитися!

Приклад перехоплення виведення та збереження у файл.

from subprocess import call
file = open('output.txt','w')
call('ls',stdout=file,shell=True)
print 'done!'

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

Виконує команду, описану args. Очікує завершення команди, а потім завершується, якщо код повернення 0, або піднімає виняток CalledProcessError, об’єкт якого повертає код завершення атрибутом returncode.

subprocess.check_output(args, *, input=None, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)

Дозволяє забрати виведення команди у рядок.

c = subprocess.check_output(['pwd'],shell=True)

Піднімає виняток CalledProcessError, якщо код повернення ненульовий.

Бібліотека subprocess оперує системними викликами os.fork() і os.execve() і це означає, що кожен процес отримає свій адресний простір і копії всіх даних батька.

Тому для забезпечення передачі даних між процесами необхідно використовувати додаткові механізми, такі як сокети або канали.

Popen клас.

Служить до створення процесу управління його потоком виконання.

>>>p = args = ['ls']
>>> p = subprocess.Popen(args)
>>> chdir_ex.py  os_ex.py  out_in_file.py  output.txt
p
<subprocess.Popen object at 0x7f5248d8f290>

а допомогою цього конструктора ми теж запускаємо команду в новому процесі, не блокуючи виконання програми, що викликає.

Нам повертається об’єкт і ми можемо взаємодіяти з цим об’єктом у момент виконання у ньому нашої команди.

На відміну від call, Popen не блокує виконання програми, що викликає, і вона продовжує своє виконання.

Така робота, коли одна програма породжує іншу не перериваючись, називається асинхронної.

Методи класу Popen.

Popen.poll() - якщо процес завершив роботу - поверне код повернення, інакше None.

Popen.wait(timeout=None) - очікує завершення роботи процесу та повертає код повернення. Якщо протягом timeout процес не завершився, підніметься виняток TimeoutExpired (який можна перехопити, після чого зробити ще раз wait).

Popen.communicate(input=None, timeout=None) - взаємодіє з процесом: посилає дані, що містяться в input в stdin процесу, чекає завершення роботи процесу, повертає кортеж даних потоку виведення та помилок. При цьому в Popen необхідно встановити значення PIPE для stdin (якщо ви хочете посилати в stdin), stdout, stderr (якщо ви хочете прочитати висновок дочірнього процесу).

Якщо протягом timeout процес не завершився, підніметься виняток TimeoutExpired (який можна перехопити, після чого зробити ще раз communicate, або вбити дочірній процес).

Popen.send_signal(signal) - посилає сигнал signal.

Popen.terminate() - зупиняє дочірній процес.

Popen.kill() - вбиває дочірній процес.

Multiprocessing

Ця бібліотека використовується для запуску функцій всередині вашої програми.

При цьому стає можливим запуск їх не всередині окремого процесу, а всередині потоку. Що уможливлює обмін даними між потоками т.к. вони запускаються у єдиному адресному просторі процесу.

Основи Python и Django. -> Оператор with - менеджер контекста.

Оператор with – менеджер контексту.

Розглянемо такий приклад коду.

f = open(filename)
try:
    do something
finally:
    f.close()

Спочатку ми відкриваємо файл для читання і в конструкції try здійснюємо з ним операції.

Для того, щоб закрити файл і звільнити ресурс незалежно від того, виникне виняток чи ні ми закриваємо файл блоку finally.

Однак, якщо ми виробляємо це багато разів, то така конструкція може стати стомлюючою.

Більш зручним підходом буде створення бібліотечної функції, яка відкривала та закривала файл.

Для спрощення запису використовується оператор with.

def controlled_execution(callback):
    f = open(filename)
    try:
        callback(thing)
    finally:
        f.close()

def my_function(thing):
    do something

controlled_execution(my_function)

Замість функції зручніше використовувати об’єкт.

class СontrolledExecution:
    def __enter__(self):
        f = open(filename)
        return f
    def __exit__(self, type, value, traceback):
        tear f.close()

with СontrolledExecution() as thing:
     some code with thing

Оператор with залучає внутрішній метод enter класу СontrolledExecution, який називається “охоронцем контексту”.

Все, що він повертає буде присвоєно змінної після ключового слова всередині блоку.

Потім Python виконає все, що знаходиться в блоці with і незалежно від того, що відбудеться при виконанні в кінці задіює метод exit. якщо виняток не було йому передається None.

Як додатковий бонус, метод exit може перехопити виняток та інформацію про помилку.

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

Функція open підтримує менеджер контексту, і можна відкрити файл набагато простіше.

with open("x.txt") as f:
    data = f.read()
    do something with data

У цьому прикладі файл буде закрито незалежно від результату всередині with.

Існує можливість використовувати відразу кілька операцій.

with A() as a, B() as b:
    suite

Що буде еквівалентно запису.

with A() as a:
    with B() as b:
        suite
Основи Python и Django. -> Домашнє завдання. Асинхронне програмування.

Домашнее задание. Асинхронное программирование.

Є такий словник з url адресами та таймаутами.

db = [ {‘url’: ‘http://google.com’, ‘timeout’: 3 }, {‘url’: ‘http://webmonstr.com’, ‘timeout’: 2 }, … ]

Необхідно створити консольну програму, яка в асинхронному режимі здійснює GET запити за заданими адресами та отримує код повернення.

Використовувати можна бібліотеку requests.

requests.get(url)

Запити здійснювати через заданий для кожного URL тайм-аут.

Результати писати в лог файл, використовуючи менеджер контексту with.

Орієнтовний формат лога.

[
    {
        'url': 'http://google.com', result: [
            {'time': '12.00:00', 'result': 200},
            {'time': '12.00:10', 'result': 400}
        ]
    } ... {} 
]
Основи Python и Django. -> Створення репозиторію GIT на сервері.

Створення репозиторію GIT на сервері.

Для створення репозиторію необхідно залогінитись на сервері з ssh.

Встановити git.

apt install git

Потім створимо нового користувача.

adduser git

Зайдемо під цим користувачем.

su git

Перейдемо до його домашньої директорії.

cd

Створимо папку.

mkdir repo.git

Зайдемо всередину папки.

cd repo.git

Запустимо створення репозиторію з прапором –bare що буде означати, що наш репозиторій не міститиме вихідників, а тільки файли для контролю версій. git init –bare

Тепер локально можна створити репозиторію.

git init

Додати віддалений репозиторій до нього.

git remote add origin ssh://git@domainname/home/git/repo.git

И залить файлы.

git add --all
git commit -m 'init'
git push --set-upstream origin master

Відповісти на запитання про додавання відбитка сервера.

The authenticity of host 'dima.webmonstr.com (188.120.241.104)' can't be established.
ECDSA key fingerprint is SHA256:fQQiO1wiRMkF9jsH7Qk3Qhi1Z1hA1MnYbp6bm+ZHPRs.
Are you sure you want to continue connecting (yes/no)?

Клонувати репозиторій можна командою.

git clone ssh://git@domainname/home/git/repo.git
Основи Python и Django. -> Асинхронне програмування з використанням asyncio.

Асинхронне програмування з використанням asyncio.

(Распаралелювання)[http://toly.github.io/blog/2014/02/13/parallelism-in-one-line/]

Припустимо, у нас є наступний словник.

store = {
    'place1' : [],
    'place2' : []
}

Заповнимо список за ключом place1 100 словниками.

store = {
    'place1' : [],
    'place2' : []
}

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

print(store)

Висновок

{‘place2’: [], ‘place1’: [{‘name’: ‘brick 0’}, {‘name’: ‘brick 1’}, {‘name’: ‘brick 2’},…

Тепер стоїть завдання перенести всі об’єкти цегли з place1 до place2.

Можна визначити функцію для цього та 100 разів її викликати.

store = {
    'place1' : [],
    'place2' : []
}

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

def move(store):
    brick = store['place1'].pop(0)
    store['place2'].append(brick)

for i in range(100):
    move(store)

print(store)

При цьому ми у функції виштовхуємо першу цеглу з одного списку.

brick = store['place1'].pop(0)

і далі заштовхуємо в другий.

store['place2'].append(brick)

Припустимо, що цей процес у нас займає багато часу, додамо до нього затримку в 1 сек.

import time
def move():
    time.sleep(1)
    brick = store['place1'].pop(0)
    print('Moving %s' % brick['name'])
    store['place2'].append(brick)


shell>python3 ex.py 
Moving brick 0
Moving brick 1
Moving brick 2

Виходить, весь процес у нас займе 100 сек.

Як його прискорити?

Для цього потрібно запустити цю функцію в кілька потоків або асинхронно.

Для реалізації асинхронного запуску потрібно мати цикл подій.

У цьому циклі система контролюватиме виконання одночасно-виконуваних завдань, надаючи механізм перемикання між ними.

Таким чином, після виконання своєї роботи, функція буде передавати управління циклу подій, який в свою чергу, буде запускати інші асинхронні завдання.

Бібліотека Asyncio дозволяємо розбивати ваш код на процедури (функції), які визначатиме як корутини, що дає можливість керувати ними як забажаєте, включаючи і одночасне виконання.

За перемикання контексту в asyncio відповідає await, який передає управління назад в event loop (цикл подій), а той у свою чергу - до іншої корутині.

Вираз після await повинен представляти спеціальний awaitable об’єкт.

Існує 3 варіанти таких об’єктів.

Як його прискорити?

  1. Інша співпрограма.

  2. Супрограма на основі генератора.

  3. Спеціальний об’єкт, у якого реалізовано магічний метод await, який повертає ітератор.

Ми можемо скористатися функцією asyncio.sleep(1), яка поверне awaitable об’єкт, емулюючи затримку.

Додамо функцію цикл від 0 до 50 і оголосимо її як корутин за допомогою async.

async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await asyncio.sleep(1)

Тепер необхідно створити список із 2-х таких функцій.

tasks = [
    ioloop.create_task(move(store)),
    ioloop.create_task(move(store))
]

Корутини можуть бути запущені лише з іншої корутини, або обернуті в завдання за допомогою create_task. Ми скористалися ioloop.create_task() для того, щоб обернути корутину в завдання.

Після того, як у нас виявилися 2 завдання, об’єднаємо їх, використовуючи wait.

asyncio.wait(tasks)

І, нарешті, відправимо на виконання у цикл подій через run_until_complete

ioloop.run_until_complete(asyncio.wait(tasks))

Повний код прикладу.

store = {
    'place1' : [],
    'place2' : []
}
import asyncio

for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await asyncio.sleep(1)


ioloop = asyncio.get_event_loop()

tasks = [
    ioloop.create_task(move(store)),
    ioloop.create_task(move(store))
]

ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()

print(store)

Ми можемо викликати одні корутини з інших, зв’язуючи їх у ланцюжки.

Наприклад, якщо ми хочемо логувати процес у файл в іншій корутині.

async def logger(brick):
    with open('log.txt', 'a') as f:
        f.write('Log: moving %s' % brick['name'])
    await asyncio.sleep(1)


async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await logger(brick)

Або створити клас з методом await, який працює як генератор із функцією yield.

class Loger(object):
    def __init__(self, brick):
        self.brick = brick
    def __await__(self):
        with open('log.txt', 'a') as f:
            f.write('Log: moving %s' % self.brick['name'])
        yield


for i in range(100):
    store['place1'].append({'name':  'brick %s' %i})

import time
async def move(store):
    for i in range(50):
        brick = store['place1'].pop(0)
        print('Moving %s' % brick['name'])
        store['place2'].append(brick)
        await Loger(brick)