-
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’ю версию.
- Python 3.8-3.9 требуют установленного Service Pack 1 на Windows 7
- В отличии от многих других языков отступ (indentation) в 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
Обучение
- Базовое обучение на py4e.com от Dr. Charles Russell Severance – создателя популярной книги python for everybody и одноименного курса на своем сайте и coursera
Версии
- 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
Установка библиотеки из файла whl (требует разрешение конфликтов).
pip3 install pywin32-228-cp37-cp37m-win_amd64.whl
pip3 install cffi-1.14.3-cp37-cp37m-win_amd64.whl
pip3 install pycparser-2.20-py2.py3-none-any.whl
Установка нескольких библиотек на основе списка в файле.
#pip3 install -r requirements.txt
#cat requirements.txt
Pillow
Обновление библиотеки.
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/
Если не удается установить библиотеку т.к. она не собрана под нашу систему – можно собрать эту библиотеку. При сборке могут поднадобится системные библиотеки (напр. MS Visual C++).
pip3 install bcrypt-3.2.0-cp36-abi3-win_amd64.whl
bcrypt-3.2.0-cp36-abi3-win_amd64.whl is not a supported wheel on this platform.
python setup.py build
python setup.py install
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/
http://go.microsoft.com/fwlink/?LinkId=691126&fixForIE=.exe.
Интерпретатор
Просмотр версии, запуск в интерактивном режиме. Другие опции можно посмотреть в 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
При необходимости импорта из несистемной и нелокальной папки можно расширить пространство поиска библиотек используя sys.path.append:
sys.path.append('/home/user/new/some_dri')
from external_lib import external_lib_function
Methods
Best practices
- Использовать Logging function вместо print
- Использовать Str.format вместо конкатенации
Functions
# 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")
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)
ndr_benchmark_test(server='127.0.0.1', iteration_duration=780.0,
title='ndr-experiments-xf5000', verbose=True, max_iterations=10,
allowed_error=1.0, q_full_resolution=2.0,
latency_pps=0, max_latency=0, lat_tolerance=0,
output=None, yaml_file=None, plugin_file=None,
tunables={}, profile='/opt/trex/v2.82/astf/rus_ent_appmix.py', profile_tunables={},
high_mult=100, low_mult=0,
>> 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
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']...
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")
copy (копирование)
from shutil import copyfile
copyfile(src, dst)
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()
Управление сервисами. Запуск функции.
check_and_change_service_status("inactive", "trex_server")
Управление сервисами. Получение статуса.
def get_service_status(service_name):
serv1_status_raw = subprocess.run("systemctl status {}".format(service_name).split(" "), stdout=subprocess.PIPE, stderr=null)
serv1_status = serv1_status_raw.stdout.decode().split("\n")[2].split()[1].replace("failed", "inactive")
return serv1_status
Управление сервисами. Установка статуса.
def set_service_status(expected_service_status, service_name):
if expected_service_status == "inactive":
expected_service_status = "stop"
elif expected_service_status == "active":
expected_service_status = "start"
else:
write_to_file_with_new_line_and_dt("real service_name {} status is {} expected is {}".format(service_name, real_status, expected_service_status), log_fname)
subprocess.run("systemctl {} {}".format(expected_service_status, service_name).split(" "), stderr=null)
Управление сервисами. Управляющая функция.
def check_and_change_service_status(expected_service_status, service_name):
real_status = get_service_status(service_name)
write_to_file_with_new_line_and_dt("real service_name {} is {} expected is {}".format(service_name, real_status, expected_service_status), log_fname)
if real_status != expected_service_status:
set_service_status(expected_service_status, service_name)
real_status = get_service_status(service_name)
if real_status != expected_service_status:
raise Exception("Can't change {} to expected value {}".format(service_name, expected_service_status), log_fname, global_log_fname)
write_to_file_with_new_line_and_dt("successfully changed {} to expected value {}".format(service_name, expected_service_status), log_fname)
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
import datetime
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
String date to date object
datetime.datetime.strptime('2020-11-04', "%Y-%m-%d").date()
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.
Округление к большему (round up) делается с помощью функции ceil.
The ceil (ceiling) function:
import math
print(math.ceil(4.2))
Среднее значение (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 functionrandom
returns a random float between 0.0 and 1.0 (including 0.0 but not 1.0).
import randomrandom.random()
Функция .randint к объекту random генерирует случайное int число от заданного x до y.
The functionrandint
takes the parameterslow
andhigh
, and returns an integer betweenlow
andhigh
(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"))
뿐胑룐닐뗐苑
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)
hi_unicode = 'привет'
hi_bytes = hi_unicode.encode('utf-8')
hi_bytes.decode('ascii',"ignore")
По умолчанию кодировка для 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(), client
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, "ignore")
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')
Закрытие подключения. В примере ssh_login возвращается объект shell и отдельный объект client, на который нужно применять метод close после завершения работы по ssh (client.close()). Делать это на объект shell неправильно – соединение закрывается не полностью и при накомплении произойдет reject!
You can have multiple channels in a single SSH connection. Hence closing a single channel, won't close whole SSH connection.
You need to call SSHClient.close to close the physical SSH connection (it takes all channels down with it, if any are still open).
ssh.close()
invoke_shell(term='vt100', width=80, height=24, width_pixels=0, height_pixels=0, environment=None)¶
Start an interactive shell session on the SSH server. A new Channel is opened and connected to a pseudo-terminal using the requested terminal type and size.
SCP
Для начала создаем ssh-подключение, например, используя paramiko.
ssh_conn, ssh_client = ssh_login(ssh_ip, ssh_user, ssh_password, ssh_shell_password)
Для загрузки файла используем scp.put, в качестве транспорта используем созданное ssh-подключение.
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 – 0x):
>>> hex(255)
'0xff'
Обратно – из шестнадцатеричного в десятичный (decimal):
>>> int('ff', 16)
255
String
Стандартная работа функции 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 очень функциональный – к примеру, можно:
- указывать размер столбца по количеству символов в каждом (как в bash printf),
- делать выравнивание текста (влево <, вправо >):
- указывать название переменных (удобно когда их много и/или они похожего формата данные)
- исключать дублированные данные с использованием именование или индексов
- переводить из одной системы исчисления в другую
- используя * передавать данные не в виде отдельных элементов, а всем массивом
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.
from copy import deepcopy
total_output = {'results': stats, 'config': config}
deep_copy_total_output = deepcopy(total_output)
return deep_copy_total_output
Длина массива или строки (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 awhile True
inside your for loop, put yourtry
code inside, and break from thatwhile
loop only when your code succeeds.
for i in range(1,exception_retries_count+1):
try:
<some_not_stable_process>
break
except Exception as e:
time.sleep(10)
write_to_file(f"exception {} was raised (retry ({}/{})".format(e, i, exception_retries_count), log_fname)
if i == exception_retries_count:
quit_with_log("Error: maximum retries count reached.", log_fname)
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()
-
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)
OUTLOOK (MAIL)
Отправка письма через outlook приложение на Windows, работает. Для нескольких адресатов указываем их через “;” в string.
import win32com.client as win32
outlook = win32.Dispatch('outlook.application')
mail = outlook.CreateItem(0)
mail.To = 'To address'
mail.Subject = 'Message subject'
mail.Body = 'Message body'
mail.HTMLBody = '<h2>HTML Message body</h2>' #this field is optional
# To attach a file to the email (optional):
attachment = "Path to the attachment"
mail.Attachments.Add(attachment)
mail.Send()
SYSTEM INFO
Библиотека sys.
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')]
pyroute2 (netns)
Устанавливаем netns, возвращаемся в дефолтный. Работает с socket без каких-либо проблем.
pip3 install pyroute2
import socket
from pyroute2 import netns
netns.setns('VRF1')
# do some stuff in netns, than return back to default
netns.setns('/proc/1/ns/net')
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"
В целом 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'')
СКРИПТЫ
Скрипт по изменению 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)
Модификация payload в дампе с пересчетом checksum.
TODO
48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
48 54 54 50 2f 31 2e 30 20 32 30 30 20 4f 4b 0d HTTP/1.0 200 OK.
from scapy.all import * #!/usr/bin/env python3 # ver 1.0 by Petr V. Redkin from scapy.all import * def change_payload(packet, load): packet["Raw"].load = load del packet["IP"].len del packet["IP"].chksum del packet["TCP"].chksum return packet packets = rdpcap('unmodified.pcap') new_packets = [] n = 0 for packet in packets: payload = packet['TCP'].payload if "HTTP/1.1 200 OK" in str(payload): print("MATCH!") print("transaction changed!") new_payload = packet["Raw"].load.replace(b"HTTP/1.1 200 OK",b"HTTP/1.0 200 OK") packet = change_payload(packet,new_payload) new_packets.append(packet)
wrpcap("modified.pcap",new_packets)
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 (так же можно напрямую в коде python используя pyroute2):
ip netns exec VRF1 python3 pcap_replay.py
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, который в качестве аргумента принимает файл).
d = get_json('strings.json')
print(d)
def get_json(j):
with open(j) as f:
j_parsed = json.load(f)
return j_parsed
Пример выгрузки 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
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')
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
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 можно почитать эту статью
What’s new in new python versions
some_funct(a,b,/):
раньше
f'var={var}'
сейчас можно
f'{var=}'
результат один
'var=var_value'
ошибки
Ошибка
TabError: inconsistent use of tabs and spaces in indentation
Появляется если разные разделители (tab and spaces), нужно к одному привести (лучше в соответствии с pep8 4 spaces вместо каждого tab). Проще всего это сделать используя sublime text, у него есть полезная функция по конвертации в один формат – View -> Indentation -> Convert Indentation to tabs/spaces.
Меняем 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 в метод при его вызове и уже после этого переназначить внутри метода.