Coding: Python заметки

  • Python.org – основной сайт. Тут можно скачать актуальный python на windows/linux/mac os, посмотреть документацию
  • Гвидо ван Россум – творец языка. По сути аналог Линус Торвальдса для Linux.
  • Python – the second best language for everything. И это действительно так, включая новые-модные devops, AI, BigData, autotests. При этом нельзя забывать, что 1) любой язык в конечном счете является лишь инструментом для решения задачи и какой-то инструмент может быть лучше, чем текущий 2) зная один язык, проще изучить новый. Подробнее в статье про программирование.
  • Repl.it – онлайн интерпретатор.
  • PEP8 – правила по написанию кода, важно соблюдать. Поможет одноименный чекер. Пример стилистики описан в статье про лучшие практики программирования.
  • У Python одна из лучших документаций в мире. 
  • Название Python не от змеи, а от комедийного шоу. 
  • Python3 по факту python 3000. В этой версии значительно улучшена архитектура. На сегодняшний день все основные библиотеки поддерживают 3’ю версию.
  • В отличии от многих других языков отступ (identation) в python очень важен (обозначает блок кода, как скобки в php/c) и неправильный отступ может приводить к ошибкам.  В этом есть большой плюс – код по умолчанию должен быть «красивым».
  • Python – язык с динамической типизацией – переменным не обязательно задавать тип.

  • Переменные в python являются ссылками на область памяти (подробнее в описании функции id)

  • Интерпретатор Python делает поиск значения переменной в последовательности областей видимостей (scope, namespace) по приоритету до первого совпадения, это называется правило LEGB. Из-за такой приоритезации можно переназначить переменные, уже определенные во встроенных/внешних функциях.
    • L (local) – в локальной функции
    • E (enclosing) – в функции, включающей нашу локальную функцию
    • G (global) – в общем пространстве скрипта
    • B (built-in) – во встроенных функциях
>>> str
<class 'str'>
>>> str=[]
>>> str
[]

>>> del str # удаляем переменную str
>>> str
<class 'str'>
  • В python работает механизм garbage collection. Garbage collection – процесс, который удаляет ненужные элементы. К примеру, чистит память (старые участки) при переназначении переменных – на участки памяти не ссылаются переменные, их можно высвобождать.

  • None – по сути нулевой элемент. С точки зрения булевой логики является False, точно так же как пустая строка “”, пустой массив [] или ноль 0.
  • Заполненный чем то элемент при этом является True и этим можно пользоваться (главное не думать, что если массив заполнен False, то он будет считаться False):
sm_str = "haha"
if sm_str:
print("{}".format(sm_str))

# вместо
if sm_str != "":
print("{}".format(sm_str))
  • Проверить что в массиве нет False элементов можно через стандартную функцию in.
if False not in l:
res = True
else:
res = False

Обучение

 

Версии

  • CPython – эталонная (классическая) реализация языка. Интерпретатор написан на C.
  • Jython – специальная версия Python (судя по дате последнего апдейта в 2015 – заброшенная) для виртуальной машины Java позволяет интерпретатору выполняться на любой системе, поддерживающей Java, при этом классы Java могут непосредственно использоваться из Python и даже быть написанными на Python.
  • MicroPython – lightweight реализация для микроконтроллеров с малым потреблением памяти, процессора и питания.  Пример девайса есть на канале Dan Bader (известный популяризатор Python). 
it is compact enough to fit and run within just 256k of code space and 16k of RAM

 

Frameworks

Фреймворки:

  • Flask для простых проектов
  • Django для всего остального

 

Install

CentOS 7

Установка python3.6 (поддерживает String Interpolation в виде format и f-strings) на Ubuntu 14.04.

sudo yum install https://centos7.iuscommunity.org/ius-release.rpm
sudo yum install python36
python3.6 -V
sudo yum install python36u-pip # ставим pip и setuptools
sudo apt install python3-pip
sudo pip3.6 install --upgrade pip # обновляем его
sudo pip3.6 install virtualenv
sudo pip3.6 install requests # ставим пакет requests
sudo pip3.6 install beautifulsoup4
sudo python3 -m pip install virtualenv # альтернатива

CentOS 6

Установка python3.6 на CentOS 6.

sudo yum install https://centos6.iuscommunity.org/ius-release.rpm
sudo yum install python36u
python3.6 -V
== other same as CentOS 7 ==

Ubuntu 14.04

Установка python3.6 на Ubuntu 14.04.

sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.6
sudo apt-get install python3-pip # ставим pip и setuptools
== other same as CentOS 7 ==

После установки в коде (#!/usr/bin/env python3.6) или при запуске (python3.6 <script>) указываем конкретную версию. Так же можно сделать эту версию версией по умолчанию для python3 (не рекомендую т.к. на него полагается shell – стоит ввести фигню в консоли чтобы это увидеть) или даже для “просто” python (еще меньше рекомендую) используя утилиту update-alternatives.

~$ python3 -V
Python 3.4.3
~$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1
update-alternatives: using /usr/bin/python3.6 to provide /usr/bin/python3 (python3) in auto mode
~$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.4 2
update-alternatives: using /usr/bin/python3.4 to provide /usr/bin/python3 (python3) in auto mode
~$ sudo update-alternatives --config python3
There are 2 choices for the alternative python3 (providing /usr/bin/python3).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/bin/python3.4 2 auto mode
1 /usr/bin/python3.4 2 manual mode
2 /usr/bin/python3.6 1 manual mode
Press enter to keep the current choice[*], or type selection number: 2
update-alternatives: using /usr/bin/python3.6 to provide /usr/bin/python3 (python3) in manual mode
~$ python3 -V
Python 3.6.8

Shebang возможен с конерктной версией python. Важно использовать env – такая запись универсальнее статического задания интерпретатора т.к. env будет находить python автоматически.

#!/usr/bin/env python3.7

Если при установке shebang выдает ошибку, то нужно очистить скрипт от служебного символа \r. Причем не обязательно он в конце строки на которую ругается. Оболочка интерпретирует эти символы CR как аргументы.

/usr/bin/env: «python3.7\r»: Нет такого файла или каталога

Пример скрипта для очистки.

with open('test.py', 'rb+') as f:
   content = f.read()
   f.seek(0) # переход в самое начало файла
   f.write(content.replace(b'\r', b''))
   f.truncate()

 

 

 

PIP (установка и использование)

Огромное количество модулей входит в стандартную библиотеку python. Те, которые не входят, обычно можно найти в pip, за редким исключением. 

Установка модулей делается через установку из источника, easy_install или pip. Лучшим вариантом насколько понимаю является pip – это аналог gem в Ruby. Он требует отдельную установку себя like “sudo pip3.6 install requests”.

Установка библиотеки.

sudo pip3 install requests # ставим пакет requests
sudo pip3 install beautifulsoup4

Обновление библиотеки.

sudo pip3 install -U requests

Удаление библиотеки.

sudo pip3 uninstall requests

Просмотр установленных библиотек.

pip3 list
Package Version
---------- ----------
certifi 2018.11.29
chardet 3.0.4
idna 2.8
pip 18.1
requests 2.21.0
setuptools 39.0.1
urllib3 1.24.1

Ошибки

Если pip не получается поставить pip с ошибкой “No package python-pip available. Error: Nothing to do”, то нужно поставить репу ius.

sudo yum install https://centos7.iuscommunity.org/ius-release.rpm

Если в качестве результата при установке пакета выдает “Consider using the `–user` option or check the permissions.”, значит нужно запускать из под sudo.

sudo pip3.6 install requests 

Если выдает “sudo: pip3.6: command not found”, то надо каждый раз при использовании pip указывать полный путь или сделать symbolic link.

sudo /usr/local/bin/pip3.6 install requests 

Если выдает timeout, то нужно проверить сеть (iptables, proxy, etc).

  Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.VerifiedHTTPSConnection object at 0x7f443823edd8>, 'Connection to pypi.org timed out. (connect timeout=15)')': /simple/pytest/

 

Интерпретатор

Просмотр версии, запуск в интерактивном режиме. Другие опции можно посмотреть в man python. 

$ python -V # версия второго
Python 2.7.10

$ python3 -V # версия третьего
Python 3.7.2

$ python3 # запуск в интерактивном режиме
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

#Простейшая математика в интерактивном режиме
>>> x=6
>>> print(x)
6
>>> x=x*6
>>> print(x)
36
quit() # выход

-u – важная опция, которая может пригодиться, особенно для длительно работающих скриптов, вывод которых нужно сохранять. При ее использовании вывод в файл при редиректе в файл будет происходить без буфферизации, как при выводе в shell. В итоге можно применять tail -f на файл и следить за процессом исполнения. В противном случае вывод будет записан в файл только после завершения скрипта. Тут подробнее и альтернативы.

-u Force stdin, stdout and stderr to be totally unbuffered.
python -u

Reserved words

33 штуки

and       del       from      None      True
as        elif      global    nonlocal  try
assert    else      if        not       while
break     except    import    or        with
class     False     in        pass      yield
continue  finally   is        raise
def       for       lambda    return

 

Виртуальная среда

Позволяет изолировать разные проекты между собой как в рамках версий python, так и в рамках версий библиотек. Аналог vrf в сетях.

Существует несколько способов создания виртуальной среды python, не говоря даже про docker или виртуализацию уровня VM.

virtualenv
sudo pip3.7 install virtualenv

 

virtualenvwrapper

virtualenvwrapper – упрощает взаимодействие с виртуальными окружениями.

sudo pip3.7 install virtualenvwrapper

После установки добавляем в .bashrc и перезапускаем bash

export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3.7
export WORKON_HOME=~/venv
. /usr/local/bin/virtualenvwrapper.sh

exec bash

Создание виртуального окружения с python3.7 по умолчанию.

mkvirtualenv --python=/usr/local/bin/python3.7 test_env

Удаление виртуального окружения

rmvirtualenv Test

Переход в виртуальное окружение

workon test_env

Выход из виртуального окружения

 deactivate

Просмотр пакетов в виртуальном окружении

lssitepackages
Подключение/импорт библиотек/модулей/классов/функций
  • С помощью import подключаем всю системную библиотеку json
  • С помощью from + import из файла net_query.py (в директории исполнения текущего скрипта) подключаем только функцию model из пользовательской библиотеки net_query.
  • С помощью from + import из файла LoadManager.py импортируем класс LoadManager.
import json
from net_query import model
from LoadManager import LoadManager

При этом мы так же могли бы импортировать весь модуль net_query и наоборот – часть (одну) функций модуля json. В любом случае импорта исполняется весь код модуля, просто:

  • в случае from + import в пространство имен кода импортируется только объекты (переменные) кода конкретной импортируемой функции, а не весь модуль
  • при import всего модуля обращение к объектам идет через модуль (import читает файл с модулем и создает объект этого модуля), а при from + import напрямую к объекту. По этой причине нельзя использовать from + import + * т.к. не будет понятно, откуда появился тот или иной объект в коде.
import statement - A statement that reads a module file and creates a module object.
module object - A value created by an import statement that provides access to the data and code defined in a module.
import sys

sys.argv
sys.version


from sys import argv, version

argv
version


#DO NOT USE from sys import *

В случае длинного названия модуля можно использовать import совместно с as.

import concurrent.futures as cf
Methods
Best practices
  • Использовать Logging function вместо print
  • Использовать Str.format вместо конкатенации
 
Functions
Функции есть встроенные – print(), type(), float(), etc.
 
Функции можно (разумеется) создавать самому. Заголовок называют header, тело – body. Функция должна быть определена перед ее вызовом, иначе будет ошибка NameError: name ‘sdfdsf’ is not defined. Результат функции генерируется через return, иначе в качестве результата будет None.
# The first line of the function definition is called the header; the rest is called the body. The header has to end with a colon and the body has to be indented. The body can contain any number of statements. As you might expect, you have to create a function before you can execute it.

def fctn():
    print("lol")
В функции желательно добавлять docstring, если есть риск того, что по названиям переменных будет сложно понять стороннему человеку, что делается в этой функции.
 
def some_funct(a,b):
'''some description of function'''
return(a+b)

print(some_funct.__doc__)
some description of function

Аргументы (переменные) в функцию могут передаваться:

  • позиционно – позиция аргумента при вызове функции соответствует описанию функции на входе. Используя * на входе есть возможность приема на входе функции всех переменных или только части и парсинга их уже в функции (по примеру ARGV из системных). Удобство такого подхода – можно написать сразу универсальную функцию, которая работает как с отдельной строкой, так и с массивом данных т.к. строка по умолчанию преобразуется в массив с одним элементом на входе в функцию.
some_funct(a,b)
>> def some_funct(*all_arg):
>> print(all_arg)

>> a=1
>> b=3
>> c=5
>> some_funct(a,b,c)

