Заметки по книге "Простой python" [Глава 9-12]


Глава 9: Распутываем Всемирную паутину

Created: Feb 15, 2021 7:02 PM

Стандартные библиотеки по работе с интернетом?

Сделать запрос на сервер: urlib.request


import urllib.request as ur
import json

url = 'http://api.quotable.io/random'
response = ur.urlopen(url)
print(response)

json_str = response.read().decode('utf8')
data = json.loads(json_str)
print(data['content'])

$ python3 ./test.py 
<http.client.HTTPResponse object at 0x7fbcfc7e58d0>
It is not so important to know everything as to appreciate what we learn.
  • Получить заголовок ответа

import urllib.request as ur

url = 'http://api.quotable.io/random'
response = ur.urlopen(url)
print('Content-Type =', response.getheader('Content-Type'))

$ python3 ./test.py 
Content-Type = application/json; charset=utf-8
  • Вывести все заголовки ответа от сервера

import urllib.request as ur

url = 'http://api.quotable.io/random'
response = ur.urlopen(url)

for key, val in response.getheaders():
    print(key, '=', val)

$ python3 ./test.py 
Server = Cowboy
Connection = close
X-Powered-By = Express
Access-Control-Allow-Origin = *
Content-Type = application/json; charset=utf-8
Content-Length = 247
Etag = W/"f7-fihZeTv49YZDFBuuHP4e4eCJgaA"
Date = Mon, 15 Feb 2021 16:22:12 GMT
Via = 1.1 vegur

Библиотека requests


import requests
import json

url = 'http://api.quotable.io/random'
response = requests.get(url)
print(response)
# <Response [200]>

print(type(response.text))
print(response.text)
# <class 'str'>
'''
{
  "_id": "4eekGH2qL80L",
  "tags": [
    "famous-quotes"
  ],
  "content": "He who lives in harmony with himself lives in harmony with the world.",
  "author": "Marcus Aurelius",
  "length": 69
}
'''

data = json.loads(response.text)
print(data['content'])
# No one has ever become poor by giving.

Веб серверы

Самый простой веб сервер: http.server

  • По умолчанию выводит содержимое текущей директории

$ ls -l
итого 12
drwxrwxr-x 2 avis avis 4096 фев 14 13:51 l1
-rwxrwxr-x 1 avis avis  918 фев 15 19:27 test.py
-rwxrwxr-x 1 avis avis 1544 фев 15 19:01 work_videos.py

$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [15/Feb/2021 19:29:25] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [15/Feb/2021 19:29:32] "GET / HTTP/1.1" 200 -

$ curl localhost:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li>l1/</li>
<li>test.py</li>
<li>work_videos.py</li>
</ul>
<hr>
</body>
</html>
$

Фреймворки

Bottle


from bottle import route, run

@route('/')
def home():
    return "Hello, world!"

run(host='localhost', port=9999)

$ python3 ./test.py 
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.

127.0.0.1 - - [15/Feb/2021 19:52:34] "GET / HTTP/1.1" 200 14

$ curl localhost:9999
Hello, world!
$
  • Для раздачи static_file

from bottle import route, run, static_file

@route('/')
def main():
    return static_file('index.html', root='.')

run(host='localhost', port=9999)

$ python3 ./test.py 
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.

127.0.0.1 - - [15/Feb/2021 20:05:31] "GET / HTTP/1.1" 200 221

$ cat index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
    <h2>Hello</h2>
</body>
</html>
$

$ curl localhost:9999
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
    <h2>Hello</h2>
