Python довольно популярен среди пентестеров. Несмотря на то, что третья версия этого языка появилась еще в декабре 2008 года, многие пишут скрипты исключительно для версии 2.7. Учитывая, что на данный момент Python 2.7 находится в режиме поддержки, важно научиться писать свой код так, чтобы потом не пришлось затрачивать слишком много усилий для переделки под третью версию.
Автор: Spencer McIntyre
Python довольно популярен среди пентестеров. Несмотря на то, что третья версия этого языка появилась еще в декабре 2008 года, многие пишут скрипты исключительно для версии 2.7. Учитывая, что на данный момент Python 2.7 находится в режиме поддержки, важно научиться писать свой код так, чтобы потом не пришлось затрачивать слишком много усилий для переделки под третью версию. Именно с этой целью я решил написать данную статью и рассказать о некоторых полезных трюках, чтобы ваш код был одновременно совместим со второй и третьей версией.
Совет №1. Как совместить типы, возвращаемые методами items(), keys() и values()
В Python 3 методы items(), keys() и values() возвращают специальные типы вместо списков как в Python 2. Следовательно, если использовать возвращаемый объект как изменяемый тип (список), в Python 3 возникнет ошибка. Пример:
my_teams_scores = {'alice': 0, 'bob': 0}
my_team = my_teams_scores.keys()
# my_team – это список в Python 2 и тип dict_keys в Python 3 (метод append работать не будет)
my_team.append('spencer')
Чтобы код, приведенный выше, был совместим с обеими версиями, необходимо сконвертировать тип dict_keys в список:
my_teams_scores = {'alice': 0, 'bob': 0}
my_team = list(my_teams_scores.keys())
# теперь my_team является списком в обеих версиях, и вызов метода list() не вызовет ошибки в Python 2
my_team.append('spencer')
Совет №2. Как совместить использование модулей с измененными именами
В Python 3 несколько модулей, именованных ранее по стандарту CamelCase, стали называться по стандарту snake_case. Некоторые имена не претерпели сильных изменений, и можно пользоваться обеими версиями. Переименованные модули: ConfigParser, Queue, SimpleHTTPServer и BaseHTTPServer. Два модуля типа HTTPServer стали именоваться чуть по-другому, поскольку основные классы находятся в модуле http.server.
import sys
if sys.version_info.major < 3:
# import the Python 2 ConfigParser module and rename it
import ConfigParser as configparser
else:
import configparser
parser = configparser.ConfigParser()
parser.read('some_config.ini')
Совет №3. Как совместить использование функции печати
Один из наиболее быстрых способов узнать, будет ли работать скрипт в Python 3 – обратить внимание, используется ли «print» как ключевое слово или как функция. В Python 3 ключевое слово «print» мигрировало в функцию, и, следовательно, для корректной работы скрипта нужны скобки.
print 'this will only work in python 2!'
print('this will work in either python 2 or python 3!')
Печать в одну линию:
# будет работать только в Python 2.x
print 'waiting for something...',
print 'done!'
from __future__ import print_function
# будет работать в Python 2.7 и Python 3.x
print('waiting for something...', end='')
print(' done!')
Совет №4. Как добиться совместимости при выполнении web-запросов
В большинстве случаев, если в скрипте необходимо сделать web-запрос, используется модуль urllib2. Однако в Python 3 это один из многих модулей, которые были переименованы и перемещены в другие пакеты. В Python 3 появился пакет urllib.request, и чтобы совместить выполнение базовых задач, связанных с web-запросами, понадобится переименование импортированных модулей (см. Совет 2). Однако можно пойти более простым путем и для выполнения web-запросов использовать сторонний пакет, совместимый с Python 2 и Python 3. Этот пакет доступен через pip (система управления пакетами), и один и тот же код можно использовать в различных версиях Python.
# будет работать только в Python 2
import urllib2
url_h = urllib2.urlopen('https://warroom.securestate.com')
# будет работать в Python 2.6 - Python 3.4 (начиная с версии пакета 2.8.1)
import requests
resp = requests.get('https://warroom.securestate.com')
Совет №5. Как совместить использование байтового и строкового типа
Вероятно, при адаптации скриптов к Python 3 наибольшие сложности возникнут c новым байтовым типом, который похож на строку с префиксом «b»:
b'these are bytes'
Использование этого типа наиболее уместно при работе с сырыми сокетами (raw sockets) или кодировкой base64. Важно отметить, что байтовый тип наиболее схож со строковым типом. Когда функция возвращает значение в байтовом типе, и пользователь ожидает текстовые данные, то преобразование в строковой тип возможно при помощи конструкции .decode(‘utf-8’). В Python 2 эта конструкция будет конвертировать строковой объект в объект типа unicode. В Python 3 байтовый объект будет конвертироваться в строковой объект. Важно отметить, что Python 2 поддерживает больше операций между строковыми объектами и объектами типа unicode, чем Python 3 - между байтовыми объектами и строковыми объектами.
# код ниже будет работать только в Python 2.x
# sock – установленное соединение через сокет
data = sock.recv(4)
if data == 'exit':
sys.exit(0)
В этом примере предполагается, что переменная «data» - текстового типа, поскольку будет использоваться в командной оболочке.
# код ниже преобразует переменную data в utf-8 и будет работать в Python 2.7 & Python 3.x
# sock – установленное соединение через сокет
data = sock.recv(4)
data = data.decode('utf-8')
if data == 'exit':
sys.exit(0)
Следует отметить, что в Python 2.7 строки с префиксом b остаются строкового типа, что позволяет выполнять совместимые операции сравнения и конкатенации.
# sock – установленное соединение через сокет
data = sock.recv(4)
# вместо декодирования сравнивается один байтовый тип с другим байтовым типом
if data == b'exit':
sys.exit(0)