(1, 3, 5)
>> def some_funct(a,*all_arg):
>> print(a, all_arg)

>> a=1
>> b=3
>> c=5

>> some_funct(a,b,c)

1 (3, 5)
  • по ключу – позиция не важна, важен ключ, можно передавать переменные в произвольном порядке, главное, чтобы на входе функции ключи соответствовали передаваемым. При передаче чисел или значений Boolean крайне удобный функционал. При этом можно только часть переменных передавать по ключу, но обязательно, чтобы та часть, которая передается по ключу, была в конце, а позиционные аргументы в начале. Кроме того есть вариант передачи в функцию словаря вида ключ: значение используя ** при передаче переменных. В таком случае функция сама распарсит значения из ключей, как и при стандартном вызове функции. Используя же ** на входе в функцию можно принимать часть или все переменные в виде словаря.
some_funct(a=a,b=b)
some_funct(a,b=b)
>> def some_funct(**d):
>>
>> d = {"pos_ip": "1.1.1.1000", "exception_debug": True}
>> some_funct(**d)
>> some_funct(pos_ip = "1.1.1.1000", exception_debug = True)
>> some_funct("1.1.1.1000", True)

'1.1.1.1000' does not appear to be an IPv4 or IPv6 address
False

>> def some_funct(a,**all_arg):
>> print(a, all_arg)

>> a=1
>> b=3
>> c=5

>> some_funct(a,b=5,c=7)

1 {'b': 5, 'c': 7}

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

def some_funct(a,b,c=[]):
'''some description of function'''
c.append(a)
c.append(b)
return(c)

a = 2
b = 3
print(some_funct(a,b))
print(some_funct(a,b))
print(some_funct(a,b))
[2, 3]
[2, 3, 2, 3]
[2, 3, 2, 3, 2, 3]
VAR basic (type, ID)
  • type(var) # узнаем тип переменной
~$ python3
>>> type("Hello weril!")
<class 'str'>
>>> type(666)
<class 'int'>
>>> type(666.6)
<class 'float'>
>>> type("666.6")
<class 'str'>
>>> type(True)
<class 'bool'>
>>> type(Funct)
<class 'function'>
>>> type(None)
<class 'NoneType'>
  • str(var) # преобразование в string
  • int(var) # преобразование в integer. Перед преобразованием с помощью метода isdigit можно проверить, что данные являются числом (не содержат, например, символов).
>>> var1="10"
>>> var1.isdigit()
True
>>> var1="10a"
>>> var1.isdigit()
False
  • float(var) # преобразование в float (число с плавающей ТОЧКОЙ, не запятой)
    • -str преобразование строки в int, строка должна состоять из цифр
    • -int числа в int. особо не нужно тк при операциях инт с флоат происходит автоматическая трансляция инта во флот, да даже при делении двух инт реззультатом является флоат – в Python3 (не Python2, там он отбрасывает все после точки)
  • id(var) смотрим адрес переменной в памяти. Чаще всего используется только в учебных целях чтобы показать, что разные переменные ссылаются на одно место памяти при равенстве. Лучше всего демонстрируется на списках т.к. они в python изменяемые, в отличии от, например, чисел. При присвоении другого числа/строки переменной всегда переменная создается заново, ей назначается новый участок памяти, а для списка в памяти по старому адресу меняется значение. Причем какой-то родительской связи между копируемым и скопированным элементом не существует – обе переменных просто ссылаются на один участок памяти, который может изменяться при изменении любой из переменных. По этой причине копирование массивов в общем случае бессмысленно и имеет смысл только при использовании метода copy – при его использовании для новой переменной создается отдельный учаток памяти, но и он имеет подводные камни – не копирует вложенные массивы (оставляет только основной). Для этого обычно используют метод deepcopy.
# integer/string
>>> var1=1000 # нужно использовать числа отличные от -5 до 256 т.к. уже хранятся в памяти (одно число всегда имеет один и тот же адрес в памяти - не создается заново), в отличии от чисел вне диапазона (каждый раз создаются в памяти заново)
>>> var2=var1
>>> id(var1)
4359970544
>>> id(var2)
4359970544

>>> var1=1001
>>> id(var1)
4359970480
>>> id(var2)
4359970544

# array
>>> var1=[1,2]
>>> var2=var1
>>> id(var1)
4360043528
>>> id(var2)
4360043528

>>> var1[0]=3
>>> id(var1)
4360043528
>>> id(var2)
4360043528
>>> var2
[3, 2]
>>> var2.append(555)
>>> var2
[3, 2, 555]
>>> var1
[3, 2, 555]
>>> var3=var2.copy()
>>> id(var2)
4317072392
>>> id(var3)
4317830152
>>> var2.append(666)
>>> var2
[3, 2, 555, 666]
>>> var3
[3, 2, 555]
User Input
input() # пауза и чтение пользовательского ввода, результат в виде string
var = input("what are you? ") # raw_input Python2
print ("Welcome", var)

 

DIR

dir(var) – с помощью метода dir можем узнать какие методы возможно применить к объекту. Определяется типом объекта

  • к строке строковые методы
  • к целому числу – целочисленные
var = "test"
print(dir(var))
...['capitalize', 'casefold', 'center', 'count', 'encode']...

var = 1
print(dir(var))
...['from_bytes', 'imag', 'numerator', 'real', 'to_bytes']...

 

 

File

read (open default)

описание первой строки: open возвращает handle (курсор) файла на чтение (не указано ничего кроме названия, по умолчанию чтение), read непосредственно читает файл, rstrip удаляет мусор с конца файла, split разделяет строки на отдельные элементы массива (списка). Как альтернатива в теории можно было использовать метод readlines (читает файл и создает массив из каждой строки), но он по практике чаще всего хуже т.к. whitespace символы с конца строки им не удаляются. Более классический подход – делать функцию открытия/итерирования по строкам не в одну строку, из минусов – больше строк, из плюсов – в случае если обхем данных большой мы еще до добавления строки в массив можем ее отбросить и не расходовать оперативную память.

def parse_file_to_list(fname):
l = open(fname).read().rstrip().split("\n")
return(l)

# classic read to array/list
l = []
with open("test.txt") as f:
for line in f:
l.append(line.rstrip())
#берем login из файла, удаляем из него мусор (\n), кодируем в байт-строку
lgn = open('/home/redkin_p/.duser').read().rstrip().encode('utf-8')
#берем большой файл, кладем его в массив (каждая строка как элемент]), ищем в каждом элементе определенный паттерн (аналог ruby .select), выдергиваем из подпавших строк последний столбец (последний элемент каждого массива) и помещаем его в новый массив
data_list = open('/home/redkin_p/ORANGE').read().rstrip().split("\n")
data_list = list(x for x in data_list if "DGS-3620" in x)
ip_list = []
for s in data_list:
ip_list.append(s.split("\t")[-1].replace(" ", ""))
print(ip_list)

write (open with w – write, a – append)

w – перезатирает файл и пишет контент в него

a – добавляет содержимое в существующий файл

# string
def write_to_file(text, fname):
with open(fname, 'a') as the_file:
the_file.write(text)

write_to_file(text, fname)

# array
with open('/home/redkin_p/result.csv', 'a') as the_file:
for n,e in enumerate(interfaces):
res = '{};{};{};{}\n'.format(e, broadcast[n], multicast[n], unknowncast[n])
the_file.write(res)

# bytes
def write_bytes_to_new_file(bytes, fname):
   with open(fname, 'wb') as the_file:
        the_file.write(bytes)

# string with new line and datetime (for logs)
def write_to_file_with_new_line_and_dt(text, fname):
   dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # for log
    with open(fname, 'a') as the_file:
        the_file.write(str(dt) + " " + text + "\n")

delete

import os

os.remove("/home/redkin_p/result.csv")


seek

Перемещает позицию в файле (при итерации по файлу) в самое начало. Нужно редко, но может пригодится (напр. двойное чтение одного файла).

f.seek(0)

 

Полезные методы

Методы по удалению:

import os

def delete_file(fname):
try:
os.remove("{}".format(fname))
 except:
   pass

def delete_files_in_dir(somedir):
res = os.listdir("{}".format(somedir))
for fname in res:
fname_full_path = "{}/{}".format(somedir,fname)
delete_file(fname_full_path)

Методы по проверке наличия (isfile или exists)/ненулевому размеру (st_size != 0):

import os

def check_file_existance_boolean(file):
if os.path.isfile(file) and os.stat(file).st_size != 0:
return True
else:
return False

def check_file_existance(file):
if os.path.isfile(file) and os.stat(file).st_size != 0:
pass
else:
print("Error: нет файла {} или он имеет нулевой размер".format(file))
quit()

 

Расчет MD5 файла

import hashlib

