Те, кто выбрал Python в качестве одного из первых изучаемых языков программирования общего назначения, нередко горят желанием написать внушительное количество мелких программ, наподобие UPD-клиентов, простеньких текстовых редакторов, чтобы отточить навыки на практике.
В этой статье я опишу процесс создания простенького генератора ip-адресов. Статья ориентирована на новичков. Среда разработки не так важна — вполне можно писать в IDLE, сложностей возникнуть не должно, однако я буду использовать Eclipse с плагином PyDev.
Представление адреса компьютера по протоколу IPv4 являет собой 32-битное число, разделенное на четыре блока для удобства записи. Такие блоки мы и будем генерировать. Следует отметить, что такая форма вывода удобна, поскольку зачастую списки ip-адресов, прокси-серверов и т. п. загружаются в сторонние программы именно текстовым файлом.
Определённо, первым параметром, который должен указать пользователь, будет количество адресов, которые нужно сгенерировать.
Поскольку мы условились, что пользователь определяет количество, то создаём функцию
Используем цикл for, так как он выполняется гораздо быстрее while'a. Не забываем импортировать модуль random (а лучше
Что же, пожалуй, есть всё, что нам необходимо, функционал допишем чуть позже, а сейчас приступим к созданию графического интерфейса. Использовать будем библиотеку Tkinter.
Подробную документацию о ней на русском языке можно прочитать тут.
Нам нужно создать поле ввода (виджет
Используем упаковщик grid, который размещает элементы по принципу табличной вёрстки. После компиляции окно должно выглядеть вот так:
Теперь нам нужно написать функцию, которая будет считывать количество, введённое в поле ввода, при нажатии на кнопку «Сгенерировать», и передавать его в generator. При этом стоит сделать так, чтобы функция выдавала в поле логов сообщение об ошибке, если пользователь ввёл не число. Для этого будем пользоваться конструкцией try-except:
Здесь я создал отдельно функцию
Тогда описание виджета будет выглядеть так:
При добавлении метода
При проверке корректности программы стало неудобно удалять текстовый файл вручную, так почему бы не добавить кнопку «Удалить файл»? Создаём функцию
Особо с названием файла я заморачиваться не стал, но при желании можно реализовать пользовательское имя файла на выводе (и удалять соответствующий файл). Теперь пишем кнопку, которая будет вызывать
Упаковщиком grid мы разместили эту кнопку в первой строке, четвёртом столбце поля
При генерации списка ip с 500 и более адресов становится заметно, что программе требуется некоторое время, чтобы обработать цикл for. Чтобы пользователь не подумал, что наша программа зависла, реализуем простенький прогресс-бар.
Сразу хочу сказать: Tkinter не подойдёт для этой цели. Мы не сможем реализовать вывод сообщения о прогрессе в окно Tkinter'а, поскольку компилятор не в состоянии обрабатывать окна графического интерфейса во время хода цикла for, даже если мы будем выводить сообщение не каждый ход, а, допустим, через 10, хоть через 100 ходов. Всё равно компилятор в состоянии вывести сообщение только последнего хода цикла (проще говоря, Tkinter таким образом будет выводить только 99%). То есть, создать кошерный прогресс-бар в том же окне, к сожалению, обычными средствами не получится. Так что логичнее всего на фоне основного интерфейса программы открыть терминал логов (обычную консоль), и в него выводить прогресс генерации. Заметка: консоль в привычном для пользователя виде откроется только при запуске программы из проводника; при запуске из компилятора сообщения выводятся в консоль компилятора.
Дорабатываем функцию
Компилируем, генерируем список в 500, например, адресов. В консоль выводится следующее:
Здорово. А как убрать лишние повторяющиеся проценты? Создаём небольшой велосипед и сохраняем предыдущее значение:
Готово! С прогресс-баром, будем считать, разобрались.
А что если я захочу ip-лист с портом 8080, к примеру? Добавим и эту функцию. Начнём с GUI: добавим два виджета
Теперь наш GUI имеет вид:
Добавляем в функцию-обработчик
Стоп, функция-генератор принимает только один аргумент. Тогда вновь возвращаемся к ней и объявляем её следующим образом:
Добавив инициализацию аргумента
В конечном счёте, исходный код программы:
На этом всё. Конечно, можно добавить ещё много интересных функций, например, сортировку, генерацию в пределах определённого диапазона адресов, а можно и вовсе сделать парсер какого-нибудь фри-прокси.ру, однако для начальной практики по Python сойдет и такой простой генератор.
В этой статье я опишу процесс создания простенького генератора ip-адресов. Статья ориентирована на новичков. Среда разработки не так важна — вполне можно писать в IDLE, сложностей возникнуть не должно, однако я буду использовать Eclipse с плагином PyDev.
Что конкретно будет выполнять программа?
Представление адреса компьютера по протоколу IPv4 являет собой 32-битное число, разделенное на четыре блока для удобства записи. Такие блоки мы и будем генерировать. Следует отметить, что такая форма вывода удобна, поскольку зачастую списки ip-адресов, прокси-серверов и т. п. загружаются в сторонние программы именно текстовым файлом.
Определённо, первым параметром, который должен указать пользователь, будет количество адресов, которые нужно сгенерировать.
Пишем функцию generator()
Поскольку мы условились, что пользователь определяет количество, то создаём функцию
generator()
, которой передаём аргумент amount
:def generator(amount):
for n in range(amount):
#следуя Дзену Питона, явно создаю 4 переменных для наглядности
a = randint(0,255)
b = randint(0,255)
c = randint(0,255)
d = randint(0,255)
#открываем файл в режиме редактирования
f = open('ip-addresses.txt', 'a', encoding='utf-8')
f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+'\n')
f.close()
#ну и почему бы не выдать в консоль сообщение об успешной генерации?
print('Success!')
Используем цикл for, так как он выполняется гораздо быстрее while'a. Не забываем импортировать модуль random (а лучше
from random import randint
, чтобы не засорять пространство имён).GUI на Tkinter
Что же, пожалуй, есть всё, что нам необходимо, функционал допишем чуть позже, а сейчас приступим к созданию графического интерфейса. Использовать будем библиотеку Tkinter.
Подробную документацию о ней на русском языке можно прочитать тут.
Нам нужно создать поле ввода (виджет
Entry
), кнопку (виджет Button
), желательно поле вывода логов, ну и добавить название программы, чтобы было с первого взгляда понятно, для чего она предназначена:from tkinter import *
#создаём главное окно
root = Tk()
label1=Label(root, text="Генератор ip-адресов")
label1.grid()
#создаём поле Frame, далее упаковываем виджеты в него
frame = Frame(root)
frame.grid()
label2=Label(frame, text='Количество:')
label2.grid(row=1,column=1)
#поле ввода количества адресов
entry_amount = Entry(frame, width=4, borderwidth=5)
entry_amount.grid(row=1,column=2)
#кнопка генерации
button1 = Button(frame, text="Сгенерировать")
button1.grid(row=1, column=3, padx=(10,0))
#поле вывода логов
output = Text(frame, bg="lightblue", font="Arial 9", width=45, height=3)
output.grid(row=2, columnspan=8)
root.mainloop()
Используем упаковщик grid, который размещает элементы по принципу табличной вёрстки. После компиляции окно должно выглядеть вот так:
Обрабатываем введённые параметры
Теперь нам нужно написать функцию, которая будет считывать количество, введённое в поле ввода, при нажатии на кнопку «Сгенерировать», и передавать его в generator. При этом стоит сделать так, чтобы функция выдавала в поле логов сообщение об ошибке, если пользователь ввёл не число. Для этого будем пользоваться конструкцией try-except:
def handler():
try:
#считываем методом .get()
amount = int(entry_amount.get())
generator(amount)
except ValueError:
notif("Невозможно определить количество")
#создаём также функцию, передающую лог в поле вывода сообщений
def notif(value):
output.delete("0.0","end") #очищаем поле перед следующим логом
output.insert("0.0",value)
Здесь я создал отдельно функцию
def notif(value)
, в которую передаётся аргумент-запись, чтобы упростить код при написании функционала, когда потребуется выводить другие ошибки. Также следует добавить в описание виджета button1
метод со значением нашей функции обработчика command=handler
(без круглых скобок в конце). Тогда описание виджета будет выглядеть так:
button1 = Button(frame, text="Сгенерировать", command=handler)
При добавлении метода
command
возможно потребуется прописать from distutils import command
в импортах. Вот и всё, собственно, программа выполняет свою основную задачу.Добавляем всякие полезности
Кнопка «Удалить файл»
При проверке корректности программы стало неудобно удалять текстовый файл вручную, так почему бы не добавить кнопку «Удалить файл»? Создаём функцию
def delete()
:def delete():
try:
remove('ip-addresses.txt')
inserter("Файл ip-addresses.txt успешно удален")
except:
inserter("Невозможно удалить несуществующий файл")
Особо с названием файла я заморачиваться не стал, но при желании можно реализовать пользовательское имя файла на выводе (и удалять соответствующий файл). Теперь пишем кнопку, которая будет вызывать
delete
:button2 = Button(frame, text="Удалить файл", command=delete)
button2.grid(row=1, column=4, padx=(10,0))
Упаковщиком grid мы разместили эту кнопку в первой строке, четвёртом столбце поля
frame
. Не забываем добавить в импорты строчку from os import remove
. Всё, с кнопкой «Удалить» разобрались.Прогресс-бар
При генерации списка ip с 500 и более адресов становится заметно, что программе требуется некоторое время, чтобы обработать цикл for. Чтобы пользователь не подумал, что наша программа зависла, реализуем простенький прогресс-бар.
Сразу хочу сказать: Tkinter не подойдёт для этой цели. Мы не сможем реализовать вывод сообщения о прогрессе в окно Tkinter'а, поскольку компилятор не в состоянии обрабатывать окна графического интерфейса во время хода цикла for, даже если мы будем выводить сообщение не каждый ход, а, допустим, через 10, хоть через 100 ходов. Всё равно компилятор в состоянии вывести сообщение только последнего хода цикла (проще говоря, Tkinter таким образом будет выводить только 99%). То есть, создать кошерный прогресс-бар в том же окне, к сожалению, обычными средствами не получится. Так что логичнее всего на фоне основного интерфейса программы открыть терминал логов (обычную консоль), и в него выводить прогресс генерации. Заметка: консоль в привычном для пользователя виде откроется только при запуске программы из проводника; при запуске из компилятора сообщения выводятся в консоль компилятора.
Дорабатываем функцию
generator
:def generator(amount, port=''):
for n in range(amount):
a = randint(0,255)
b = randint(0,255)
c = randint(0,255)
d = randint(0,255)
f = open('ip-addresses.txt', 'a', encoding='utf-8')
f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+port+'\n')
f.close()
#для каждого хода цикла присваиваем переменной prc целочисленное значение, которое n составляет от amount при условии, что amount — 100%
prc = int(n//(amount/100))
print(str(prc)+'%')
print('Success!')
#выводим уже в GUI сообщение об успешной генерации txt-файла
notif("IP-адреса успешно сгенерированы и записаны в\nip-addresses.txt")
Компилируем, генерируем список в 500, например, адресов. В консоль выводится следующее:
Здорово. А как убрать лишние повторяющиеся проценты? Создаём небольшой велосипед и сохраняем предыдущее значение:
def generator(amount, port=''):
prc_bfr=0 #здесь будем запоминать прогресс предыдущего хода цикла
for n in range(amount):
a = randint(0,255)
b = randint(0,255)
c = randint(0,255)
d = randint(0,255)
f = open('ip-addresses.txt', 'a', encoding='utf-8')
f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+port+'\n')
f.close()
prc = int(n//(amount/100))
#проверяем, равняется ли предыдущее значение переменной prc текущему
if(prc!=prc_bfr):
print(str(prc)+'%')
prc_bfr = prc
print('Success!')
notif("IP-адреса успешно сгенерированы и записаны в\nip-addresses.txt")
Готово! С прогресс-баром, будем считать, разобрались.
Добавляем порты к ip-адресам
А что если я захочу ip-лист с портом 8080, к примеру? Добавим и эту функцию. Начнём с GUI: добавим два виджета
Radiobutton
, которые будут определять два режима: генерация с портами и без портов. Также следует добавить виджет Entry
для ввода порта:label2 = Label(frame, text='Указать порт:')
label2.grid(row=2,column=1)
entry_port = Entry(frame, width=4, borderwidth=5, state=DISABLED)
entry_port.grid(row=2,column=2)
#объявляем var1 для работы с radiobutton'ами
var1 = IntVar()
check_port1 = Radiobutton(frame, text='С портами', variable=var1, value=1, command=lock)
check_port1.grid(row=2,column=3)
check_port2 = Radiobutton(frame, text="Без портов", variable=var1, value=0, command=lock)
check_port2.grid(row=2,column=4)
entry_port
по умолчанию у нас скрыт. Каждый check_port
выполняет функцию lock()
при активации, которая соответственно скрывает/активирует поле ввода портов. Опишем функцию lock
:def lock():
#если активирован check_port2:
if var1.get() == 1:
entry_port.configure(state=NORMAL)
#если активирован check_port1:
elif var1.get() == 0:
entry_port.configure(state=DISABLED)
Теперь наш GUI имеет вид:
Добавляем в функцию-обработчик
handler
конструкцию if-else, которая выполняется в зависимости от режима генерации (от значения var1
):def handler():
try:
amount = int(entry_amount.get())
#проверяем режим генерации
if var1.get() == 1:
port = ':'+str(int(entry_port.get())) #выполняем явное преобразование в int для проверки целочисленности данных, и снова в str, чтобы добавить двоеточие к порту
generator(amount, port)
else:
#иначе передаём генератору один аргумент — количество
generator(amount)
except ValueError:
notif("Невозможно определить количество/порт")
Стоп, функция-генератор принимает только один аргумент. Тогда вновь возвращаемся к ней и объявляем её следующим образом:
def generator(amount, port='')
Добавив инициализацию аргумента
port
непосредственно в объявлении функции, мы сделали его необязательным, и он у нас изменит значение при вызове функции в режиме генерации с портами. Также добавляем его при записи в текстовый файл: f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+port+'\n')
В конечном счёте, исходный код программы:
import tkinter
import random
from tkinter import *
from random import randint
from os import remove
from distutils import command
print('Logs terminal:')
def generator(amount, port=''):
prc_bfr=0
for n in range(amount):
a = randint(0,255)
b = randint(0,255)
c = randint(0,255)
d = randint(0,255)
f = open('ip-addresses.txt', 'a', encoding='utf-8')
f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+port+'\n')
f.close()
prc = int(n//(amount/100))
if(prc!=prc_bfr):
print(str(prc)+'%')
prc_bfr = prc
print('Success!')
notif("IP-адреса успешно сгенерированы и записаны в\nip-addresses.txt")
def notif(value):
output.delete("0.0","end")
output.insert("0.0",value)
def handler():
try:
amount = int(entry_amount.get())
if var1.get() == 1:
port = ':'+str(int(entry_port.get()))
generator(amount, port)
else:
generator(amount)
except ValueError:
notif("Невозможно определить количество/порт")
def delete():
try:
remove('ip-addresses.txt')
notif("Файл ip-addresses.txt успешно удален")
except:
notif("Невозможно удалить несуществующий файл")
def lock():
if var1.get() == 1:
entry_port.configure(state=NORMAL)
elif var1.get() == 0:
entry_port.configure(state=DISABLED)
root = Tk()
label1=Label(root, text="Генератор ip-адресов")
label1.grid()
frame = Frame(root)
frame.grid()
label2=Label(frame, text='Количество:')
label2.grid(row=1,column=1)
entry_amount = Entry(frame, width=4, borderwidth=5)
entry_amount.grid(row=1,column=2)
button1 = Button(frame, text="Сгенерировать", command=handler)
button1.grid(row=1, column=3, padx=(10,0))
button2 = Button(frame, text="Удалить файл", command=delete)
button2.grid(row=1, column=4, padx=(10,0))
label2=Label(frame, text='Указать порт:')
label2.grid(row=2,column=1)
entry_port = Entry(frame, width=4, borderwidth=5, state=DISABLED)
entry_port.grid(row=2,column=2)
var1 = IntVar()
check_port1 = Radiobutton(frame, text='С портами', variable = var1, value=1, command=lock)
check_port1.grid(row=2,column=3)
check_port2 = Radiobutton(frame, text="Без портов", variable = var1, value=0, command=lock)
check_port2.grid(row=2,column=4)
output = Text(frame, bg="lightblue", font="Arial 9", width=45, height=3)
output.grid(row=3, columnspan=8)
root.mainloop()
На этом всё. Конечно, можно добавить ещё много интересных функций, например, сортировку, генерацию в пределах определённого диапазона адресов, а можно и вовсе сделать парсер какого-нибудь фри-прокси.ру, однако для начальной практики по Python сойдет и такой простой генератор.
Комментарии (10)
AnutaU
02.10.2017 12:41Что-то прямо вспомнилось, как в универе подсети считали методом квадратов на бумажке.
GennPen
02.10.2017 13:21Представление адреса компьютера по протоколу IPv4 являет собой четыре десятизначных числа (от 0 до 255), разделённых точками. Именно такие десятичные ip-адреса мы и будем генерировать и записывать текстовый файл.
У вас неправильно генерируются IP адреса.
mwizard
02.10.2017 13:27+2У генератора слишком много сайдэффектов. И какая разница, быстрее выполняется for, чем while? Пишите понятный код, а скорость придет естественным образом. Сравните ваш генератор и:
import logging from itertools import islice from pathlib import Path from random import randrange logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) def ips(): while True: a = randrange(256) b = randrange(256) c = randrange(256) d = randrange(256) yield f'{a}.{b}.{c}.{d}' def write_out(filename, lines): with Path(filename).open('a', encoding='utf8') as fp: for line in lines: fp.write(f'{line}\n') write_out('ip-addresses.txt', islice(ips(), 100)) logger.info('Complete!')
Теперь можно и поддержку подсетей в ips() реализовать, и ничего не сломается, и https://docs.python.org/3/library/ipaddress.html использовать, да и вообще, немного чище все стало :)
saw_tooth
02.10.2017 14:58+2f.write(str(a)+'.'+str(b)+'.'+str(c)+'.'+str(d)+port+'\n')
Вас Borland Delphi покусал?
f = open('ip-addresses.txt', 'a', encoding='utf-8')
Контекстные менеджеры не нужны.
однако для начальной практики по Python сойдет и такой простой генератор
Не пойдет, потому что это скорее практическое руководство как НЕ надо делать… уж простите за такую резкую критику.
9660
03.10.2017 07:58for n in range(amount): a = randint(0,255) b = randint(0,255) c = randint(0,255) d = randint(0,255)
У меня смутные подозрения о валидности некоторых адресов.
iSergios
03.10.2017 08:14У Вас собственно генерация идет в цикле. И писать процент в каждой новой строке реально не комильфо. В одну строчку будет симпатичнее и нагляднее. Скажем, хотябы так:
def progressbar(position, max): total_points = 50 # длина прогрессбара, выберите по вкусу current_points = int(position / max * total_points) percent = int(current_points / total_points * 100) print(f'\r[ {"-" * current_points}{"-" * (total_points-current_points)} ] {percent}%', end='', flush=True)
Sly_tom_cat
Только не понятен смыл генерации IP адресов без учета хотя бы выделенных значений для локальных сетей…
Да и 4 рандома — крайне сомнительное решение. При достаточно большом наборе адресов будут дубли (смотрим бездей парадокс), не говоря уже о попытке создать список всех возможных IP адресов. В таком списке никогда всего диапазона не получить указанным методом.
Как казалось бы — в параметрах в первую очередь должна быть маска подсети в которой будут генерироваться адреса.