</body>
</html>
$
  • Все что будет передано после /echo/* будет в передано как срока первому параметру

@route('/echo/<thing>')
def echo(thing):
    return "Привет %s\n" % thing
  • Пример unit теста

Server =


from bottle import route, run, static_file

@route('/')
def main():
    return static_file('index.html', root='.')

@route('/echo/<thing>')
def echo(thing):
    return "Hello %s\n" % thing

run(host='localhost', port=9999)

Test script =


import requests

response = requests.get('http://localhost:9999/echo/Test')

if response.status_code == 200 and response.text == "Hello Test\n":
    print("ok")
else:
    print("not ok; got =", response.text)

Run


$ python3 ./test.py 
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:9999/
Hit Ctrl-C to quit.

127.0.0.1 - - [16/Feb/2021 18:26:08] "GET /echo/Test HTTP/1.1" 200 11

$ python3 ./bottle_test.py 
ok

Flask

  • Пример веб-сервера на flask

from flask import Flask
app = Flask(__name__, static_folder='.', static_url_path='')

@app.route('/')
def home():
    return app.send_static_file('index.html')

@app.route('/echo/<thing>')
def echo(thing):
    return "Hello %s\n" % thing

app.run(port=9999, debug=True)

$ python3 ./test.py 
 * Serving Flask app "test" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:9999/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 257-630-425
127.0.0.1 - - [16/Feb/2021 19:31:12] "GET /echo/Test HTTP/1.1" 200 -

$ python3 ./bottle_test.py 
ok
  • Пример использования шаблонизатора jijna2

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/echo/<thing>')
def echo(thing):
    return render_template('flask.html', thing=thing)

app.run(port=9999, debug=True)

$ cat ./templates/flask.html 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask Example</title>
</head>
<body>
    <h2>Hello </h2>
</body>
</html>

$ python3 ./test.py 
 * Serving Flask app "test" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:9999/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 257-630-425
127.0.0.1 - - [16/Feb/2021 19:38:13] "GET /echo/Test HTTP/1.1" 200 -

$ curl localhost:9999/echo/Test
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask Example</title>
</head>
<body>
    <h2>Hello Test</h2>
</body>
</html>
  • Передача параметра в query_string

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/echo/')
def echo():
    param1 = request.args.get('param1')
    param2 = request.args.get('param2')
    return render_template('flask2.html', param1=param1, param2=param2)

if __name__ == '__main__':
    app.run(port=9999, debug=True)

$ curl localhost:9999/echo/?param1=Test
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask2 Example</title>
</head>
<body>
    <h2>Admin: Hello Test. How are you?
Test: I am None, and you?</h2> </body> </html>

Web Server Gateway Interface (WSGI)

Apache #TODO

Nginx #TODO

Работа с браузером

  • Открыть новую страницу в браузере

import webbrowser

url = 'https://python.org'
webbrowser.open(url)

Selenium: #TODO

Парсинг сайтов

Паук scrapy

Вывод html данных: BeautifulSoup

  • Вывести все ссылки с веб страницы

import sys
import requests
from bs4 import BeautifulSoup as soup

def get_links(url):
    result = requests.get(url)
    html = result.text
    doc = soup(html)
    print(type(doc))
    links = []
    for el in doc.find_all('a'):
        links.append(el.get('href'))
    return links

if __name__ == '__main__':
    for url in sys.argv[1:]:
        print(url)
        for num, link in enumerate(get_links(url)):
            print(num, link)

$ python3 ./test.py https://ya.ru
https://ya.ru
./test.py:11: GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line 11 of the file ./test.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.

  doc = soup(html)
<class 'bs4.BeautifulSoup'>
0 https://mail.yandex.ru
1 //yandex.ru
$ python3 ./test.py https://google.ru
https://google.ru
./test.py:11: GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line 11 of the file ./test.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.

  doc = soup(html)
<class 'bs4.BeautifulSoup'>
0 https://www.google.ru/imghp?hl=ru&tab=wi
1 https://maps.google.ru/maps?hl=ru&tab=wl
2 https://play.google.com/?hl=ru&tab=w8
3 https://www.youtube.com/?gl=RU&tab=w1
4 https://news.google.com/?tab=wn
5 https://mail.google.com/mail/?tab=wm
6 https://drive.google.com/?tab=wo
7 https://www.google.ru/intl/ru/about/products?tab=wh
8 http://www.google.ru/history/optout?hl=ru
9 /preferences?hl=ru
10 https://accounts.google.com/ServiceLogin?hl=ru&passive=true&continue=https://www.google.ru/&ec=GAZAAQ
11 /advanced_search?hl=ru&authuser=0
12 /intl/ru/ads/
13 http://www.google.ru/intl/ru/services/
14 /intl/ru/about.html
15 https://www.google.ru/setprefdomain?prefdom=US&sig=K_cPvulQWDJ9leqNkSVr2saS_BfLM%3D
16 /intl/ru/policies/privacy/
17 /intl/ru/policies/terms/
$

Глава 10: Операционные системы

Created: Feb 19, 2021 5:43 PM

Файлы

Проверка существования файла: exists


import os

file_name = './text.txt'

with open(file_name, 'wt') as file:
    file.write('hello\n')

print("File exists? ", os.path.exists(file_name))
# File exists?  True
print("File exists? ", os.path.exists('alalala'))
# File exists?  False

print("is file?", os.path.isfile(file_name))
# is file? True

print("is file?", os.path.isfile('.'))
# is file? False

print("is dir?", os.path.isdir('.'))
# is dir? True

Копирование файла: shutil.copy


import os
import shutil

file_name = './text.txt'
new_file_name = file_name + 'new.txt'

with open(file_name, 'wt') as file:
    file.write('hello\n')

shutil.copy(file_name, new_file_name)
print(os.path.exists(new_file_name))

Перемещение (переименование) файла: os.rename


import os

file_name = './text.txt'
new_file_name = file_name + 'new.txt'

with open(file_name, 'wt') as file:
    file.write('hello\n')

print(os.rename(file_name, new_file_name))
# None

print(os.path.exists(file_name))
# False

print(os.path.exists(new_file_name))
# True
  • link - жесткая ссылка

import os

file_name = './text.txt'
new_file_name = file_name + '.new.txt'

with open(file_name, 'wt') as file:
    file.write('hello\n')

print(os.link(file_name, new_file_name))
# None

print(os.path.islink(new_file_name))
# True

print(os.remove(new_file_name))

$ ls -la ./text.txt.new.txt 
-rw-rw-r-- 2 avis avis 6 фев 19 18:03 ./text.txt.new.txt
  • 2 - сколько ссылок

  • symlink - символьная ссылка


import os

file_name = './text.txt'
new_file_name = file_name + '.new.txt'

with open(file_name, 'wt') as file:
    file.write('hello\n')

print(os.symlink(file_name, new_file_name))
# None

print(os.path.islink(new_file_name))
# True

print(os.remove(new_file_name))

$ ls -la ./text.txt.new.txt 
lrwxrwxrwx 1 avis avis 10 фев 19 18:04 ./text.txt.new.txt -> ./text.txt

#TODO:

  • функции chmod()
  • функции chown()
  • abspath()
  • функции realpath()
  • remove()
  • mkdir()
  • rmdir()
  • listdir()
  • chdir()
  • glob()

Процессы

Создание процесса: subprocess


import subprocess

result = subprocess.getoutput('date')
print(result)
# Пт фев 19 18:17:59 MSK 2021
  • Основной процесс будет висеть пока подпроцесс не завершиться

import subprocess

result = subprocess.getoutput("perl -E 'sleep 3; say 2'")
print(result)

print(1)

$ python3 ./test.py 
2
1
$
  • Строка в getoutput передается как есть

import subprocess

result = subprocess.getoutput('ps aux | grep python | wc -l')
print(result)

$ python3 ./test.py 
11
  • Для запуска списка команд - check_output. Возвращает список байтов

import subprocess

result = subprocess.check_output(['date', '-u'])
print(result.decode('utf8'))

$ python3 ./test.py 
Пт фев 19 15:25:45 UTC 2021
  • Получить статус и результат в виде кортежа - getstatusoutput

import subprocess

result = subprocess.getstatusoutput(['date', '-u'])
print(result)

$ python3 ./test.py 
(0, 'Пт фев 19 18:27:05 MSK 2021')
  • Получить только статус - call

import subprocess

result = subprocess.call(['date', '-u'])
print(result)

print('result=', result)

$ python3 ./test.py 
Пт фев 19 15:36:41 UTC 2021
0
result= 0

Причем обычную строку он уже не принимает…


import subprocess

result = subprocess.call('date -u')
print(result)

$ python3 ./test.py 
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    result = subprocess.call('date -u')
  • Нужно через shell=True

import subprocess

result = subprocess.call('date -u', shell=True)
print(result)

print('result=', result)

$ python3 ./test.py 
Пт фев 19 15:38:17 UTC 2021
0
result= 0

Использование мультипроцессинга: multiprocessing


import multiprocessing as mp
import os
import time

def do_this(text):
    whoami(text)

def whoami(text):
    time.sleep(1)
    print("Процесс %s говорит: '%s'" % (os.getpid(), text))

if __name__ == '__main__':
    whoami('Я основная программа main')
    for i in range(4):
        proc = mp.Process(target=do_this, args=("Я функция %s" % i, ))
        proc.start()
    print("Конец main")

$ python3 ./test.py 
Процесс 14933 говорит: 'Я основная программа main'
Конец main
Процесс 14935 говорит: 'Я функция 0'
Процесс 14937 говорит: 'Я функция 2'
Процесс 14936 говорит: 'Я функция 1'
Процесс 14938 говорит: 'Я функция 3'
  • Основной процесс породил несколько fork процессов и ждет пока все завершаться

запятая в “args=(“Я функция %s” % i, )” обязательна! без нее ошибка

Принудительное завершение процесса: terminate()


import multiprocessing as mp
import time
import os

def whoami(name):
    print("Я %s, PID=%s" % (name, os.getpid()))

def loop(name):
    whoami(name)
    start = 1
    stop = 1000
    for n in range(start, stop):
        print("\tNumber %s of %s" % (n, stop))
        time.sleep(start)

if __name__ == '__main__':
    whoami("основной процесс main")
    proc = mp.Process(target=loop, args=("loopy", ))
    proc.start()
    time.sleep(3)
    print(proc.terminate())

$ python3 ./test.py 
Я основной процесс main, PID=16249
Я loopy, PID=16250
    Number 1 of 1000
    Number 2 of 1000
    Number 3 of 1000
None
  • бгг, если завершить основной процесс то дочерний будет дальше жить))

import subprocess
import multiprocessing as mp
import time
import os

def whoami(name):
    print("Я %s, PID=%s" % (name, os.getpid()))

def loop(name):
    whoami(name)
    start = 1
    stop = 1000
    for n in range(start, stop):
        print("\tNumber %s of %s; parent pid=%s" % (n, stop, os.getppid()))
        time.sleep(start)

if __name__ == '__main__':
    whoami("основной процесс main")
    proc = mp.Process(target=loop, args=("loopy", ))
    proc.start()
    time.sleep(3)
    print("I'll be back -_-")
    subprocess.call(['kill', str(os.getpid())])

$ python3 ./test.py 
Я основной процесс main, PID=16970
Я loopy, PID=16971
    Number 1 of 1000; parent pid=16970
    Number 2 of 1000; parent pid=16970
    Number 3 of 1000; parent pid=16970
I'll be back -_-
    Number 4 of 1000; parent pid=16970
Завершено
$   Number 5 of 1000; parent pid=2146
    Number 6 of 1000; parent pid=2146

Дата и время

Дата


from datetime import date

today = date(2021, 2, 22)

print(today)
# 2021-02-22

print("day =", today.day, "month =", today.month, "year =", today.year)
# day = 22 month = 2 year = 2021

Создать дату: date


from datetime import date

today = date(2021, 2, 22)

print(today)
# 2021-02-22

Обращение к дате по отдельности: year, month, day


print("day =", today.day, "month =", today.month, "year =", today.year)
# day = 22 month = 2 year = 2021

Текущая дата: date.today()


from datetime import date

now = date.today()

print(now)
# 2021-02-22

Добавить дельту к дате: timedelta()


from datetime import date, timedelta

now = date.today()

print(now)
# 2021-02-22

one_day = timedelta(days=1)

print(now + one_day)
# 2021-02-23

print(now + one_day * 7)
# 2021-03-01

Время: time

Вывод текущего времени в epoth: time.time()


from time import time, ctime

now = time()

print(now)
# 1613993148.653525

fnow = ctime(now)
print(fnow)
# Mon Feb 22 14:27:51 2021

Время в текущем часовом поясе (localtime) и в UTC (gmtime)


import time

print(time.localtime())
# time.struct_time(tm_year=2021, tm_mon=2, tm_mday=22, tm_hour=14, tm_min=31,
# tm_sec=52, tm_wday=0, tm_yday=53, tm_isdst=0)

print(time.gmtime())
# time.struct_time(tm_year=2021, tm_mon=2, tm_mday=22, tm_hour=11, tm_min=32,
# tm_sec=32, tm_wday=0, tm_yday=53, tm_isdst=0)

Форматирование времени

  • Преобразовать время к строке - strftime

import time

format = "Сегодня: %A, %B %d %Y. Локальное время: %H:%M:%S%p"

now = time.localtime()

print(time.strftime(format, now))
# Сегодня: Monday, February 22 2021. Локальное время: 14:39:42PM
  • Спецификатор Единица даты/времени Диапазон
  • %Y Год 1900–…
  • %m Месяц 01–12
  • %B Название месяца Январь, …
  • %b Сокращение для месяца Янв, …
  • %d День месяца 01–31
  • %А Название дня Воскресенье, …
  • а Сокращение для дня Вск, …
  • %Н Часы (24 часа) 00–23
  • %I Часы (12 часов) 01–12
  • %p AM или PM AM, PM
  • %M Минуты 00–59
  • %S Секунды 00–59

  • Преобразовать строку ко времени - strptime

import time

fmt = "%Y-%m-%d"

print(time.strptime("2020-01-06", fmt))
# time.struct_time(tm_year=2020, tm_mon=1, tm_mday=6, tm_hour=0, tm_min=0,
# tm_sec=0, tm_wday=0, tm_yday=6, tm_isdst=-1)

Глава 11. Конкуренция и сети

Created: Feb 23, 2021 11:20 AM

Очереди


import multiprocessing as mp
import time

def washer(dishes, output):
    for dish in dishes:
        print("Мытье ", dish, time.localtime().tm_sec)
        time.sleep(3)
        output.put(dish)

def dryer(input):
    while True:
        dish = input.get()
        print("Сушка ", dish, time.localtime().tm_sec)
        time.sleep(1)
        input.task_done()

dish_queue = mp.JoinableQueue()
dryer_proc = mp.Process(target=dryer, args=(dish_queue, ))
dryer_proc.daemon = True
dryer_proc.start()
dishes = ['салат', 'суп', 'жаркое', 'бараньи ребрышки']
washer(dishes, dish_queue)
dish_queue.join()

$ python3 ./dishes.py 
Мытье  салат 41
Мытье  суп 44
Сушка  салат 44
Мытье  жаркое 47
Сушка  суп 47
Мытье  бараньи ребрышки 50
Сушка  жаркое 50
Сушка  бараньи ребрышки 53

python3(11140)-+-python3(11141)
               `-{python3}(11150)

Потоки


import os
import time
import threading

def do_this(what):
    time.sleep(100)
    whoami(what)

def whoami(text):
    print("Поток %s сказал: %s " % (threading.current_thread(), text))

if __name__ == '__main__':
    whoami("Я основная программа, " + str(os.getpid()))
    for i in range(4):
        proc = threading.Thread(target=do_this, args=("Я поток %s" % i,))
        proc.start()

$ python3 thread.py
Поток <_MainThread(MainThread, started 140012683785984)> сказал: Я основная программа, 10785 
Поток <Thread(Thread-4, started 140012627429120)> сказал: Я поток 3 
Поток <Thread(Thread-2, started 140012644214528)> сказал: Я поток 1 
Поток <Thread(Thread-1, started 140012652607232)> сказал: Я поток 0 
Поток <Thread(Thread-3, started 140012635821824)> сказал: Я поток 2

$ pstree -p 10857 | less
python3(10857)-+-{python3}(10858)
               |-{python3}(10859)
               |-{python3}(10860)
               `-{python3}(10861)
  • Мытье посуды на потоках

import os
import threading
import queue
import time

def washer(dishes, dish_queue):
    for dish in dishes:
        print("Мытье ", dish, time.localtime().tm_sec)
        time.sleep(10)
        dish_queue.put(dish)

def dryer(dish_queue):
    while True:
        dish = dish_queue.get()
        print("Сушка ", dish, time.localtime().tm_sec)
        time.sleep(5)
        dish_queue.task_done()

print(os.getpid())
dish_queue = queue.Queue()
for n in range(2):
    thread = threading.Thread(target=dryer, args=(dish_queue, ))
    thread.start()

dishes = ['салат', 'суп', 'жаркое', 'бараньи ребрышки']
washer(dishes, dish_queue)
dish_queue.join()

$ python3 dishes.py 
11871
Мытье  салат 50
Мытье  суп 0
Сушка  салат 0
Мытье  жаркое 10
Сушка  суп 10
Мытье  бараньи ребрышки 20
Сушка  жаркое 20
Сушка  бараньи ребрышки 30

python3(11871)-+-{python3}(11872)
               `-{python3}(11873)

Зеленые потоки и gevent

gevent


pip3 install gevent

import gevent
from gevent import socket

hosts = ['google.com', 'ya.ru']
jobs = [gevent.spawn(socket.gethostbyname, host) for host in hosts]
gevent.joinall(jobs, timeout=5)
for job in jobs:
    print(job.key, job.value)

$ python3 ./gevent_test.py 
74.125.205.138
87.250.250.242
  • gevent.spawn - создает потоки и в случае если один из них заблокирован, переключает на другой
  • socket.gethostbyname - возвращает ip адрес хоста
  • gevent.joinall - ждет выполнения всех потоков

import gevent
from gevent import monkey
import socket

monkey.patch_all()

hosts = ['google.com', 'ya.ru', 'dads.com']
jobs = [gevent.spawn(socket.gethostbyname, host) for host in hosts]
gevent.joinall(jobs, timeout=5)
for job in jobs:
    print(job.value)

По факту ничего не поменялось, только добавился monkey.patch_all() который вроде ничего не делает

twisted


pip3 install twisted
  • какаята магия если честно… я ваще не шарю о чем пишу


asyncio (Asynchronous IO Support Rebooted)

Очередь на основе redis

  • мойщик

import redis

redis = redis.Redis()

print("Мойка началась!")

dishes = ['салат', 'суп', 'жаркое', 'бараньи ребрышки']
for dish in dishes:
    print("Мытье", dish)
    msg = dish.encode('utf8')
    redis.rpush('dishes', msg)

redis.rpush('dishes', 'quit')
print("Мойка закончилась!")
  • сушильщик

import redis

redis = redis.Redis()

print("Сушка началась!")

while True:
    msg = redis.blpop('dishes')
    if not msg:
        break
    value = msg[1].decode('utf8')
    if value == 'quit':
        break
    print("Сушка", value)

print("Сушка закончилась!")
  • Запуск мойщика - в редисе 4 тарелки

$ python3 redis_washer.py
Мойка началась!
Мытье салат
Мытье суп
Мытье жаркое
Мытье бараньи ребрышки
Мойка закончилась!
$

127.0.0.1:6379> llen dishes
(integer) 5

import os
import time
import multiprocessing as mp
import redis

COUNT_DRYERS = 3
TIMEOUT = 20
redis = redis.Redis()

def drying():
    pid = os.getpid()
    print("Сушка началась! процесс {}".format(pid))
    while True:
        msg = redis.blpop('dishes', TIMEOUT)
        if not msg:
            break
        value = msg[1].decode('utf8')
        if value == 'quit':
            break
        print("{}: сушка {}".format(pid, value))
        time.sleep(0.3)
    print("Сушка закончилась! процесс {}".format(pid))

if __name__ == '__main__':
    for dryer in range(COUNT_DRYERS):
        proc = mp.Process(target=drying)
        proc.start()

Сети

Публикация-Подписка (pub-sub)

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

Redis

  • Публикатор

import redis
import random

redis = redis.Redis()

artists = ['DETACH', 'Orden Ogan', 'Matt Guillory', 'Dragged Under',
           'Radio Tapok', 'Strike', '10 Years', 'A Day To Remember']

tracks = ['Afterglow', 'Heart of the Android',
          'Give Me a Sign', 'Just Like Me',
          'Wrong Side of Heaven', 'Плачь',
          'Never Too Late', 'Sleep In The Fire']

for msg in range(10):
    artist = random.choice(artists)
    track = random.choice(tracks)
    print("Публикация: Группа {} исполняет {}".format(artist, track))
    redis.publish(artist, track)
  • Подписчик

import redis

redis = redis.Redis()

topics = ['Radio Tapok', 'A Day To Remember']

sub = redis.pubsub()
sub.subscribe(topics)
for msg in sub.listen():
    if msg['type'] == 'message':
        artist = msg['channel']
        track = msg['data']
        print("Подписка: Группа {} исполняет {}".format(
            artist.decode('utf8'),
            track.decode('utf8')
        ))
  • Запуск публикатора+подписчика

$ python3 redis_pub.py 
Публикация: Группа A Day To Remember исполняет Just Like Me
Публикация: Группа Strike исполняет Give Me a Sign
Публикация: Группа Orden Ogan исполняет Heart of the Android
Публикация: Группа Radio Tapok исполняет Just Like Me
Публикация: Группа Strike исполняет Never Too Late
Публикация: Группа A Day To Remember исполняет Плачь
Публикация: Группа Matt Guillory исполняет Just Like Me
Публикация: Группа Strike исполняет Never Too Late
Публикация: Группа Dragged Under исполняет Never Too Late
Публикация: Группа 10 Years исполняет Afterglow

$ python3 ./redis_sub.py
Подписка: Группа A Day To Remember исполняет Just Like Me
Подписка: Группа Radio Tapok исполняет Just Like Me
Подписка: Группа A Day To Remember исполняет Плачь

1614355585.717940 [0 127.0.0.1:34986] "SUBSCRIBE" "Radio Tapok" "A Day To Remember"
1614355604.167930 [0 127.0.0.1:34988] "PUBLISH" "A Day To Remember" "Just Like Me"
1614355604.168081 [0 127.0.0.1:34988] "PUBLISH" "Strike" "Give Me a Sign"
1614355604.168188 [0 127.0.0.1:34988] "PUBLISH" "Orden Ogan" "Heart of the Android"
1614355604.168282 [0 127.0.0.1:34988] "PUBLISH" "Radio Tapok" "Just Like Me"
1614355604.168380 [0 127.0.0.1:34988] "PUBLISH" "Strike" "Never Too Late"
1614355604.168470 [0 127.0.0.1:34988] "PUBLISH" "A Day To Remember" "\xd0\x9f\xd0\xbb\xd0\xb0\xd1\x87\xd1\x8c"
1614355604.168564 [0 127.0.0.1:34988] "PUBLISH" "Matt Guillory" "Just Like Me"
1614355604.168650 [0 127.0.0.1:34988] "PUBLISH" "Strike" "Never Too Late"
1614355604.168759 [0 127.0.0.1:34988] "PUBLISH" "Dragged Under" "Never Too Late"
1614355604.168859 [0 127.0.0.1:34988] "PUBLISH" "10 Years" "Afterglow"

Важно вначале запустить подписчика а потом публикатора! Иначе сообщения пропадут и никто их не увидит!

Если будет 2 и более подписчика, то оба получат сообщения

ZeroMQ


pip3 install zmq

или ?


pip3 install pyzmq
  • Публикатор

import random
import time
import zmq

host = '*'
port = 6789

ctx = zmq.Context()
print(zmq.PUB)
pub = ctx.socket(zmq.PUB)
pub.bind('tcp://{}:{}'.format(host, port))

artists = ['DETACH', 'Orden Ogan', 'Matt Guillory', 'Dragged Under',
           'Radio Tapok', 'Strike', '10 Years', 'A Day To Remember']

tracks = ['Afterglow', 'Heart of the Android',
          'Give Me a Sign', 'Just Like Me',
          'Wrong Side of Heaven', 'Плачь',
          'Never Too Late', 'Sleep In The Fire']

time.sleep(1)
for msg in range(10):
    artist = random.choice(artists)
    track = random.choice(tracks)
    print("Публикация: Группа {} исполняет {}".format(artist, track))
    pub.send_multipart([artist.encode('utf8'), track.encode('utf8')])
  • Подписчик

import zmq

host = 'localhost'
port = 6789

ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.connect('tcp://{}:{}'.format(host, port))
topics = ['Radio Tapok', 'A Day To Remember']

for top in topics:
    sub.setsockopt(zmq.SUBSCRIBE, top.encode('utf8'))

while True:
    artist, track = sub.recv_multipart()
    print("Подписка: Группа {} исполняет {}".format(
        artist.decode('utf8'),
        track.decode('utf8')
    ))

$ python ./main.py 
1
Публикация: Группа A Day To Remember исполняет Never Too Late
Публикация: Группа Radio Tapok исполняет Never Too Late
Публикация: Группа A Day To Remember исполняет Плачь
Публикация: Группа DETACH исполняет Плачь
Публикация: Группа Strike исполняет Heart of the Android
Публикация: Группа A Day To Remember исполняет Heart of the Android
Публикация: Группа 10 Years исполняет Afterglow
Публикация: Группа Matt Guillory исполняет Just Like Me
Публикация: Группа Strike исполняет Afterglow
Публикация: Группа Strike исполняет Wrong Side of Heaven
$ python ./main.py 
1
Публикация: Группа A Day To Remember исполняет Плачь
Публикация: Группа Dragged Under исполняет Just Like Me
Публикация: Группа DETACH исполняет Just Like Me
Публикация: Группа Orden Ogan исполняет Heart of the Android
Публикация: Группа Dragged Under исполняет Just Like Me
Публикация: Группа 10 Years исполняет Never Too Late
Публикация: Группа Radio Tapok исполняет Плачь
Публикация: Группа Dragged Under исполняет Just Like Me
Публикация: Группа Orden Ogan исполняет Afterglow
Публикация: Группа Strike исполняет Never Too Late

$ python ./test.py 
Подписка: Группа A Day To Remember исполняет Never Too Late
Подписка: Группа Radio Tapok исполняет Never Too Late
Подписка: Группа A Day To Remember исполняет Плачь
Подписка: Группа A Day To Remember исполняет Heart of the Android
Подписка: Группа A Day To Remember исполняет Плачь
Подписка: Группа Radio Tapok исполняет Плачь

RabbitMQ

Запрос-Ответ

Пример UDP и TCP сервера

  • UDP сервер

from datetime import datetime
import socket

print("Старт сервера в", datetime.now())
print("Ждем клиентов")

max_size = 4096
server_address = ('localhost', 6789)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(server_address)

data, client = server.recvfrom(max_size)
print("В ", datetime.now(), client, "сказал", data)
server.sendto(b'Are you talking to me?', client)
server.close()
  • UDP клиент

from datetime import datetime
import socket

server_address = ('localhost', 6789)
max_size = 4096
print("Старт клиента в ", datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b'HEy!', server_address)

data, server = client.recvfrom(max_size)
print("В ", datetime.now(), server, "сказал", data)
client.close()
  • socket.SOCK_DGRAM - говорит о том что сервер и/или клиент будет работать с датаграммой т.е. по UDP соединению
  • После запуска сервера, он ждет подключения клиентов (server.recvfrom(max_size))

$ python3 udp_server.py
Старт сервера в 2021-02-28 13:36:43.385251
Ждем клиентов

$ python3 udp_client.py
Старт клиента в  2021-02-28 13:37:56.216326
В  2021-02-28 13:37:56.216635 ('127.0.0.1', 6789) сказал b'Are you talking to me?'
$

$ python3 udp_server.py
Старт сервера в 2021-02-28 13:36:43.385251
Ждем клиентов
В  2021-02-28 13:37:56.216575 ('127.0.0.1', 42279) сказал b'HEy!'
$

После запуска клиента, тот отправил строку “HEy!” и в ответ получил “Are you talking to me?”

  • TCP сервер

from datetime import datetime
import socket

print("Старт сервера в", datetime.now())
print("Ждем клиентов")

max_size = 1024
server_address = ('localhost', 6789)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(server_address)
server.listen(5)
client, addr = server.accept()
data = client.recv(max_size)
print("В ", datetime.now(), client, "сказал", data)
client.sendall(b'Are you talking to me?')
client.close()
server.close()

$ python3 tcp_server.py
Старт сервера в 2021-02-28 13:49:06.119605
Ждем клиентов
  • TCP клиент

from datetime import datetime
import socket

server_address = ('localhost', 6789)
max_size = 1024
print("Старт клиента в ", datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(server_address)
client.sendall(b'HEy!')
data = client.recv(max_size)
print("В ", datetime.now(), "сказал", data)
client.close()

$ python3 tcp_client.py
Старт клиента в  2021-02-28 13:51:16.269556
  1. Клиент отправил сообщение на сервер

$ python3 tcp_server.py
Старт сервера в 2021-02-28 13:49:06.119605
Ждем клиентов
В  2021-02-28 13:51:16.269913 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6789), raddr=('127.0.0.1', 57602)> сказал b'HEy!'
$
  1. После чего сервер ответил клиенту

$ python3 tcp_client.py
Старт клиента в  2021-02-28 13:51:16.269556
В  2021-02-28 13:51:16.270032 сказал b'Are you talking to me?'
$

ZeroMQ

Для Python 3.5.2 последняя версия модуля ломалась, так что поставил более раннюю


pip3 install 'pyzmq==19.0.0'
  • Сервер

import zmq

host = '127.0.0.1'
port = 6789

ctx = zmq.Context()
server = ctx.socket(zmq.REP)
addres = "tcp://{}:{}".format(host, port)
print(addres)
server.bind(addres)

while True:
    req_bytes = server.recv()
    request_str = req_bytes.decode('utf8')
    print("Получен запрос [", request_str, "]", sep='')
    reply_str = "Ответ от сервера: {}".format(request_str)
    reply_bytes = reply_str.encode('utf8')
    server.send(reply_bytes)
  • Клиент

import zmq

host = '127.0.0.1'
port = 6789
address = "tcp://{}:{}".format(host, port)
print(address)

ctx = zmq.Context()
client = ctx.socket(zmq.REQ)
client.connect(address)

for n in range(1, 5):
    req_msg = "Message #{}".format(n)
    req_bytes = req_msg.encode('utf8')
    client.send(req_bytes)
    reply_bytes = client.recv()
    reply_str = reply_bytes.decode('utf8')
    print("Отправлено: [{}]; Получено: [{}]".format(req_msg, reply_str))
  • Запуск сервера

$ python3 zmq_server.py
tcp://127.0.0.1:6789
Получен запрос [Message #1]
Получен запрос [Message #2]
Получен запрос [Message #3]
Получен запрос [Message #4]
  • Запуск клиента

$ python3 zmq_client.py
tcp://127.0.0.1:6789
Отправлено: [Message #1]; Получено: [Ответ от сервера: Message #1]
Отправлено: [Message #2]; Получено: [Ответ от сервера: Message #2]
Отправлено: [Message #3]; Получено: [Ответ от сервера: Message #3]
Отправлено: [Message #4]; Получено: [Ответ от сервера: Message #4]

Глава 12. Быть питонщиком

Created: Mar 4, 2021 8:01 PM