def md5(fname):
hash_md5 = hashlib.md5()
with open(fname, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()

 

CSV

Парсинг csv (отброс header, delimiter) с функцией reader.

import csv

with open('file.csv') as f:
reader = csv.reader(f, delimiter=';')
headers = next(reader)
for row in reader:
     print(row)

Можно так же перевернуть массив (чтение с конца).

l = reversed(list(csv.reader(f, delimiter=';')))
for row in l:

Использование библиотеки для некоторых кейсов лучше простого split.

  • можно использовать csv.DictReader вместо обычного reader для сопоставления значений в header (главное чтобы он был или в файле или заданным опцией fieldnames=[]) конкретным элементам без своего кода.
  • данные ниже будут корректно разбиты по элементам при использовании библиотеки т.к. используемый разделитель “;” находится внутри кавычек.
ip;10.10.10.10;"Узловая 1-ая д.1; чердак"

Записываем в файл список в виде строки с разделителями с функцией writerow для объекта writer. Можно использовать опцию quoting (QUOTE_NONNUMERIC, ALL) для обозначения тех данных, которые должны быть в скобках.

writer = csv.writer(f, delimiter=';', quoting=csv.QUOTE_NONNUMERIC)
for line in data:
writer.writerow(line)

Так же можно записать большое количество данных скопом, используя функцию writerows. В виде данных должен быть список списков (каждая строка = отдельный список).

writer = csv.writer(f, delimiter=';', quoting=csv.QUOTE_NONNUMERIC)
writer.writerows(data)

Есть и аналогичная reader функция DictWriter, которая позволяет записать словарь (формат аналогичный получаемому из DictReader) с использованием writerow/writerows. При использовании есть особенность – header нужно записывать отдельно (writeheader()), даже если он задан в fieldnames к объекту.

 

Directory (директории)

Смотрим файлы в директории

import os

for fname in os.listdir( sys.argv[1] ):
print("{}".format(fname))

 

Date

Datetime

import datetime
print(datetime.datetime.now())

Date-time to string

datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S") # for file_names
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # for general usage

Today

import datetime
print(datetime.date.today())

 

Math (математика, статистика)

Отходящую от базовой калькуляции можно делать со стандартной библиотекой math. Далее, по аналогии с re (regexp), можно вызывая модуль объект math применять функции (или извлекать данные, например значение числа pi), определенные в этом модуль-объекте через точку (dot-notation).

import math

math.pi
math.sin(radians)
math.log10(ratio)
math.sqrt(2)

The module object contains the functions and variables defined in the module. To access one of the functions, you have to specify the name of the module and the name of the function, separated by a dot (also known as a period). This format is called dot notation.
dot notation - The syntax for calling a function in another module by specifying the module name followed by a dot (period) and the function name.

Среднее значение (average/mean value) для списка (массива).

import statistics

first_values_avr = statistics.mean(first_values)
last_values_avr = statistics.mean(last_values)

Коэффициент корреляции (correlation coefficient).

import numpy

#a = list(map(int, a)) # example for data from file
#b = list(map(int, b)) # example for data from file
a = [1,4,6]
b = [1,2,3]
print(numpy.corrcoef(a,b)[0][1])
Количественная мера
тесноты связи
Качественная характеристика
силы связи
0,1-0,3 Слабая
0,3-0,5 Умеренная
0,5-0,7 Заметная
0,7-0,9 Высокая
0,9-0,99 Весьма высокая

Линия тренда в математическом виде (судя по всему рассчитанная на основе метода наименьших квадратов).

 

 

Комментарии

В целом о комментариях и их использовании тут.

Simple comment:

# comment

 

Random

Библиотека random позволяет генерировать псевдо-случайные числа.

The random module provides functions that generate pseudorandom numbers (which I will simply call "random" from here on).

Функция .random к объекту random генерирует случайное число от 0.1 до 1.

The function random returns a random float between 0.0 and 1.0 (including 0.0 but not 1.0).

import random

random.random()

Функция .randint к объекту random генерирует случайное int число от заданного x до y.

The function randint takes the parameters low and high, and returns an integer between low and high (including both).

random.randint(3, 10)

Функция .choice к объекту random выбирает случайный элемент из массива.

To choose an element from a sequence at random, you can use choice

random.choice([1, 4, 2, 5])

 

Кодировка (Coding), utf-8, ascii, encode, decode, ENCODING

Подробнее о таблицах символов ascii/unicode и кодировках utf-8/16/32 в словаре.

Всегда лучше указывать кодировку в header файла. Может помочь от ошибок типа “Non-ASCII character ‘\xc2’ in file” и открывать файлы со скриптами в utf-8 на Windows без преобразований в другие кодировки.

# -*- coding: utf-8 -*-

Для кодирования символов в byte строку используем encode. Можно применять на саму строку или на класс str. Если символы не поддерживаются в таблице символов – будет ошибка. Можно указать разные опции (костыли), что делать с символами, которые не распознаются (ignore, replace, namereplace).

>>> print("привет".encode("utf-8"))
b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

>>> print(str.encode("привет","utf-8"))
b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

>>> print("привет".encode("ascii"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

>>> print("привет".encode("ascii","ignore"))
b''
>>> print("привет".encode("ascii","replace"))
b'??????'
>>> print("привет".encode("ascii","namereplace"))
b'\\N{CYRILLIC SMALL LETTER PE}\\N{CYRILLIC SMALL LETTER ER}\\N{CYRILLIC SMALL LETTER I}\\N{CYRILLIC SMALL LETTER VE}\\N{CYRILLIC SMALL LETTER IE}\\N{CYRILLIC SMALL LETTER TE}'

Для декодирования символов из byte строки используем decode. При декодировании нужно знать исходную кодировку, иначе (если кодировка кодирования и декодирования не совпадают, например utf-8 – utf-16) могут декодироваться кракозябры. Поэтому желательно указывать кодировку в header файла (см. выше).

>>> print(b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'.decode("utf-8"))
привет

>>> print(bytes.decode(b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82',"utf-8"))
привет
>>> print(b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'.decode("utf-16"))
뿐胑룐닐뗐苑

По умолчанию кодировка для Linux/MAC – UTF-8, для Windows – зависит от языка системы, в случае русского это cp1251.

Lin
>>> import locale

>>> locale.getpreferredencoding()
'UTF-8'

Win
>>> import locale
>>> locale.getpreferredencoding()
'cp1251'

Большинство библиотек, которые работают с внешними данными (файлы, подключения по сети и к базам данными) позволяют указать кодировку данных непосредственно при вызове. Например, subprocess, file. Каким то библиотекам данные обязательно нужны в виде byte (telnetlib).

test = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') # декодируем полученные байты в строку 

>>> open('/home/redkin_p/sw')
<_io.TextIOWrapper name='/home/redkin_p/sw' mode='r' encoding='UTF-8'>

Особенность python – байт строку с ascii символами python автоматически декодирует (показывает симолы вместо byte) при отображении, при этом не меняя тип. При этом к byte строке можно применить методы строк типо uppper и это создает еще большую иллюзию, что идет работа с с символами, а не байтами. Если символы не ascii  – python показывает сырые байты, как они есть (as is). В случае перемешки ascii и не-ascii происходит отображение в виде символов того, что удалось конвертировать. 

>>> print("hi".encode("utf-8"))
b'hi'
>>> print("привет".encode("utf-8"))
b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

>>> print(type("привет".encode("utf-8")))
<class 'bytes'>
>>> print(type("hi".encode("utf-8")))
<class 'bytes'>
>>> print("hello and привет".encode("utf-8"))
b'hello and \xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
Telnet

При использовании telnetlib с python 3+ обязательно кодируем в байт строку данные иначе будет выдавать ошибку TypeError: ‘str’ does not support the buffer interface (python 3.4) или TypeError: a bytes-like object is required, not ‘str’ (python 3.6). Это можно сделать поставив символ “b” перед строкой или применив к нему метод encode (с кодировкой utf-8/ascii или без указания кодировки). Полученные данные в str декодируем в байт строку через decode.

#!/usr/bin/env python3.6
#coding: utf-8

import telnetlib

lgn = open('/home/redkin_p/.duser').read().rstrip().encode('utf-8') #берем login из файла, удаляем из него мусор (\n), кодируем в байт-строку
pswd = open('/home/redkin_p/.dpass').read().rstrip().encode('ascii') #берем password из файла, удаляем из него мусор (\n), кодируем в байт-строку

tn = telnetlib.Telnet("10.10.10.10") #подключаемся к узлу
tn.read_until("UserName:".encode()) #ждем строку "UserName:"
tn.write(lgn + b"\r") #передаем кодированный в байт-строку login
tn.read_until(b"PassWord:") #ждем строку "PassWord:"
tn.write(pswd + b"\r") #передаем кодированный в байт-строку password
tn.read_until(b"#") #ждем строку "#"
tn.write(b"show switch\r") #передаем необходимый запрос
res = tn.read_until(b"#") #забираем данные, можно так же использовать функцию read_very_eager() на объект telnetlib.
tn.close

print(res.decode('ascii')) #
SSH

Для ssh используем paramiko.

Функции подключения по ssh и отправке команд в созданный connect:

import paramiko

import time

def ssh_login(ssh_ip, ssh_user, ssh_password):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ssh_ip, username=ssh_user, password=ssh_password)
return client.invoke_shell()

def push_ssh_command(ssh_conn, command, timeout, out_buf, encoding):
ssh_conn.send(command)
time.sleep(timeout)
command_result = ssh_conn.recv(out_buf).decode(encoding)
print(command_result)
return command_result

После этого можно делать операции.

Отдельными командами:

push_ssh_command(ssh_conn, 'conf t\n', ssh_timeout, 5000, 'utf-8')

Или набором, объединенным в функции:

def shutdown_if(ssh_conn, ssh_timeout, if):
push_ssh_command(ssh_conn, 'conf t\n', ssh_timeout, 5000, 'utf-8')
push_ssh_command(ssh_conn, 'int {}\n'.format(if), ssh_timeout, 5000, 'utf-8')
push_ssh_command(ssh_conn, 'shutdown', ssh_timeout, 5000, 'utf-8')

 

SCP

Для загрузки файла используем scp.put.

def scp_put(ssh_conn, fromdir, todir):
scp = SCPClient(ssh_conn.get_transport())
scp.put(fromdir, todir)

Для получения файла по scp проще/лучше всего использовать стандартную функцию get. В интернете есть пример с использованием вида получения файла receive, далее чтения content и записи в файл, но по факту в Windows при таком подходе могут быть проблемы с поврежденным tar архивом (crc правильный, но контент поврежден).

Правильно

def scp_get(ssh_conn, fromdir, todir):
scp = SCPClient(ssh_conn.get_transport())
scp.get(fromdir, todir)

Нежелательно

scp = scpclient.Read(ssh.get_transport(), localDir)
content = scp.receive( remoteFile )
if content:
fd = open( '%s/%s' % (localDir, os.path.split(remoteFile)[1]), 'w' )
 fd.write( content )
 fd.close()
Conditional structures

If/else/elif. В случае использования elif не обязателен else (в отличии от Ruby), а подпадание под несколько elif не означает исполнения кода под каждым – только под первым (как ACL в сетевом мире). 

# one-line
if res == "test": print(x)
if "string" in somevar: print("Hit")

# simple
if res == "test":
  print("true")

# if with else
if res == "test":
print("true")
else:
print("false")

# if with else with elif
if res == "test":
  print("true")
elif res == "test2":
pass # do nothing
elif res == "test3":
print("false3")
else:
print("false")

# можно делать конструкции и без else (если не подпадет ни под одно из условий, то не отработает никак)
if res == "test":
  print("true")
elif res == "test2":
print("false")

 

Multiline Code, Variable, Comment (многострочный код/переменная комменты)

Code (полезно когда много всего в одной строке)

from some_module import 1, 2, 3, 4, 5, 6, \
                      6, 7, 8, 9, 10,  \
                      11, 12, 13,14

Comment

# обычный однострочный
''' или """ multiline

Variable

var = '''line1
line2
line3'''

 

Числа (Integer/FLOAT, Hex, dec, bin)

Python even or odd (остаток деления)

number % 2 == 0

Округление python. Вторая переменная определяет количество знаков после запятой.

round(2.65, 1)

Long

Тип long в python 2x использовался для длинных целы чисел. В общем случае при сравнении типов нужно учитывать что число может быть и int и long и не делать exception (если только exception именно то, что вы хотели – не получить длинное целое число).

В python 3x отказались от Long вообще.

Системы исчисления

Для перевода между системами исчисления может помимо описанных способов использоваться метод format, подробнее в описании функции.

bin – dec

Из десятичного в двоичный (binary – 0b):

>>> bin(128)
'0b10000000'

Обратно – из двоичного в десятичный (decimal):

>>> int('10000000', 2)
128
 
hex – dec

Из десятичного в шестнадцатеричный (hex – 0x):

>>> hex(255)
'0xff'

Обратно – из шестнадцатеричного в десятичный (decimal):

>>> int('ff', 16)
255
String

PRINT

Стандартная работа функции print

print (x) # вывод переменной х
print (‘hello world’) # вывод текста hello world (можно как в двойных, так и в одинарных скобках)

Опциональные параметры функции print (аргументы надо передавать как ключевые, не позиционные):

  • символ в конце строки end – например, позволяет делать print без new_line
  • разделитель sep – по аналогии с AWK
  • файл file – по умолчанию std.out, можно сделать std.err, в теории так же можно делать вывод сразу в файл, но лучше не делать
  • параметр flush – полезно в циклах (печатать сразу после итерации, к примеру, реализация таймера sleep при print на одном line)
>> print("no_new_line", end='&')
no_new_line&
>> print("new_sep", "test", sep=';')
new_sep;test
for i in range(10):
print(i, end=" ", flush=True)
time.sleep(1)

0 1 2 3 4 5 6 7 8 9

replace позволяет сделать замену всех символов (по умолчанию), которые подпадают под первое условие, с регулярными выражениями не работает:

.replace("\x08", "")

В третьем аргументе replace можно указать количество подпадений, которое нужно поменять (чаще всего нужно первое):

.replace("\x08", "", 1)

in позволяет сделать поиск строки в подстроке:

if "string" in somevar: print("Hit")

if "vlan" in "switchport access vlan 666":
print("Yep")

Простая конкатенация с print через + и ,:

print ("Hello"+name+"master") # конкатенация статичного текста и переменных без пробела
print ("Hello", name, "master") # конкатенация статичного текста и переменных через пробел

Конкатенация через .format, довольно удобно, хотя и в начале кажется что хуже ruby string interpolation

res = '{};{};{};{}\n'.format(e, broadcast[n], multicast[n], unknowncast[n])

Но .format очень функциональный – к примеру, можно:

  1. указывать размер столбца по количеству символов в каждом (как в bash printf),
  2. делать выравнивание текста (влево <, вправо >):
  3. указывать название переменных (удобно когда их много и/или они похожего формата данные)
  4. исключать дублированные данные с использованием именование или индексов
  5. переводить из одной системы исчисления в другую
  6. используя * передавать данные не в виде отдельных элементов, а всем массивом
1-2
var = "{:<2} {:20} {:4} {:4}".format(1, "FastEthernet0/1", "down", "down")
print(var)
var = "{:<2} {:20} {:4} {:4}".format(2, "FastEthernet0/10", "down", "down")
print(var)

1 FastEthernet0/1 down down
2 FastEthernet0/10 down down



3
var = "{num:<2} {int:20} {pstat:4} {dstat:4}".format(num=1, pstat="down", dstat="down", int="FastEthernet0/1")
print(var)

1 FastEthernet0/1 down down


4
var = "{num:<2} {int:20} {dstat:4} {dstat:4}".format(num=1, dstat="down", int="FastEthernet0/1")
print(var)

1 FastEthernet0/1 down down


var = "1 FastEthernet0/1 down down"
var = "{0:<2} {2:20} {1:4} {1:4}".format(1, "down", "FastEthernet0/1")

1 FastEthernet0/1 down down


5
var = "{:b}.{:b}.{:b}.{:b}".format(192, 168, 1, 1)
var = "{:08b}.{:08b}.{:08b}.{:08b}".format(192, 168, 1, 1)

11000000.10101000.1.1
11000000.10101000.00000001.00000001


6
temp = "{} {} {}"
values = ["1", "2", "3"]
res = temp.format(*values)

1 2 3

Для конкатенации так же можно использовать f-strings (с python 3.6), он поддерживает максимально простой способ string interpolation – прямо в тексте (как в ruby):

>>> name = "Petr"
>>> print(f"Hello, {name}!")
Hello, Petr!

Можно еще так же множить текст, умножив str на int

>>> first = 'Test '
>>> second = 3
>>> print(first * second)
Test Test Test

Downcase/lowercase, upcase/uppercase. Тут и в аналогичных случаях можно вызывать метод напрямую на переменную, а можно на объект str, передав переменную в метод.

var.lower()
var.upper()

str.lower(var)
str.upper(var)

strip – удаляет все whitespace символы в начале и конце строки. rstrip удаляет только справа (в конце строки), lstrip только слева. В качестве аргумента можно указать символ(ы), которые будут удаляться.

out = "[- vlan1 --]"
print(out.strip(" [-]"))

vlan1

 

 

Вместо регулярных выражений для извлечения данных зачастую можно и нужно использовать (достаточно использовать): Find, Startswith (ниже).

FIND

Можно найти позицию определенной строки/буквы в заданной строке, а потом извлечь данные используя slice и номер позиции.

s = "hello.my@weril.me"
pos = s.find("@")
print(pos)
pos2 = s.find(".", pos) # второй аргумент в find определяет стартовую позицию, иначе в выборку попал бы индекс 5 вместо 14
print(pos2)
res = s[pos+1:pos2]
print(res)

# test.py
8
14
weril
Startswith

Можно сделать поиск по началу строки не только с помощью regexp:

line = "From weril@me.com"
if line.startswith("From "):
print("choto")
SPLIT

split – по умолчанию split разделяет строку по whitespace (пробел/etc, причем любое количество whitespace рассматривается как одно целое) на массив (список), но можно конкретно указывать конкретный символ.

line.split()
line.split("\n")

>>> line="some more line exist"
>>> line.split()
['some', 'more', 'line', 'exist']

В сравнении с regexp split будет работать быстрее/занимать меньше строк кода/будет более понятным по коду:

  • для ^ или $ использовать split()[0] с доступом к первому или последнему элементу
x.split()[0]
  • для того же, после определенного pattern можно использовать двойной split (например, для получения числа, окруженного двумя уникальными для строки pattern – blabsdfsdfbla;user_id:666;user_class:admin;blablalblsdfsdf)
The double split pattern - Sometimes we split a line one way, and then grab one of the pieces of the line and split that piece again.

x.split(";user_id:")[1].split(";user_class:")[0]

Case insensitive

res = re.sub(r".*(cve[,_-]\d\d\d\d[,_-]\d\d\d\d?\d)", '', x, flags=re.IGNORECASE)

При этом для объекта regexp есть split, который позволит решить те задачи, которые сложнее/невозможно решить обычным split.

REGEXP
  • База про regexp с примерами. Там в том числе описаны флаги (напр. DOTALL).
  • regexp compile (re.compile)
    • для ускорения сейчас не склишком нужен т.к. по факту в современном python сделано ряд операций, по сути делающих compile и не-compile выражения эквивалентными по скорости
    • используется по привычки и для красивого кода (заранее описываем regexp выражение и к нему применяем методы regexp)
    • все методы эквивалентны, только применяются к компилированному объекту, а не объекту re
regex = re.compile(r'som[Ee] reg[Ee]xp')
m = regex.search(line)

Методы

re.search – смотрим, подпадает есть ли regexp в строке, в результате в базовом сценарии получаем true/false (none). Есть так же почти аналогичная функция re.match, которая делает поиск только в начале строки. По факту при подпадении возвращается объект Match, из которого

  • можно получить конкретный match (первое подпадение) или match подпавшей группы используя функцию group (нулевой элемент конкретный match, все последующие относятся к группам); причем в новых версиях python3 (c python 3.6 точно, но не работает в python 3.4) groups использовать не обязательно и можно обратится к match объекту по index для извлечения группы (аналог group: нулевой элемент занят под match, все последующие по номеру подпавшей группы)
  • можно получить подпавшие группы () как отдельные элементы списка, используя groups
  • можно так же получить index start/stop (span) начала/окончания match строки.
import re

line = "sdfsdf fdsf mm 555 fdfm ffsdf weril.me mg 77 sdfsdf 66"
res = re.search("weril.me", line)
print(res)
print(res.group())

$ python3 ./test.py
<re.Match object; span=(31, 39), match='weril.me'>
weril.me

# добавляем в regexp группы и делаем print groups
res = re.search("(weril.me).*(66)", line)
print(res.groups())
print(res.group(2))
print(res[2])

~$ python3.6 bin/test.py
<_sre.SRE_Match object; span=(30, 54), match='weril.me mg 77 sdfsdf 66'>
('weril.me', '66')
66
66

re.sub – замена символов с поддержкой regexp. Поддерживает счетчик количества замен (по умолчанию бесконечность) – при указании заменит только столько раз. Так же поддерживает группы – можно из какого то вывода извлечь с помощью regexp matching group и сделать подмену.

import re

re.sub(r".*interface gei-0/1/1/", '', text)
re.sub(r".*interface gei-0/1/1/", '', text, count=10)

re.sub(r"(\d+) (\d+)", r'\1:\2', text)

re.findall – получение данных из match. Причем в результате не только первый/последний match, а все в виде list. Есть так же почти аналогичная функция re.finditer, которая возвращает итератор с объектами Match (в разных случаях разный- list/set list, но в любом случае итератор). Используя группировку () (подробнее в статье про regexp) можно получать только часть данных из match:

import re

line = "sdfsdf fdsf mm 555 fdfm ffsdf mg 77 sdfsdf 66"
res = re.findall("[0-9]+", line)
print(res)

$ python3 ./test.py
['555', '77', '66']


line = "kakayato chush = 0.432423"
res = re.findall("^kakayato chush = [0-9.]+", line)
print(res)

$ python3 ./test.py
['kakayato chush = 0.432423']

line = "kakayato chush = 0.432423"
res = re.findall("^kakayato chush = ([0-9.]+)", line)
print(res)

$ python3 ./test.py
['0.432423']

line = "From toster@weril.me at Sat Jan 13 01:01:01 2019"
res = re.findall("^From .*@([^ ]*)", line)
print(res)

$ python3 ./test.py
['weril.me']

re.split – для объекта regexp есть split, который позволит решить те задачи, которые сложнее/невозможно решить обычным split.

re.split(r' +', text)
re.split(r'[ ,|]', text)
Кодировки
  • Строка в python3 имеет по умолчанию кодировку unicode, в отличии от python2.
  • В python2 для преобразования в unicode нужно перед строкой ставить u.
  • В python3 нужно преобразовывать данные, полученные из сети.
Python 3.7.2
>>> line='line'
>>> type(line)
<class 'str'>
>>> line=u'line'
>>> type(line)
<class 'str'>

Python 2.7.10
>>> line='line'
>>> type(line)
<type 'str'>
>>> line=u'line'
>>> type(line)
<type 'unicode'>
 
Array/LIST/TUPLE/DICTIONARY/set (массивы, списки, словари, кортежи, множества)
  • До python 3.6 словари были неупорядоченны – т.е. последовательность элементов после создания могла меняться. Начиная с python 3.6 они упорядочены. Полагаться на это не стоит т.к. старые версии никуда не пропали и не пропадут в ближайшее время. Если нужно создать последовательный словарь (ordereddict), то можно использовать соответствующую функцию класса Collections при создании массива. Конвертации многомерного массива в ordered нет, но можно это сделать с помощью json.
import collections

# создание нового массива
d1 = collections.OrderedDict()
d1['a'] = 'A'
d1['b'] = 'B'
d1['c'] = 'C'
d1['d'] = 'D'
d1['e'] = 'E'
print(d1)

# конвертация многомерного массива
json_pattern = json.loads(json_pattern, object_pairs_hook=collections.OrderedDict)

Про особенности копирования массивов делай поиск по deepcopy. Если в кратце – копирование простым равно не работает, нужно использовать метод deepcopy.

Длина массива или строки (length)

len(arr)

Количество определенных элементов в массиве

arr = [ 1, 2, 3, 1, 5, 1]
print( arr.count(1) )

3

Максимальный/минимальный элемент (работает для элементов в виде str, так и для int; работает не только для array, но и просто для строки)

max(arr)
min(arr)

Индекс искомого элемента массива

>>> var3
[3, 2, 555, 2]
>>> var3.index(555)
2

Сумма элементов массива (должны быть int/float). Если нет – может помочь метод ниже.

sum(arr)

Разделение массивов на две части используя slice (split in two parts/half).

count = len(values)
first_values = values[:count//2]
last_values = values[count//2:]
print(count, len(first_values), len(last_values))

Конкатенация нескольких массивов (списков, кортежей, множеств). Аналогична возможна разность.

>>> a=[1,2]
>>> b=[3,4]
>>> a+b
[1, 2, 3, 4]

Переворачивание (reverse) массива.

reversed(l)

Поиск элементов по шаблону в списке/массиве и возврат нового списка/массива (аналог ruby .select).

data_list = list(x for x in data_list if "DGS-3620" in x)

Преобразование всех элементов массива в int. Для этого можно использовать генераторы, а можно map.

results = map(int, results)
results = [int(x) for x in results]

Список кортежей или словарь (hash) можно создать из объединения двух списков с помощью функции zip. Для списка кортежей можно zip’ить и большее количество list, не только два.

arr1 = [1,2,3]
arr2 = ["kaka", "maka", "myka"]
print(list(zip(arr1,arr2)))
[(1, 'kaka'), (2, 'maka'), (3, 'myka')]

print(dict(zip(arr1,arr2)))
{1: 'kaka', 2: 'maka', 3: 'myka'}

arr3 = ["l", "k", "m"]
print(list(zip(arr1,arr2,arr3)))
[(1, 'kaka', 'l'), (2, 'maka', 'k'), (3, 'myka', 'm')]

Tuple (кортеж) – по сути тот же самый list, только его нельзя изменять и сортировать. Поэтому он быстрее/занимает меньше памяти.

>>> a1
[1, 3, 5]
>>> t1
(1, 3, 5)
>>> import sys
>>> sys.getsizeof(a1)
88
>>> sys.getsizeof(t1)
72

Используются всегда, когда могут с учетом “недостатков”. Часто используются для временных переменных, для данных, которые хочется иметь в исходном виде (например, данные выгруженные из БД).

Tuples is that they're not mutable and that allows them to be stored more densely than lists. Whatever order you put the tuple in when you create it, it stays in that. You can't append, extend, flip, sort it.

We tend to use tuples when we can.

tup = ("raz", "dvas")
print(tup[0])
>> raz
print(max(tup))
>> raz
print(len(tup))
>> 2
tup[0] = 2
>> Traceback (most recent call last):
File ".\test.py", line 5, in <module>
tup[0] = 2
TypeError: 'tuple' object does not support item assignment

Из list можно получить кортеж, используя метод tuple (справедливо и обратное с помощью метода list).

>>> a = [1, 2]
>>> tuple(a)
(1, 2)

Кортеж из одного элемента можно создать поставив запятую после него, иначе интерпретатор будет думать, что это число в скобках. Аналогично и с операциями с tuple – например, добавление элемента.

>>> t1=(1)
>>> t1
1
>>> type(t1)
<class 'int'>

>>> t1=(1,)
>>> t1
(1,)
>>> type(t1)
<class 'tuple'>

>>> t1 = (1,3,5)
>>> t1 + (7,)
(1, 3, 5, 7)

Из dictionary так же можно получить список кортежей, используя метод items (справедливо и обратное с помощью метода list). Подробнее о методе в dictionary. 

>>> a.items()
dict_items([('id', 1), ('name', 'Sesame Street'), ('location', None), ('location2', 'some_loc')])

Кортежи (так же как и списки/множества) поддерживают multiple assignment, что используется при итерациях/выводе с функций.

raz, dvas = ("raz", "dvas")
print(raz)
>> raz
print(dvas)
>> dvas

d = {"id": 1, "name":"Sesame Street"}
for key, value in d.items():
print(key, value)

print(d.items())
dict_items([('id', 1), ('name', 'Sesame Street')])

Кортежи можно объединять.

>>> t1=(1,3,5)
>>> t2=(2,4,6)
>>> t1+t2
(1, 3, 5, 2, 4, 6)
Создание

Для создания списков/словарей и других итерируемых объектов могут применяться генераторы (list comprehension) – например, цикл for внутри списка. Они являются синтетическим сахаром – т.е. не обязательны для создания программы, но зачастую делают код красивее/быстрее.

>>> var1=["line " + str(i) for i in range(10)]
>>> var1
['line 0', 'line 1', 'line 2', 'line 3', 'line 4', 'line 5', 'line 6', 'line 7', 'line 8', 'line 9']

>>> list(range(6,16))
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

>>> ip_correct_list = [ip for ip in ip_list if check_ip(ip)]
array of unique values

Один массив – создаем ряд на основе списка (множество set по умолчанию содержит только уникальные значения), преобразуем обратно в список

>>> a1
[1, 2, 3, 1, 2, 3, 3, 3, 5, 1]
>>> set(a1)
{1, 2, 3, 5}
>>> list(set(a1))
[1, 2, 3, 5]
Объединение массивов (union)

Несколько массивов, через объединение массовой получаем список уникальных значений в обоих массивах (дубли между массивами удаляются). Так же используем множества (set) для этой операции.

def union(a, b):
return list(set(a) | set(b))
return list( a.union(b) ) # альтернативный способ (требует a и b в виде множеств)
Разность (Удаление)

Разность массивов (находим значения, которые уникальны в первом массиве относительно второго):

def remove(a, b):
    return list(set(a) - set(b))
return list( a.difference(b) ) # альтернативный способ (требует a и b в виде множеств)
Пересечение

Пересечение массивов (находим значения, которые есть в обоих массивах):

def intersect(a, b):
   return(list(set(a) & set(b)))
return(list( a.intersection(b) )) # альтернативный способ (требует a и b в виде множеств)
LOOP (WHILE TRUE)
while True:
...
Split

Описывал выше.

Join

Преобразует массив в строку. Что интересно, join – строковой метод, применяется на строку, но в качестве аргумента использует массив.

delim = "%"
arg_str = delim.join(sys.argv[1:])

join не обязательно через переменную указывать, можно непосредственно строку.

>>> L
['a', 'b', 'c']

>>> ''.join(L)
'abc'

data_list = "\n".join(L)
Sleep
import time

time.sleep(1)

Sleep функция с print счетчиком countdown в одной строке на основе. Работает и для Python 3 и для Python 2 (2.7).

# python3 ./test.py
[*] sleep to get some data... 10 9 8 7 6 5 4 3 2 1


/test.py
import sys
import time

def sleep(sleep_interval, reason=""):
sys.stdout.write("[*] sleep{}... ".format(reason))
 for i in range(sleep_interval,0,-1):
  sys.stdout.write(str(i)+' ')
   sys.stdout.flush()
   time.sleep(1)
print("")

sleep(10, " to get some data")
Slice

Получаем от элемента (строки/массива) до элемента, но не включая. Например: получаем первые пять элементов массива (или букв строки). Применений множество.

arr[beginning_of_string/list:end_of_string/list]
arr[:5] #первые пять элементов массива (с нулевого по четвертый)
>>> c
[1, 2, 3, 4, 5, 6]
>>> c[:5]
[1, 2, 3, 4, 5]

arr[0:5] #первые пять элементов массива
s[-3:] #последние 3 элемента
s[:] #копия списка, часто очень полезно
s[1:] #все элементы кроме первого (полезно при получении аргументов и ARGV для исключения названия скрипта)
s[2:-2] #откидываем первые и последние 2
s[::2] #парные элементы
s[1:4:2] #элементы с первого по четвертый с шагом 2
Iteration/Итерация/итерирование

Итерирование работает с итерируемыми (iterable) объектами. Внутри в базовом сценарии (for до окончания элементов) это процесс последовательных next, next, next на один итерируемый объект до exception (StopIteration).

l = [3,4,7,9]
i = iter(l)
print(next(i))
print(next(i))
print(next(i))
print(next(i))
print(next(i))

3

4
7
9
Traceback (most recent call last):
File "/home/redkin_p/bin/test.py", line 11, in <module>
print(next(i))
StopIteration

Без индекса

for sw in sw_list:
print(sw)

Итерация с индексом или значением ключа dictionary (по умолчанию при итерации словаря берутся только ключи) на основе:

  • enumerate – создает кортеж – номер, значение; можно поменять стартовое значение с 0 на кастомное; enumerate полезно использовать для route-map, EEM скриптов, там, где нужно создавать некую последовательность.
  • items – items создает кортеж/tuple вида key, value.
for n,e in enumerate(interfaces):
print(n)

0 str1
1 str2
2 str3

for n,e in enumerate(interfaces, 1):
print(n)

1 str1
2 str2
3 str3
# two iteration variables (key and value) for dictionary (default only key)
for key, value in d.items():

С помощью range можно легко итерироваться, создавать последовательности (см. генераторы). При указании range одного аргумента оно используется в качестве stop, а в качестве start используется 0, так же можно указать start value и шаг. Причем функция хранит только эти значения, а промежуточные вычисляются -> независимо от размера диапазона он будет занимать фиксированный объем памяти. Совместно с range удобно использовать функцию in – можно проверить, есть ли значение в диапазоне. Так же из range можно получать значения элементов, как будто это список, рассчитывать длину диапазона с помощью len, делать slice.

>>> for i in range(5):
... print(i)
...
0
1
2
3
4

>>> list(range(3))
[0, 1, 2]

>> r1 = range(10)
>> r2 = range(5,10)
>> r3 = range(4,10,2)
>> print(list(r1))
>> print(list(r2))
>> print(list(r3))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]
[4, 6, 8]

>> print(10 in range(11))
True

>> print(range(10)[9])
9
>> print(range(10)[0])
0

>> print(len(range(10)))
10
>> print(range(10)[0:5])
range(0, 5)

С помощью range можно создать цифровой словарь (предположим, для bruteforce). Например код ниже создает 8-digit dictionary. В коде сделан костыль для добавления в словарь чисел с leading zero 00000000 – 09999999.

for i in range(100000000):
if (len(str(i))) == 8:
if str(i)[0] == "1":
zero_list = list(str(i))
zero_list[0] = "0"
zero_int = str("".join(zero_list))
print(zero_int)
print(i)

all – выдает True если все элементы True или элемент пустой – что довольно нелогично т.к. пустой объект по умолчанию False (можно проверить функцией bool).

print(all([True, True, False]))
print(all([True, True, True]))
print(all([]))
print(bool([]))

False
True
True
False

any – выдает True если любой элемент True. При этом нулевой элемент считается False, что доставляет (с учетом вышестоящего).

print(any([True, True, False]))
print(any([True, True, True]))
print(any([]))

True
True
False

append – добавление элемента в конец списка

# Одного
sw_list_with_hostname = []
for sw in sw_list:
sw_list_with_hostname.append(sw)

# Нескольких (через вложенный массив)
sw_list_with_hostname = []
for sw in sw_list:
sw_list_with_hostname.append([sw, hostname])

extend – добавление нескольких элементов в конец списка (не вложенного массива в виде одного элемента)

>>> var2
[3, 2, 444, 555, 666]
>>> var2.append(777,888,999)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: append() takes exactly one argument (3 given)
>>> var2
[3, 2, 555, 666, [777, 888, 999]]

>>> var2.extend([777,888,999])
>>> var2
[3, 2, 555, 666, [777, 888, 999], 777, 888, 999]

insert – добавление элементов на определенную позицию (не в конец)

>>> var2
[3, 2, 555, 666]
>>> var2.insert(2,444)
>>> var2
[3, 2, 444, 555, 666]

pop – удаление последнего элемента или произвольного элемента по id

>>> var2
[3, 2, 555, 666, [777, 888, 999], 777, 888, 999]
>>> var2.pop()
999
>>> var2
[3, 2, 555, 666, [777, 888, 999], 777, 888]
>>> var2.pop(3)
666
>>> var2
[3, 2, 555, [777, 888, 999], 777, 888]

remove – удаление элемента по значению (удаляется первый подпавший элемент)

>>> var2
[3, 2, 555, [777, 888, 999], 777, 888]
>>> var2.remove(777)
>>> var2
[3, 2, 555, [777, 888, 999], 888]

filter – фильтрация массива в новый урезанный массив по заданному условию. К примеру, ищем строки, которые можно интерпретировать как число.

>>> a = ["a", "b", "1", "6"]
>>> list(filter(str.isdigit, a))

['1', '6']
Поиск элементов

Самый простой способ:

7 in a # 7 in array?

Можно так же в dictionary (hash/assoc array) по ключу, что зачастую удобно:

if name in names:
names[name] = names[name] + 1
else:
names[name] = 1

Пример

#берем большой файл, кладем его в массив (каждая строка как элемент]), ищем в каждом элементе определенный паттерн, выдергиваем из подпавших строк последний столбец (последний элемент каждого массива) и помещаем его в новый массив
data_list = open('/home/redkin_p/ORANGE').read().rstrip().split("\n")
data_list = list(x for x in data_list if "DGS-3620" in x)
ip_list = []
for s in data_list:
ip_list.append(s.split("\t")[-1].replace(" ", ""))
print(ip_list)

collections

Аналог sort | uniq –c (количество уникальных объектов)

from collections import Counter
input = ['a', 'a', 'b', 'b', 'b']
c = Counter( input )
sorted, sort

sorted – сортировка list (array), более универсальная в сравнении с sort альтернатива, работает не только с list, а с любым итерируемым (iterable) объектом. Не изменяет исходный list, в отличии, опять же, от sort. Кроме того sorted можно использовать на многомерный массив. Сортировка по умолчанию будет по первому элементу в каждом массиве, но этим можно управлять, например, с помощью использования ключевого аргумента key + itemgetter или с помощью key + lambda.

sort – сортировка list (array), работает только с list, изменяет исходный list

flist.sort()
sorted(flist)

>>> var1
[3, 2, 444, 555, 666, 777, 888, 999]
>>> sorted(var1)
[2, 3, 444, 555, 666, 777, 888, 999]
>>> var1.sort()
>>> var1
[2, 3, 444, 555, 666, 777, 888, 999]
arr = [ [1, 22, 55],
[7, 11, 34],
[2, 111, 23],
[31, 11, 24] ]
print(sorted(arr))

[[1, 22, 55], [2, 111, 23], [7, 11, 34], [31, 11, 24]]
import operator

<same_array>
print(sorted(arr, key=operator.itemgetter(1)))

[[7, 11, 34], [31, 11, 24], [1, 22, 55], [2, 111, 23]]

>>> print(sorted(arr, key=lambda x: x[1]))

[[7, 11, 34], [31, 11, 24], [1, 22, 55], [2, 111, 23]]

через равно sort делать не надо, будет ошибка; sorted можно

flist = flist.sort()
TypeError: 'NoneType' object is not iterable

flist = sorted(flist)

reverse с помощью sort (безусловно можно и с sorted)

flist.sort(reverse=True)
sorted(flist, reverse=True)

Сортировка dictionary (hash, assoc array)

по значению (аналогичное можно и с помощью itemgetter)

names = {}
sorted_x = sorted(names.items(), key=lambda kv: kv[1])

по ключу (аналогичное можно и с помощью itemgetter)

names = {}
sorted_x = sorted(names.items(), key=lambda kv: kv[0])

Чуть оффтопика про lambda – это по сути однострочный эквивалент функции (типа генераторов).

def sum(a,b):
    return a+b

lambda a, b: a+b # equal
Python dictionary (key-value store: hash, associative array, property bag)

Python dictionary – наиболее “powerful” memory collection в Python. По сути это key-value store, который реализован в большинстве ЯП (примеры в header). Такое хранилище позволяет создавать простую in-memory “database” в одной переменной.

Создание

a = {"id": 1, "name":"Sesame Street"}
a = dict(id=1, name="Sesame Street") # ключ должен быть str

# через преобразование
list(str)
list(tuple)
list(set)
list(dictionary)

# на основе массива, не использовать если значение будет arr - иначе arr будет единый для всех ключей (не уникален т.к. ссылка на один участок памяти) - подробнее в примере. В таком случае лучше использовать генератор (по аналоги с list).
>>> arr = [1,2,3]
>>> dict.fromkeys(arr)
{1: None, 2: None, 3: None}
>>> dict.fromkeys(arr, "some_data")
{1: 'some_data', 2: 'some_data', 3: 'some_data'}

# пример почему не стоит использовать массив в качестве value при создании с помощью fromkeys
>>> dct = dict.fromkeys(arr, "some_data")
>>> dct
{1: 'some_data', 2: 'some_data', 3: 'some_data'}
>>> dct[1] = "some_new_data"
>>> dct
{1: 'some_new_data', 2: 'some_data', 3: 'some_data'}
>>>
>>> dct = dict.fromkeys(arr, [1])
>>> dct
{1: [1], 2: [1], 3: [1]}
>>> dct[1].append(555)
>>> dct
{1: [1, 555], 2: [1, 555], 3: [1, 555]}

# Ключем dictionary может быть только неизменяемая переменная, содержимое которой не изменит свой hash т.к. все операции "внутри" по ключу происходят именно по hash. Это можно увидеть, попытавшись сделать ключ в виде list.
>>> a = {[1,2]: 1, "name":"Sesame Street"}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Добавление элемента

a["id"] = 1

Обновление элемента (или добавление нового, если такого элемента нет; append for dict)

a.update({'some_key': 555})

Удаление элемента

>>> a = {"id": 1, "name":"Sesame Street"}
>>> a
{'id': 1, 'name': 'Sesame Street'}
>>> del a["id"]
>>> a
{'name': 'Sesame Street'}

Получение значения элемента по ключу.

>>> a["name"]
'Sesame Street'

При отсутствии ключа и потенциальной возможности запроса по несуществующему ключу нужно или использовать try except или использовать метод get (возвращает None при отсутствии элемента или заданное во второй переменной к методу значение), в противном случае будет exception. Еще одной безусловной альтернативой (но хуже get – он по сути делает тоже самое без if) является использование in совместно с if – с помощью in мы сможем посмотреть есть ли ключ в словаре.

The pattern of checking to see if a key is already in a dictionary and assuming a default value if the key is not there so common that there is a method called get() that does this for us.

>>> a["location"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'location'
>>> a.get("location")
>>> a.get("location", "some_val")
'some_val'


# simple histogram without if-else
counts = dict()
items = [1,2,2,3,1,1]
for item in items:
counts[item] = counts.get(item, 0) + 1
print(counts)

~$ test.py
{1: 3, 2: 2, 3: 1}

Так же в таком случае возможно использование функции setdefault. В случае наличия ключа она возвращает его значение, при отсутствии ключа создает его с значением по умолчанию None. В аргументах None можно изменить на что-то конкретное.

>>> a.setdefault("location")
>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None}
>>> a.setdefault("name")
'Sesame Street'
>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None}
>>> a.setdefault("location2", "some_loc")
'some_loc'
>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None, 'location2': 'some_loc'}

Методами keys, values, items можно получить/преобразовать данные исходного словаря в списки. При использовании этих методов нужно учитывать, что они не создают копию данных и при изменении исходного словаря меняются в соответствии с ним. Для защиты от этого результат метода можно преобразовывать в list или работать с копией массива (использовать deepcopy).

>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None, 'location2': 'some_loc'}
>>> a.keys()
dict_keys(['id', 'name', 'location', 'location2'])
>>> a["new_id"] = 2
>>> a.keys()
dict_keys(['id', 'name', 'location', 'location2', 'new_id'])

>>> b = list(a.keys())
>>> a["new_new_id"] = 2
>>> list(a.keys())
['id', 'name', 'location', 'location2', 'new_id', 'new_new_id']
>>> b
['id', 'name', 'location', 'location2', 'new_id']

keys – получить все ключи.

>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None, 'location2': 'some_loc'}

>>> a.keys()
dict_keys(['id', 'name', 'location', 'location2'])

values – получить все значения.

>>> a.values()
dict_values([1, 'Sesame Street', None, 'some_loc'])

items – преобразовать словарь в список кортежей.

>>> a.items()
dict_items([('id', 1), ('name', 'Sesame Street'), ('location', None), ('location2', 'some_loc')])

update – 1) обновляем значения ключей при наличии ключей в существующем массиве 2) создаем пары ключ-значение при отсутствии ключа в существующем массиве

>>> a = {'id': 1, 'name': 'Sesame Street', 'location': None}
>>> a
{'id': 1, 'name': 'Sesame Street', 'location': None}
>>> b = {'id':2, 'password':123456}
>>> a.update(b)
>>> a
{'id': 2, 'name': 'Sesame Street', 'location': None, 'password': 123456}

 

Python set (множества)

Содержит уникальные значения. Часто используется для задач слияния/разности массивов.

>>> set('tetst')
{'e', 's', 't'}
>>> set([1,2,"ha",2,3,1])
{1, 2, 3, 'ha'} 

Для множеств есть аналогичные list методы только с другими названиями.

>>> s1 = {1,3,5,7}
>>> type(s1)
<class 'set'>
>>> s1.add(9)
>>> s1
{1, 3, 5, 7, 9}
>>> s1.discard(3)
>>> s1
{1, 5, 7, 9}
>>> s1.discard(3) # нет exception в случае отсутствия значения в множестве

Как и в случае с кортежами/словарями из списка/кортежа/словаря можно получить множество и наоборот. Это свойство может использоваться для чистки дублей в списке – преобразовываем его во множество, а потом обратно в список.

>>> s1
{1, 5, 7, 9}
>>> list(s1)
[1, 5, 7, 9]

>>> a1
[1, 2, 3]
>>> set(a1)
{1, 2, 3}

>>> a1
[1, 2, 3, 1, 2, 3, 3, 3, 5, 1]
>>> set(a1)
{1, 2, 3, 5}
>>> list(set(a1))
[1, 2, 3, 5]

Нельзя обращаться по индексу к set т.к. в set не сохраняется позиция элемента. Обходим через list(set).

res_set = set(res_arr)
print(res_set[0])
TypeError: 'set' object is not subscriptable

uniq_res_arr = list(set(res_arr))
print(uniq_res_arr[0])

Thread

Многопоточность можно запустить очень просто

from multiprocessing.dummy import Pool as ThreadPool 

def query_data(ip):
print(ip)

pool = ThreadPool(10) # количество потоков
pool.map(query_data, sw_list) # запускаем функцию query_data и передаем ей на входе массив sw_list

Если аргументов в функцию нужно передать несколько, можно создать двумерный массив и далее:

1) передать его starmap вместо одномерного и наслаждаться стандартной функцией

from multiprocessing.dummy import Pool as ThreadPool 

def query_data(ip, hostname):
print(ip, hostname)

pool = ThreadPool(10) # количество потоков
sw_list_with_hostname = [] # создаем новый массив вида [[ip, hostname], [ip2, hostname]]
pool.starmap(query_data, sw_list_with_hostname) # запускаем функцию query_data и передаем ей на входе массив sw_list_with_hostname

2) передать его pool.map вместо одномерного и разбирать на входе функции (менее удобно)

from multiprocessing.dummy import Pool as ThreadPool 

def query_data(data):
ip = data[0]
hostname = data[1]
print(ip)

pool = ThreadPool(10) # количество потоков
sw_list_with_hostname = [] # создаем новый массив вида [[ip, hostname], [ip2, hostname]]
for sw in sw_list:
sw_list_with_hostname.append([sw, hostname])
pool.map(query_data, sw_list_with_hostname) # запускаем функцию query_data и передаем ей на входе массив sw_list_with_hostname
ARGV

Получаем данные из системных переменных в виде array – путь к исполняемому файлу (всегда в argv[0]) и далее опционально (что передали) – имена файлов, адреса (IP, mail, url), все что угодно.

import sys

print(sys.argv)
print(sys.argv[1])
quit()

Для этой задачи можно так же использовать argparse (есть и другие альтернативы) – позволит сделать аналогию linux утилитам.

python ping_function.py -a 8.8.8.8 -c 5

$ python ping_function.py
usage: ping_function.py [-h] -a IP [-c COUNT]
ping_function.py: error: the following arguments are required: -a
$ python ping_function.py -h
usage: ping_function.py [-h] -a IP [-c COUNT]

Ping script

optional arguments:
  -h, --help  show this help message and exit
  -a IP
  -c COUNT
Exception

offtop: зачастую вместо exception handling лучше использовать guardian pattern – перед опасным участком кода делать необходимые для корректного исполнения кода проверки. Их можно делать в отдельных if блоках или составляя логическое выражение, где первыми элементами проверяются guardian, а в последующем необходимая логика (последовательность важна, иначе guardian не сработает и будет exception). Подробнее о guardian pattern и short-circuit evaluation можно прочитать тут.

# Guardian
if len(a) < 0:
  quit()
if a[0] == "Added":
    print(a)

# Guardian in compound statement
if len(a) > 0 and a[0] == "Added":
print(a)
Exception raise (вызов)

Raise можно использовать для генерации exception.

if os.path.exists(workDir):
   raise Exception("Error: Directory already exist.")
exception Handling (обработка)

Отработка на KeyBoardInterrupt exception, далее на ZeroDivisionError. Ниже так же можно было бы добавить блоки else (исполнение в случае если exception не сработал), finally (исполнение в любом случае – сработал exception или нет).

try:
print("here is untrusted code")
except (KeyboardInterrupt, SystemExit):
 print("KeyBoard exception")
except ZeroDivisionError:
print("Do not divide by zero")

Отработка на все ошибки (так не рекомендуется делать), без сбора информации о ошибке

try:
print("here is untrusted code")
except: # catch *all* exceptions
print("exception")

Отработка на все ошибки, со сбором информации о ошибке

try:
print("here is untrusted code")
except Exception as e:
print("exception")

Повтор операции N раз в случае exception легко делается через связку for + break.

How to retry after exception?
https://stackoverflow.com/questions/2083987/how-to-retry-after-exception
Do a while True inside your for loop, put your try code inside, and break from that while loop only when your code succeeds.
exception_threshold=4
for i in range (1,exception_threshold+1):
result = some_operation()
if result == True:
break
if i == exception_threshold:
raise Exception(result)
print("[{}/3] caught exception".format(i), result)

Retiries до success делается с помощью while True + rescue + break:

       while True:
           try:
            <CODE>
           except Exception as e:
          sleep_fun(10, " caught exception ({}), trying again".format(e))
continue
          break

В функции с exception можно заложить режим debug (писать exception) по передаваемой опции.

def check_ip(pos_ip, exception_debug=False):
'''check ip function'''
try:
ipaddress.ip_address(pos_ip)
exceptValueErroras e:
if exception_debug: print(e)
return(False)
return(True)

check_ip(ip, exception_debug=True)
execution timeout

В python есть ряд разных реализаций execution timeout разной степени “поршивости” – что-то например не будет работать в Windows, что-то переодически подвисать из-за использования thread, вызова дочерних процессов и каких то еще проблем. Чаще всего работают через декоратор перед функцией. Наиболее стабильной функцией в этом контексте была timeout опция для subprocess и timeout утилита для linux – т.е. не питоновские “исходные” функции вовсе.

Способы, которые пробовал (первые более менее – последние совсем “зашквар”:

1. Способ wrapt-timeout-decorator работает в виде декоратора (перед функцией) и не имеет недостатков выше – возвращает как значения при успешном исполнении, не применяется на другие функции. Но есть недостаток в том, что процесс дочерний (напр. функция с timeout запускает subprocess) остается жив, что потенциально может приводить к формированию zombie.

pip3 install wrapt-timeout-decorator
import time
from wrapt_timeout_decorator import *
@timeout(5)

def mytest(message):
   print(message)
   for i in range(1,10):
       time.sleep(1)
       print('{} seconds have passed'.format(i))
    return "puk"

def mytest2(message):
   print(message)
   for i in range(1,10):
       time.sleep(1)
       print('{} seconds have passed'.format(i))
    return "puk"

res = mytest2('starting')
print(res)

2. timeout-decorator, не работает на Windows в режиме по умолчанию.

import time
import timeout_decorator

@timeout_decorator.timeout(5) # для Linux передаем timeout(5, use_signals=False)
def mytest():
print("Start")
for i in range(1,10):
  time.sleep(1) print("{} seconds have passed".format(i))
if __name__ == '__main__': mytest()
  1. 3. Метод со stackoverflow работает и не требует установки модулей и зависимый библиотек (были с этим проблемы). Может зависать сам (ждет пользовательского ввода).

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from threading import Thread
    import functools

    def timeout(timeout):
    ...
    ...
    ..

4. Не очень способ т.к. он глобальный и если в коде есть функции, которые должны долго исполнятся, а другие нет – не подойдет.

import signal
import time

def handler(signum, frame):
   print("Forever is over!")
    raise Exception("end of time")

def loop_forever():
   while 1:
       print("sec")
        time.sleep(1)

def loop_forever2():
   while 1:
       print("sec2")
        time.sleep(1)

signal.signal(signal.SIGALRM, handler)
signal.alarm(10)
try:
  loop_forever2()
except:
    print("some shit")

5. Есть так же вариант с использованием thread, но он так же довольно кривой т.к. в таком случае нужно собирать output из процесса, например, используя shared variables, что усложняет код.

import multiprocessing
import time

# bar
def bar():
   for i in range(100):
       print("Tick")
        time.sleep(1)

# Start bar as a process
p = multiprocessing.Process(target=bar)
p.start()

# Wait for 10 seconds or until process finishes
p.join(10)

# If thred is still active
if p.is_alive():
   print("running... let's kill it...")
   # Terminate
   p.terminate()
   p.join()
p = multiprocessing.Process(target=bar)
p.start()
 
ООП
Классы (CLasses) 

super – функция, которая может использоваться в базовом сценарии для переопределения функции, в наследовании.

Вызов метода внутри класса возможен с помощью self.

def json_gen(self, var1, var2, var3):
...

self.json_gen(var1, var2, var3)

Аналогично через self. можно передавать переменные между методами класса – не обязательно их передавать в сам метод в виде переменной.

self.somevar = "test"
OS.SYSTEM (EXTERNAL COMMAND)

С системным модулем subprocess запуск внешних команд похож на обычный shell или запуск в ruby/php через ‘command’. НО есть огромная разница – вывод не сохранить сразу в переменную в python, он только отображается в bash.

В целом общее мнение, что os.system сейчас лучше не использовать, а использовать subprocess.

import sys

os.system("ip netns exec VRF1 ip a | grep 'inet ' | awk '{print $NF}'")
Subprocess (external command)

С системным модулем subprocess запуск внешних команд похож gem Ruby Open3. Subprocess.run появился в новых версиях subprocess, раньше его не было. По практике – всегда, когда возможно, лучше использовать run вместо check_output.

import subprocess

subprocess.run(["ls", "-l"])
subprocess.check_output(["snmpget", "-v", "2c", "-c", "test", "-Ov", "-Oq", ip, oid])

Для длинных команд удобно сначала использовать split и переменную.

cmd = "ip netns exec VRF1 ip a".split()
subprocess.run(cmd)

Вывод STDOUT/STDERR скрыть можно через опции. Для check_output можно скрыть только stderr (stdout аргумент не разрешен для check_output).

import os
import subprocess

null = open(os.devnull, 'w')
subprocess.run(cmd, stderr=null, stdout=null)
subprocess.check_output(cmd, stderr=null)

Получение stdout/stderr в subprocess возможно и с использованием run и с использованием Popen при перенаправлении stdout в PIPE. При использовании check_output stdout изменить нельзя и он не отображается.

test = subprocess.Popen(cmd, stdout=subprocess.PIPE)
data = test.communicate()
print(data)

test = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(data)
print(test.stdout) # вывод в виде byte cтроки
print(test.stdout.decode()) # преобразуем в string
print(test.returncode)

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

Вместо преобразования byte строки через decode (как выше) можно указать кодирование вывода в unicode используя utf-8 сразу в subprocess.

test = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')

 

Execution Timeout (subprocess)

В subprocess есть встроенная функция с execution timeout, которая работает с subprocess.run. 

res = subprocess.run("some_scripts", stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', timeout=900)

 

Timeout + stdout/err in file (немного костылей)

Очень удобно использовать timeout, но есть недостаток – если мы присваиваем вывод в переменную, она не соберет данные при exception execution timeout. В итоге мы не будем знать что же сформировалось за время работы скрипта/не увидим ошибки. Решение – использовать запись в файл stdout/stderr через промежуточный скрипт bash скрипт. Обязательно /bin/bash, а не /bin/sh, иначе редирект не будет работать.

scripts.sh
#!/bin/bash
$1 $2 $3 &>$4

res = subprocess.run("some_scripts opt1 opt2 log_file.log", timeout=900)

 

Background

Запуск background subprocess.

test = subprocess.Popen(cmd)

 

Background + сбор вывода (еще немного костылей)

Для запуска в background со сбором вывода или при использовании в коде спец. символов (например, *|) может потребоваться использование shell.

test = subprocess.run("ls -ltr *py", shell=True)

Запуск в background + запись в файл ((есть так же вариант с промежуточный bash-скриптом, см выше)). Реализация через shell:

  • tee для записи в файл,
  • & для background,
  • stdout=null для отброса вывода в консоль (запись только в файл)
import os
null = open(os.devnull, 'w')

subprocess.call('some_script.hz | tee log_file &', shell=True, stdout=null)

 

 

SYSTEM INFO

Библиотека sys.

Определение операционной системы (ОС) в Python
sys.platform == "win32"

 

SOCKETS

Python как и Ruby имеет встроенную библиотеку socket, которая позволяет как принимать данные из сети, так и отправлять их. С ней можно поднять базовый TCP-сервер, отправить какой-то трафик (часто используется в exploit’ах) и проч.

Базовая работа с socket: import, create, connect, send, receive, close.

import socket

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((HOST,PORT))
s.send(evil)
data = s.recv(4096)
s.close()

Пример включения и настройки отправки TCP keepalive для создаваемого socket. Так же timeout можно сделать безусловным на socket (без idle).

s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 60)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 30)

s.settimeout(30)

Пример системных (при установке socket из python не работают):
root@debian8:~# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
root@debian8:~# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200

Настройка MSS (уменьшаем с default 1460 до 96). Сделать еще меньше (48 байт) можно настроив mtu на интерфейсе. При попытке задать MSS через  TCP_MAXSEG может выдавать ошибку OSError: [Errno 22] Invalid argument.

s.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 96)

root@DMG:/home/user/attacks/stateful_generator# sysctl -a | grep -i tcp | grep mss
net.ipv4.tcp_base_mss = 1024
net.ipv4.tcp_min_snd_mss = 48

Отключаем DF flag в IP-пакетах.

IP_MTU_DISCOVER = 10
IP_PMTUDISC_DONT = 0 # Never send DF frames.
s.setsockopt(socket.SOL_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT)

Включаем возможность socket reuse, например полезно в сценарии когда демон постоянно по какой-то причине должен перезагружаться и “подниматься” максимально быстро (без ожидания полного высвобождения socket).

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 

UDP socket (SOCK_DGRAM)

Особенности UDP socket. Несколько примеров.

TCP sockets should use socket.recv and UDP sockets should use socket.recvfrom. This is because TCP is a connection-oriented protocol. Once you create a connection, it does not change. UDP, on the other hand, is a connectionless ("send-and-forget") protocol. You use recvfrom so you know to whom you should send data back. Recvfrom does not work on TCP sockets the same way.

As for the 1024/2048, these represent the number of bytes you want to accept. Generally speaking, UDP has less overhead than TCP allowing you to receive more data, but this is not a strict rule and is almost negligible in this context. You can receive as much or as little as you would like. 4096 is common as well (for both).
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

if proto == "TCP":
s.listen(1)
c, addr = s.accept()
print("TCP connection established with client ip/port: {}".format(addr))
data = c.recv(4096)
else:
data, addr = s.recvfrom(4096)
print("UDP data received from client ip/port: {}".format(addr))
data_new = "hello"
s.sendto(data_new, addr)

 

 

IPADDRESS

Безусловно есть как поддержка IPv4Address/Network объектов, так и IPv6. Ниже описана работа только с IPv4. IPv6 по аналогии.

Address

С помощью функций модуля можно делать разные вещи, например с помощью функции ip_address легко проверять строки на соответствие формату IP-адреса.

import ipaddress

def check_ip(pos_ip):
'''check ip function'''
try:
ipaddress.ip_address(pos_ip)
except ValueError:
return(False)
return(True)

ip1 = "1.1.1.1"
ip2 = "2.2.2.2"
ip66 = "6.66.666.66"
print(ip1, check_ip(ip1))
print(ip2, check_ip(ip2))
print(ip66, check_ip(ip66))

1.1.1.1 True
2.2.2.2 True
6.66.666.66 False

С помощью функции is_private можем понять, относится ли IP к private диапазонам. Есть так же аналогичные функции is_multicast, is_loopback.

ip1 = "10.1.1.1"
ip1_validated = ipaddress.ip_address(ip1)
print(ip1_validated.is_private)
print(ip1_validated.is_multicast)
print(ip1_validated.is_loopback)

True
False
False

Можно так же сравнивать между собой адреса (ip1 > ip2), добавлять к адресам integer для получения нового адреса.

ip1 = "10.1.1.1"
ip1_validated = ipaddress.ip_address(ip1)
print(ip1_validated + 300)

10.1.2.45

Можно сделать проверку принадлежности Address объекта к сети Network.

ip = ipaddress.IPv4Address(u"1.1.1.1")
net = ipaddress.IPv4Network(u"1.1.1.1/255.255.255.0", strict=False)
print(ip in net)

True

Network

С помощью функций network_address и broadcast_address можно получить соответствующие адреса на основе адреса сети или даже IP адреса (strict false) для объекта Network. Так же можно получить общее количество адресов в сети, используя num_addresses. Так же Network является итерируемым объектом – из него можно извлечь объекты IPAddress для каждого адреса как по одному, так и по index.

net = ipaddress.IPv4Network(u"1.1.1.1/255.255.255.0", strict=False)
print('Network address:', net.network_address)
print('Broadcast:', net.broadcast_address)
print('Addr count:', net.num_addresses)

Network address: 1.1.1.0
Broadcast: 1.1.1.255
Addr count: 256


for ip in net:
print(ip)

1.1.1.0
1.1.1.1
1.1.1.2
...
1.1.1.254
1.1.1.255


print(net[4])
1.1.1.4


# без strict будет ошибка, если указать не адрес сети
File "/usr/lib/python2.7/dist-packages/ipaddress.py", line 1662, in __init__
raise ValueError('%s has host bits set' % self)
ValueError: 1.1.1.1/24 has host bits set

Представлять маску в любом формате (with_netmask – mask, with_hostmask-wildcard, prefixlen – prefix) для объекта Network.

net = "10.1.1.0/24"
ipnet = ipaddress.ip_network(net)
print(ipnet.with_netmask)
print(ipnet.with_hostmask)
print(ipnet.prefixlen)

10.1.1.0/255.255.255.0
10.1.1.0/0.0.0.255
24

Можно так же для объекта Network использовать разделение сети на подсети – указав конкретную подсеть (new_prefix) или сделав вычитание из текущей (prefixlen_diff, по умолчанию 1, поэтому не обязательно указывать).

net = ipaddress.IPv4Network(u"1.1.1.1/255.255.255.0", strict=False)
print(list(net.subnets(new_prefix=27)))

[IPv4Network('1.1.1.0/27'), IPv4Network('1.1.1.32/27'), IPv4Network('1.1.1.64/27'), IPv4Network('1.1.1.96/27'), IPv4Network('1.1.1.128/27'), IPv4Network('1.1.1.160/27'), IPv4Network('1.1.1.192/27'), IPv4Network('1.1.1.224/27')]

print(list(net.subnets(prefixlen_diff=1)))
[IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/25')]

print(list(net.subnets()))
[IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/25')]
SCAPy

Самый удобный способ когда нужно сделать более чем одно действие по изменению сетевых дампов. Легко можно сделать даже непростые операции, например, заменить все сетевые (MAC, IP, PORT) адреса в дампе на другие, IPv6 header на IPv4. Замена IPv6 header на IPv4 подразумевает извлечение payload, создание IPv4 header, добавление в него payload, пересчет checksum и length (см. в HEX EDITORS).

Установка

sudo pip3 install scapy

Базовый запуск

from scapy.all import *

packets = rdpcap('test.pcap')
print(len(packets))
for packet in packets:
print(packet) # print packet info from layer 2
print(dir(packet))# print functions
print(packet.payload) # print packet info from layer 3
print(packet['TCP']) # print only TCP info
print(packet['TCP'].flags) # print only TCP flag info

 

Scapy packet examples (часть из документации к TRex)

# PARSING
print(packet.summary())
print(packet.show())
packet['IP'].src
packet['IP'].src
packet['IP'].proto # определяем транспортный протокол 6 TCP, 17 UDP
packet['TCP'].sport
packet['TCP'].dport
packet['TCP'].flags == "S"

# Payload manipulation
payload = packet[TCP/UDP].payload # packet.payload is just a pointer to the next layer.
payload = raw(payload) # преобразуем payload в byte строку
if type(payload) == scapy.packet.Padding # проверяем не является ли payload == padding, иначе это пакет без фактического payload

# CREATION

# UDP header
Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)

# UDP over one vlan
Ether()/Dot1Q(vlan=12)/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)

# UDP QinQ
Ether()/Dot1Q(vlan=12)/Dot1Q(vlan=12)/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)

#TCP over IP over VLAN
Ether()/Dot1Q(vlan=12)/IP(src="16.0.0.1",dst="48.0.0.1")/TCP(dport=12,sport=1025)

# IPv6 over vlan
Ether()/Dot1Q(vlan=12)/IPv6(src="::5")/TCP(dport=12,sport=1025)

#Ipv6 over UDP over IP
Ether()/IP()/UDP()/IPv6(src="::5")/TCP(dport=12,sport=1025)

#DNS packet
Ether()/IP()/UDP()/DNS()

#HTTP packet
Ether()/IP()/TCP()/"GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n"

Скрипт по изменению MAC/IP адресов без создания нового пакета c пересчетом checksum IP/TCP.

from scapy.all import *

packets = rdpcap('/home/user/test.pcap')
new_packets = []
for packet in packets:
sport = packet['TCP'].sport
payload = packet['TCP']
if sport == 80 or sport == 8080:
packet[Ether].dst = "00:10:11:a4:08:87"
packet[Ether].src = "00:10:11:b0:8e:e7"
packet[IP].dst = "172.0.0.2"
packet[IP].src = "172.1.0.2"
else:
packet[Ether].dst = "00:10:11:a4:56:40"
packet[Ether].src = "00:10:11:b0:8e:e5"
packet[IP].dst = "172.1.0.2"
packet[IP].src = "172.0.0.2"
del(packet[IP].chksum)
del(packet[TCP].chksum)
print(packet.show2()) # смотрим содержимое пакета
new_packets.append(packet)

wrpcap("test_new.pcap",new_packets)

Скрипт по извлечению TCP сегмента из существующего дампа, создание нового фрейма и IP пакета, добавление в него TCP payload. В итоге можно из исходного дампа удалить IPv6 header, изменить MAC/IP адреса и прочее, при этом сохранив полностью L4.

from scapy.all import *

packets = rdpcap('old.pcap')
new_packets = []
for packet in packets:
  sport = packet['TCP'].sport
  payload = packet['TCP']
  if sport == 49474:
    new_packet = Ether(src='00:11:22:33:44:55', dst='aa:bb:cc:dd:ee:ff')/IP(dst="1.1.1.1",src="192.168.0.1",proto=6)/Raw(load=payload)
  else:
    new_packet = Ether(src='00:11:22:33:44:55', dst='aa:bb:cc:dd:ee:ff')/IP(dst="192.168.0.1",src="1.1.1.1",proto=6)/Raw(load=payload)
  new_packets.append(new_packet)
wrpcap("new.pcap",new_packets)

Можно извлечь и данные других протоколов, например, ICMP. И добавить в новый IP пакет/фрейм.

payload = packet['ICMP']
new_packet = Ether(src='00:11:22:33:44:55', dst='aa:bb:cc:dd:ee:ff')/ IP(dst="1.1.1.1",src="172.17.0.2",proto=1)/ Raw(load=payload)

IP пакет с рандомным ID, установленным флагом DF, и TOS 16.

new_packet = Ether(src='00:11:22:33:44:55', dst='aa:bb:cc:dd:ee:ff')/ IP(dst="1.1.1.1",src="172.17.0.2",flags=2,id=random.randint(1,65534),tos=16,proto=6) / Raw(load=payload)

Scapy отправка трафика

# Ethernet
new_packet = Ether(src='00:11:22:33:44:55', dst='aa:bb:cc:dd:ee:ff')/ IP(dst="172.17.0.2",src="1.2.3.4",proto=6) / Raw(load=payload)
sendp(new_packet, iface="enp5s0f0")
# IP
pi=IP(dst="10.13.37.218")/ICMP()
send(pi, loop=True)

Можно отправить и в vrf.

ip netns exec VRF1 python3 pcap_replay.py

В целом scapy очень функциональный. Например, есть даже fuzzing (подробнее в документации по ссылке сверху).

>>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1)

Поля можно посмотреть через ls. Очень удобно.

ls(Ether)
dst        : DestMACField                        = (None)
src        : SourceMACField                      = (None)
type       : XShortEnumField                     = (36864)

ls(IP)
version    : BitField (4 bits)                   = (4)
ihl        : BitField (4 bits)                   = (None)
tos        : XByteField                          = (0)
len        : ShortField                          = (None)
id         : ShortField                          = (1)
flags      : FlagsField (3 bits)                 = (<Flag 0 ()>)
frag       : BitField (13 bits)                  = (0)
ttl        : ByteField                           = (64)
proto      : ByteEnumField                       = (0)
chksum     : XShortField                         = (None)
src        : SourceIPField                       = (None)
dst        : DestIPField                         = (None)
options    : PacketListField                     = ([])

ls(TCP)
sport      : ShortEnumField                      = (20)
dport      : ShortEnumField                      = (80)
seq        : IntField                            = (0)
ack        : IntField                            = (0)
dataofs    : BitField (4 bits)                   = (None)
reserved   : BitField (3 bits)                   = (0)
flags      : FlagsField (9 bits)                 = (<Flag 2 (S)>)
window     : ShortField                          = (8192)
chksum     : XShortField                         = (None)
urgptr     : ShortField                          = (0)
options    : TCPOptionsField                     = (b'')

Scapy может не распознавать дампы из-за некорректного magic в файле. Решается пересохранением дампа в Wireshark/tcpdump/tshark, которые этот дамп зачастую (рил кейс) распознают без проблем.

https://stackoverflow.com/questions/32465850/not-a-pcap-capture-file-bad-magic-scapy-python
"Not a pcap capture file (bad magic: %r)" % magic
scapy.error.Scapy_Exception: Not a pcap capture file (bad magic: b'4\xcd\xb2\xa1')

"Not a pcapng capture file (bad magic: %r)" % magic
scapy.error.Scapy_Exception: Not a pcapng capture file (bad magic: b'4\xcd\xb2\xa1')
scapy.error.Scapy_Exception: Not a supported capture file
 
pyshark

не умеет модифицировать пакеты. Scapy умеет.

sudo pip3 install pyshark

import pyshark
shark_cap = pyshark.FileCapture('test.pcap')
for packet in shark_cap:
print(packet.tcp)
print(dir(packet.tcp))
JSON

Python содержит встроенный модуль под названием json для кодирования и декодирования данных JSON.

import json

arr = { "test": 2, "test2": "xxx", "arr": [1,2,7] }
print(arr)
encoded = json.dumps(arr)
print(encoded)
decoded = json.loads(encoded)
print(decoded)

>
{'test': 2, 'test2': 'xxx', 'arr': [1, 2, 7]}
{"test": 2, "test2": "xxx", "arr": [1, 2, 7]}
{'test': 2, 'test2': 'xxx', 'arr': [1, 2, 7]}

Пример чтения json из файла (вместо loads используем load, который в качестве аргумента принимает файл).

with open('strings.json') as f:
  d = json.load(f)
print(d)

Пример выгрузки json с помощью urlib, парсинга его и сложения всех итемов “count”.

import json
from urllib.request import urlopen

url = "http://py4e-data.dr-chuck.net/comments_247944.json"
data = urlopen(url).read()
info = json.loads(data)

summ = 0
for item in info['comments']:
print('Test', item['count'])
summ = summ + int(item['count'])

print(summ)

Pretty print json

json_pattern = json.dumps(json_pattern, indent=4)
XML
Сквозной поиск по названию во всем XML файле (дереве).
You can use an XPath selector string to look through the entire tree of XML for any tag named 'count' with the following line of code:

counts = tree.findall('.//count')
Парсинг XML: поиск всех тагов count и сумма по значению внутри тага (полная функциональная аналогия скрипта по парсингу json).
 
from urllib.request import urlopen
import xml.etree.ElementTree as ET

url = input('Enter location: ')
data = urlopen(url).read()

print('Retrieved', len(data), 'characters')
print(data.decode())
tree = ET.fromstring(data)
results = tree.findall('.//count')

smm = 0
for item in results:
print("Count: ", item.text)
smm = smm + int(item.text)

print(smm)
JAML

Установка

sudo pip3 install PyYAML
Импорт
import yaml
Чтение
with open(some_file) as f:
content = f.read()
test = yaml.safe_load(content)
print(test)
 
 

 

pPrint

Стандартный модуль. Позволяет читаемо отобразить list/dict и прочие данные. Единственное нужно помнить, что длинная строка (меньше width) может быть разделена на string literal – одна строка будет состоять из нескольких строк (каждая из которых укладывается в width), объединенных пробелом. При использовании на словари можно указывать уровень “погружения” depth. Подробнее тут.

from pprint import pprint

data = [('Interface', 'IP', 'Status', 'Protocol'),
('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
print(data)
pprint(data)

[('Interface', 'IP', 'Status', 'Protocol'), ('FastEthernet0/0', '15.0.15.1', 'up', 'up'), ('FastEthernet0/1', '10.0.12.1', 'up', 'up'), ('FastEthernet0/2', '10.0.13.1', 'up', 'up'), ('Loopback0', '10.1.1.1', 'up', 'up'), ('Loopback100', '100.0.0.1', 'up', 'up')]

[('Interface', 'IP', 'Status', 'Protocol'),
('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
Tabulate

Позволяет list/dict (в том числе многомерные) отобразить в красивом виде – с разделением по табам, html тегами, в виде таблиц и проч. Требует установки т.к. не стандартный модуль. Подробно тут.

sudo pip3 install tabulate

from tabulate import tabulate

data = [('Interface', 'IP', 'Status', 'Protocol'),
('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
('Loopback0', '10.1.1.1', 'up', 'up'),
('Loopback100', '100.0.0.1', 'up', 'up')]
print(tabulate(data, headers='firstrow'))

Interface IP Status Protocol
--------------- --------- -------- ----------
FastEthernet0/0 15.0.15.1 up up
FastEthernet0/1 10.0.12.1 up up
FastEthernet0/2 10.0.13.1 up up
Loopback0 10.1.1.1 up up
Loopback100 100.0.0.1 up up


print(tabulate(data, headers='firstrow', tablefmt='grid'))
+-----------------+-----------+----------+------------+
| Interface | IP | Status | Protocol |
+=================+===========+==========+============+
| FastEthernet0/0 | 15.0.15.1 | up | up |
+-----------------+-----------+----------+------------+
| FastEthernet0/1 | 10.0.12.1 | up | up |
+-----------------+-----------+----------+------------+
| FastEthernet0/2 | 10.0.13.1 | up | up |
+-----------------+-----------+----------+------------+
| Loopback0 | 10.1.1.1 | up | up |
+-----------------+-----------+----------+------------+
| Loopback100 | 100.0.0.1 | up | up |
+-----------------+-----------+----------+------------+

 

 

Parsing WEB/парсинг

Простейший get-аналог curl с входом из терминала в виде трех переменных и выводом в терминал.

import requests
import sys

one = sys.argv[1]
two = sys.argv[2]
three = sys.argv[3]

# Set the URL you want to webscrape from
url = 'http://127.0.0.1:2222/?text={}%{}%{}'.format(one, two, three)
# Connect to the URL
print(requests.get(url).text)

Можно оптимизировать используя join c разделителем % и отбросом через slice первого элемента, чтобы на входе были любое количество переменных.

delim = "%"
arg_str = delim.join(sys.argv[1:])

# Set the URL you want to webscrape from
url = 'http://127.0.0.1:2222/?text={}'.format(arg_str)

С utf-8 есть траблы. Вот как решаются:

# Узнать кодировку ответа
requests.get(url).encoding

# Использовать заданную кодировку на полученный ответ
requests.encoding = 'utf-8'

# Пример
res = requests.get(url)
print(res.encoding)
res.encoding = 'utf-8'
print(res.text)
BeautifulSoup with ssl disable

Примеры парсинга c bs4 HTTP

1) Пример выгрузки всех URL ссылок на странице
 
from urllib.request import urlopen
from bs4 import BeautifulSoup

url = input('Enter - ')
html = urlopen(url).read()
soup = BeautifulSoup(html, "html.parser")

# Retrieve all of the anchor tags
tags = soup('a')

2) Выгружаем все с тагом span, далее смотрим в содержимое этого тага и складываем содержимое во всех тагах

from urllib.request import urlopen
from bs4 import BeautifulSoup

url = input('Enter - ')
html = urlopen(url).read()
soup = BeautifulSoup(html, "html.parser")

# Retrieve all of the span tags
tags = soup('span')
summ = 0
for tag in tags:
# Look at the parts of a tag
#print('Contents:', tag.contents[0])
summ = summ + int(tag.contents[0])

print(summ)

HTTPS требует добавления всего нескольких строк и изменения одной:

import ssl

# Ignore SSL certificate errors
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

html = urlopen(url, context=ctx).read()
 
DEBUG/отладчики

Отладчики зачастую встроены в IDE (PyCharm).

Пример базового отладчика – pythontutor.com

Позволяет проследить ход исполнения программы, например, понять, как итерируются объекты.

Пример полноценного отладчика – pdb. Вызывать отладчик можно с помощью set-trace из кода самой программы.

import pdb; 

...

pdb.set-trace()
Ускорение работы
  • Потоки python по умолчанию выполняются на одном ядре
  • Для ускорения python можно почитать эту статью
ошибки

Ошибка

TabError: inconsistent use of tabs and spaces in indentation

Появляется если разные разделители (tab and spaces), нужно к одному привести (лучше в соответствии с pep8 4 spaces вместо каждого tab).

Меняем byte object на string

strng.decode()
#a bytes-like object is required, not 'str'

Меняем string на byte object

btobj.encode()
#cannot use a string pattern on a bytes-like object

 

Ошибка

UnboundLocalError: local variable 'exection_timeout' referenced before assignment

Появляется когда объявили глобальную переменную и захотели переназначить ее в методе. Можно исправить сделав ее глобальной, можно передать explicit в метод при его вызове и уже после этого переназначить внутри метода.

 

 

Leave a Reply