Введение
Как человек, который только начинает осваивать Python, я стараюсь получать новые знания и умения путём непосредственной практики. На мой взгляд, данный подход лучше штрудирования гайдов и многотомников по программированию. В первую очередь проектами подобным тому, о котором расскажу, я пытаюсь закрыть свои потребности. На втором месте находится надежда на то, что кому-то ещё может пригодиться то, что я создам (после того, как все имеющиеся ошибки будут исправлены)
Идея данной программы пришла в момент, когда пытался найти ранее загруженный файл среди «помойки», образовавшейся в папке «Загрузки». Изображения, документы, архивы и видео с музыкой: разобраться в этой мешанине и отфильтровать файлы можно было бы конечно, но неохота из-за больших временных затрат. И я подумал о том, что неплохо иметь программу, которая сама разберёт папку «Загрузки» и распределит файлы по стандартным папкам системы «Изображения», «Документы», «Видео» и «Музыка», а ненужные файлы удалит. В итоге на свет появился Perfectionist Organizer, о котором я хотел бы рассказать. Сразу скажу, что это статья от новичка для новичков, потому опытные Python-программисты не найдут здесь ничего полезного (кроме возможности поковыряться в коде и указать на множество ошибок).
Общая идея
Первое, что должна делать программа — определять в какой операционной системе она запущена. Это нужно потому, что в Linux и Windows папка «Загрузки» расположена по разным путям (возможности тестировать программу на Mac у меня нет, потому эту ОС я не учитывал). В обнаруженной папке с загрузками программа перебирает все имеющиеся файлы и определяет к какому типу они относятся (музыка, видео, документы и пр.). После происходит перемещение обнаруженных файлов в дефолтные папки и программа узнаёт у пользователя, что делать с теми файлами, которые не подходят для перемещения — оставить или удалить.
Импорт необходимых библиотек и определение типа системы
Для работы с операционной системой и файлами в ней нам понадобится библиотека os. Библиотека getpass позволит узнать имя пользователя в системе, а platform определит Windows это или Linux. Все нужные библиотеки подключаем в самом начале через import и после определяем тип ОС и имя пользователя.
import os
import getpass
import platform
type_os = platform.system()
usermane = getpass.getuser()
Создание словаря с расширениями популярных файлов и дефолтными папками, которые с ними связаны
Стандартными средствами Python создадим четыре словаря с самыми популярными типами файлов: музыка, изображения, видео и документы.
video_folder = {".3gp" : "Видео/", ".avi" : "Видео/", ".flv" : "Видео/", ".m4v" : "Видео/", ".mkv" : "Видео/", ".mov" : "Видео/", ".mp4" : "Видео/", ".wmv" : "Видео/", ".webm" : "Видео/"}
music_folder = {".mp3" : "Музыка/", ".aac": "Музыка/", ".flac" : "Музыка/", ".mpc" : "Музыка/", ".wma" : "Музыка/", ".wav" : "Музыка/"}
pic_folder = {".raw" : "Изображения/", ".jpg" : "Изображения/", ".tiff" : "Изображения/", ".psd" : "Изображения/", ".bmp" : "Изображения/", ".gif" : "Изображения/", ".png" : "Изображения/", ".jp2" : "Изображения/", ".jpeg" : "Изображения/"}
doc_folder = {".doc" : "Документы/", ".docx" : "Документы/", ".txt" : "Документы/", ".rtf" : "Документы/", ".pdf" : "Документы/", ".fb2" : "Документы/", ".djvu" : "Документы/", ".xls" : "Документы/", ".xlsx" : "Документы/", ".ppt" : "Документы/", ".pptx" : "Документы/", ".mdb" : "Документы/", ".accdb" : "Документы/", ".rar" : "Документы/", ".zip" : "Документы/", ".7z" : "Документы/"}
Не забываем, что после имени папки будет ещё идти имя файла, потому в конце у меня стоит /. Новые типы файлов легко добавляются путём редактирования словаря.
Запрашиваем у пользователя название папки с загрузками
В Windows и Linux папка с загрузками видится в системе с разными названиями. Для Windows это независимо от локализации папка Downloads, тогда на Linux-дистрибутивах эта папка называется «Загрузки». К тому же может произойти так, что пользователь изменил название папки с загруженными файлами и его нужно спросить отличается ли текущее название директории от стандартных.
if type_os == "Linux":
user_downloads_path = input("Как у вас называется папка с загрузками? (по-умолчанию: Загрузки) ") or "Загрузки"
if type_os == "Windows":
user_downloads_path = input("Как у вас называется папка с загрузками? (по-умолчанию: Загрузки) ") or "Downloads"
Конструкция or позволяет использовать в input значения по-умолчанию. То есть, пользователю в случае если у него нет изменений в имени папки с загрузками, не нужно ничего делать, программа сама подставит нужные значения в дальнейший код.
Устанавливаем путь до папки с загрузками
В зависимости от операционной системы папка с загрузками может находиться в разных местах. Для Linux — это папка расположена по пути /home/имя пользователя/Загрузки/, в Windows же это путь C:/Users/имяпользователя/Downloads/. Чтобы в дальнейшем не прописывать эти пути вручную, проще создать переменные, в которых эти пути будут указаны.
if type_os == "Linux":
default_path_d = "/home/" + usermane + "/" + user_downloads_path + "/"
else:
default_path_d_win = r"C:/Users/" + usermane + r"/" + user_downloads_path+ r"/"
Задаём путь к папке с загрузками для конкретного пользователя
Чтобы в дальнейшем использовать возможности ранее подключённых модулей для работы с операционной системой и файлами, мы должны задать путь к папке с загрузками, применяя те переменные, которые создали и получили ранее.
if type_os == "Linux":
downloads_path = os.listdir("/home/" + usermane + "/" + user_downloads_path)
else:
downloads_path_win = os.listdir(r"C:/Users/" + usermane + r"/" + user_downloads_path)
Ну и также для удобства дальнейшего использования кода зададим путь вида /домашняяпапка/имяпользователя
if type_os == "Linux":
default_path_u = "/home/" + usermane + "/"
else:
default_path_u_win = r"C:/Users/" + usermane + r"/"
Проверяем наличие конкретных файлов в папке с загрузками
Путём перебора словаря мы сравниваем его ключи с расширениями файлов, которые находятся в папке с загрузками. Если расширение соответствует ключу, то нужный файл нужно переместить в соответствующую дефолтную папку (имя которой является значением в словаре).
#Проверяем есть ли в папке загрузок аудиофайлы. Если есть, кидаем их в папку Музыка
for music_format in music_folder:
if type_os == "Linux":
for name_file in downloads_path:
if name_file.endswith(music_format):
result = name_file.split(str(music_format), 1)
os.rename(default_path_d + result[0] + music_format, default_path_u + music_folder.get(music_format) + result[0] + music_format)
if type_os == "Windows":
for name_file in downloads_path_win:
if name_file.endswith(music_format):
result = name_file.split(str(music_format), 1)
os.rename(default_path_d_win + result[0] + music_format, default_path_u_win + "Music" + r"/" + result[0] + music_format)
#Проверяем есть ли в папке загрузок изображения. Если есть, кидаем их в папку Изображения
for pic_format in pic_folder:
if type_os == "Linux":
for name_file in downloads_path:
if name_file.endswith(pic_format):
result = name_file.split(str(pic_format), 1)
os.rename(default_path_d + result[0] + pic_format, default_path_u + pic_folder.get(pic_format) + result[0] + pic_format)
if type_os == "Windows":
for name_file in downloads_path_win:
if name_file.endswith(pic_format):
result = name_file.split(str(pic_format), 1)
os.rename(default_path_d_win + result[0] + pic_format, default_path_u_win + "Pictures" + r"/" + result[0] + pic_format)
#Проверяем есть ли в папке загрузок документы или архивы. Если есть, кидаем их в папку Документы
for doc_format in doc_folder:
if type_os == "Linux":
for name_file in downloads_path:
if name_file.endswith(doc_format):
result = name_file.split(str(doc_format), 1)
os.rename(default_path_d + result[0] + doc_format, default_path_u + doc_folder.get(doc_format) + result[0] + doc_format)
if type_os == "Windows":
for name_file in downloads_path_win:
if name_file.endswith(doc_format):
result = name_file.split(str(doc_format), 1)
os.rename(default_path_d_win + result[0] + doc_format, default_path_u_win + "Documents" + r"/" + result[0] + doc_format)
Изначально я планировал решить задачу поиска расширений файлов путём использования регулярных выражений и занимался их формулировкой. Но потом прочитал на одном из сайтов, что если вы желаете решить какую-либо задачу, то только в последнюю очередь нужно пользоваться регулярными выражениями. Python имеет иные более простые и понятные способы сделать то, что от вас требуется. Потому вместо этих выражений расширение файла определяется путём использования строкового метода endswith(). Он берёт ключ из словаря и проверяет заканчивается ли файл на него. Затем при помощи метода split() берётся только имя файла для дальнейшего перемещения при помощи os.rename(). В качестве аргументов последнего используются все ранее созданные переменные.
Спрашиваем у пользователя, что делать с оставшимися файлами
После того, как нужные файлы отсортированы программа должна либо завершиться, либо удалить те файлы, которые не попали под перемещение (например, exe-файлы или deb-пакеты). Решение об этом принимает пользователь, по-умолчанию удаление не происходит.
#Запрос на удаление оставшихся файлов в директории загрузок
delete_user_confirm = input('Удалить из папки загрузок оставшиеся файлы? Напишите да или нет (по-умолчанию: нет) ' or 'нет')
if delete_user_confirm == 'да':
if type_os == "Linux":
files_to_remove = os.listdir(default_path_d)
for remove_files in files_to_remove:
os.remove(default_path_d + "/" + remove_files)
if type_os == "Windows":
files_to_remove = os.listdir(default_path_d_win)
for remove_files in files_to_remove:
os.remove(default_path_d_win + "/" + remove_files)
else:
print('Программа завершила работу. Все файлы размещены.')
Подводя итог
Постарался объяснить то, как работает созданная мной программа как можно понятнее. Но думаю, что желающие разбираться всё равно пересмотрят сам код несколько раз. Perfectionist Organizer представлен в двух версиях — консольной и графической. По ссылке на мой Github можно найти обе, там же приведены инструкции по тому, как скачать и использовать программу. Я прогнал консольную версию на своей основной ОС (Archlinux) и GUI-версию в виртуальной машине на Windows 7. В обоих случаях программа сработала на «УРА». Также хочу прикрепить демонстрацию того, как она работает под Linux. Если эта тема кому-то будет интересна, то в следующей статье расскажу, как я делал GUI-версию и с какими сложностями столкнулся.
Планы на будущее
Следующей на очереди программа, которая будет напоминать о важных событиях из таблицы Google Sheets в Telegram'е. Как только закончу её расскажу здесь. Любые свои вопросы и feedback по работе Perfectionist Organizer можете оставлять в комментариях или в личку. Спасибо за внимание!
Комментарии (54)
eumorozov
21.09.2019 15:20+1Вот эти словари:
video_folder = {".3gp" : "Видео/", ".avi" : "Видео/", ".flv" : "Видео/", ".m4v" : "Видео/", ".mkv" : "Видео/", ".mov" : "Видео/", ".mp4" : "Видео/", ".wmv" : "Видео/", ".webm" : "Видео/"} music_folder = {".mp3" : "Музыка/", ".aac": "Музыка/", ".flac" : "Музыка/", ".mpc" : "Музыка/", ".wma" : "Музыка/", ".wav" : "Музыка/"} pic_folder = {".raw" : "Изображения/", ".jpg" : "Изображения/", ".tiff" : "Изображения/", ".psd" : "Изображения/", ".bmp" : "Изображения/", ".gif" : "Изображения/", ".png" : "Изображения/", ".jp2" : "Изображения/", ".jpeg" : "Изображения/"} doc_folder = {".doc" : "Документы/", ".docx" : "Документы/", ".txt" : "Документы/", ".rtf" : "Документы/", ".pdf" : "Документы/", ".fb2" : "Документы/", ".djvu" : "Документы/", ".xls" : "Документы/", ".xlsx" : "Документы/", ".ppt" : "Документы/", ".pptx" : "Документы/", ".mdb" : "Документы/", ".accdb" : "Документы/", ".rar" : "Документы/", ".zip" : "Документы/", ".7z" : "Документы/"}
отформатированы просто ужасно, невозможно читать. Кроме того, зачем здесь использовать словарь, если у всех ключей одно и то же значение? Налицо неверный выбор структуры данных.
Ну и дальше работа с этими словарями вызывает… гм… вопросы… Вот это:
result = name_file.split(str(doc_format), 1)
Да за такое надо линейкой по рукам бить, простите. Потому что это просто адский способ работы со словарями, просто намеренное запутывание кода какое-то.
Aleksashka1990 Автор
21.09.2019 17:56Тонкость в том, что совсем не намеренная :) За комментарий спасибо!
Aleksashka1990 Автор
22.09.2019 09:24некому линейкой по рукам бить, я одинок и познаю Python самостоятельно :) потому мне и нужна критика, разбор кода, указание на ошибки. на мой взгляд, это лучше, чем писание «в стол».
eumorozov
22.09.2019 10:46Начинать самостоятельно надо с Python Tutorial. Потом пробежаться по Library Reference, хотя бы бегло, чтобы не изобретать велосипед.
И когда вы пишете код типа этих адских словарей, которые на 90% состоят из копи-пасты, в тот момент, когда вы 5-й раз писали одно и то же, уже надо остановиться и задать себе вопрос: «А то ли я делаю?»
Хороший программист почти никогда не копипастит. И не пишет такой boilerplate код. Кстати, вот эти бесконечные конкатенации путей
file_name_a + file_name_b
в общем из той же оперы.
vpiskunov
21.09.2019 15:30+4Да простит меня комьюнити за мое мнение, но, еще раз ИМХО, статьи с подобным уровнем подготовки а-ля курсач 3-го курса первого полугодия способствуют общему понижению уровня ресурса.
Особенно, если учесть использование жутких антипаттернов.
DollaR84
21.09.2019 16:20Я тоже сделаю небольшое замечание по словарям. Постоянное повторение одного и того же куска кода, или значения в словаре, или еще что повторяющееся должно сразу наталкивать на мысль, что здесь что-то не так, и наверняка можно оптимизировать более доступно.
Например все эти 4 словаря можно преобразовать к одному словарю типа:
folders = { "Видео/": [".3gp", ".avi", ".flv", ".m4v", ".mkv", ".mov", ".mp4", ".wmv", ".webm"], "Музыка/": [".mp3", ".aac", ".flac", ".mpc", ".wma", ".wav"], "Изображения/": [".raw", ".jpg", ".tiff", ".psd", ".bmp", ".gif", ".png", ".jp2", ".jpeg"], "Документы/": [".doc", ".docx", ".txt", ".rtf", ".pdf", ".fb2", ".djvu", ".xls", ".xlsx", ".ppt", ".pptx", ".mdb", ".accdb", ".rar", ".zip", ".7z"] }
Тогда потом получится один маленький цикл на 4 итерации. А Наличие расширения в списке можно проверять оператором in.
Например что-то типа такого:
ext = #тут вы получаете расширение файла for folder, ext_list in folders.items(): if ext in ext_list: #ваш код
Denai
22.09.2019 00:06Здесь точка в каждом из значений не менее лишняя, чем повторение имени папки
DollaR84
22.09.2019 11:12Возможно. Способ получения расширения файлов и работа с ними оставлена автору. Я просто показал как можно реорганизовать словарь для более короткого определения и более удобной работы с ним. Так же замечу, что мой вариант не является единственно правильным, а просто один из вариантов, который как по мне в первую очередь бросается в голову, глядя на словари автора. Каждый может построить свои структуры, как кому по нраву.
tsklab
21.09.2019 18:02C:/Users/имяпользователя/Downloads/
В разных версиях Windows эта папка находится в других местах. И это по-умолчанию, так как её можно переместить или переопределить, опять же в зависимости от версии Windows/Aleksashka1990 Автор
21.09.2019 20:09В версиях с 7 до 10 папка загрузок всегда находится по одному пути. Проверено уже. А вот возможность переноса это да — я это не учитывал намеренно пока что.
maxzhurkin
24.09.2019 21:01Это только если:
- Windows установлена на диске C:
- Профиль пользователя находится в C:/Users/имяпользователя/
- пользователь никуда не переместил папку загрузок
- пользователь не переопределил путь к папке загрузок
Не многовато ли оговорок для того, чтобы ваши слова соответствовали истине?
Denai
21.09.2019 20:24А с чего вы взяли что папка с загрузками лежит по пути «C:/Users/» + usermane + r"/" + user_downloads_path"?
ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D1%80%D0%B5%D0%B4%D1%8BAleksashka1990 Автор
21.09.2019 21:47Ну вот у вас где папка Загрузки находится?
Denai
21.09.2019 21:48+1D:/downloads/
Aleksashka1990 Автор
21.09.2019 22:44Но это явно не стандартный путь. Вариант, при котором папка перемещена пользователем я пока что не рассматривал даже.
andreymal
21.09.2019 22:48А зря. Плюс одна ошибка в статье. У меня вот папки загрузки тоже расположены не по стандартным путям ни в винде, ни в линуксе.
И домашняя папка пользователя целиком тоже может быть перемещена как минимум в линуксе.
cy-ernado
21.09.2019 20:27+1Очень напомнило мою первую программу на питоне восьмилетней давности, правда я там всё же выбрал регекспы :)
Denai
22.09.2019 00:18А что произойдёт, если в папку для перемещения прав на запись не будет или самой папки не будет или она совпадёт с исходной или файл будет занят другим процессом на момент перемещения, но не будет занят на момент завершения (при включённом удалении)? А с симлинками и вложенными папками что будет? Если места не хватит для переноса файлов, они все в конце удалятся?
felix21
22.09.2019 09:22Надо сделать примерно то же, но с целым диском (500 Гб, ntfs). На нем после восстановления данных образовалось около четырехсот папок до тысячи файлов в каждой. Надо отсортировать их по расширениям и положить в соответствующие папки. Система основана на Archlinux, если что.
Есть ли несложный способ для выполнения этой задачи?svk28
22.09.2019 12:31Примерно так:
shell>: export OUTDIR=~/tmp/mp3; mkdir $OUTDIR; find /mnt/disk/ -type f -name "*.mp3" -print -exec cp {} $OUTDIR \;
Данная команда найдет все mp3-файлы в каталоге /mnt/disk (куда примонтирован диск) и скопирует их в созданный каталог tmp/mp3 в домашнем каталоге пользователя.
Если я правильно понял задачу.
Если нужно переместить, то «cp» меняется на «mv», но сперва лучше попробовать с копированием =)
Это решение в лоб, но по хорошему надо сразу автоматом анализировать типы файлов, создавать соответственно каталоги и копировать туда.felix21
23.09.2019 14:40Спасибо, Вы правильно поняли задачу. Только желательно сразу перемещать файлы в соответствующие папки на том же диске, ибо под линукс отрезано не так много места на диске.
Если бы ещё и папки автоматом создавались… ))) Но это не принципиально.
bogolt
22.09.2019 10:14лучше объединять пути через
os.path.join(root, dir)
Спрашивать у пользователя в тексте ввод директории не очень для него удобно. Легко допустить ошибку при вводе. Навреное лучше подключиться к одной из гуи библиотек и показать нормальное окно выбора директории. что-то типа PyWx или PyQt подойдет, хотя конечно подключать их для одного окна это жуткий оверкилл ( хотя кого я обманываю сейчас весь софт так пишется ).eumorozov
22.09.2019 10:33Нет уже смысла использовать
os.path.join
—pathlib
во много раз удобнее.devpony
22.09.2019 14:47-1Код использующий
pathlib
очень плохо читается и слишком неявный в отличии отos.path.join
.eumorozov
22.09.2019 14:53А можно пример, где он плохо читается? Я мало работаю с путями, и там, где использюу, мне кажется, что выглядит вполне читаемо.
devpony
23.09.2019 13:24В одном открытом проекте встречал:
path = long_var_name_a / long_var_name_b / long_var_name_c / long_var_name_d / long_var_name_e
Вместо:
path = os.path.join( long_var_name_a, long_var_name_b, long_var_name_c, long_var_name_d, long_var_name_e, )
Форматирование это только одна проблема. Если не держать в голове, что в переменных пути, придётся отматывать контекст назад, пытаясь понять, что же здесь на что делится. С
join
такой проблемы нет, он задаёт контекст сразу же.
kAIST
22.09.2019 22:33Да можно же использовать tkinter, который на windows к тому же идет в комплекте с питоном. Системный диалог выбор директории, реализуется в одну строчку (кроме импорта конечно же)
Aleksashka1990 Автор
22.09.2019 22:40У меня Archlinux, потому я ориентировался в первую очередь на Linux-based дистрибутивы.
eumorozov
Можно побыть немного занудой?
Использование модуля
pathlib
из стандартной библиотеки приводит к гораздо более читаемому и удобному коду. Префиксr
в части послеelse
совершенно не нужен. Сpathlib
код стал бы намного более читабельный, сейчас это какая-то каша, в которой глаза постоянно спотыкаются на бесконечных+
.Пользовательские каталоги типа
Downloads
под Linux правильно брать из xdg-user-dirs. В Windows, кстати, пользователь тоже может менять их местоположение.Aleksashka1990 Автор
конечно можно и даже нужно :) спасибо за дополнения, учту в сл. раз или может даже перепишу это, чтобы не было кашей. да, в данный момент вообще не учитывается, что пользователь мог перенести Загрузки в другое место.
Lazytech
Тоже немного побуду занудой: наверное, все-таки не usermane («грива пользователя»), а username («имя пользователя»). :)
maxzhurkin
Кто-то уже отписал кучу сообщений об ошибке в личку
Aleksashka1990 Автор
ошибки в основном грамматические, по крайней мере в личке инфа о них
maxzhurkin
Занудой надо быть последовательно: есть ещё относительно распространённые ОС вроде Free/Net/Open/DraginFlyBSD (пожалуй, примерно в порядке убывания распространённости/популярности), для которых код бы вполне работал, если бы не линуксовиндовый шовинизм автора
P.S. кажется, я не учёл, что если последовать вашим рекомендациям, он, видимо, и заработает и там. Тем они (рекомендации) ещё ценнее
Aleksashka1990 Автор
ну на Windows и Linux работает. а на др. системы я даже не ориентировался. потому что не надо.
eumorozov
Оно только у вас работает. Например, я после установки Linux часто меняю каталоги типа
~/Загрузки
, потому что много работаю в командной строке, и мне неудобно в командной строке постоянно менять язык и регистр. И все программы корректно это отрабатывают, потому что есть стандарты Free Desktop, которые описывают, где брать местоположения подобных каталогов.Для Windows такие стандарты тоже есть, и многие пользователи в Windows тоже переносят подобные каталоги. Например, разбивают диск на два: системный и пользовательский, и переносят каталоги типа
Мои Документы
на пользовательский раздел (D:\
).И универсальный инструмент должен использовать эти настройки, тем более, что это совсем несложно.
Aleksashka1990 Автор
вы полагаете, что только я после установки системы оставляю папку с загрузками там, где она стоит по-умолчанию? :) а все иные пользователи сразу же начинают её перемещать в другие места? говоря многие вы оперируете достаточно размытыми понятиями. например, число пользователей Office 365 (а следовательно пользователей Windows) достигло 135 млн человек. вы можете назвать какой процент из этих людей переносит папку загрузок после установки системы?
BoogieMan75
Вы просто делаете не универсальное решение и вам на это пытаются указать, а вы старательно отмазываетесь. Хотите делать что то — старайтесь делать это хорошо.
Aleksashka1990 Автор
Это не попытка указать на ошибки, а попытка умничать. В этом случае человек пытается выдать свой опыт за то, что делают другие. Чего делать не стоит. Обратите внимание на нормальные указания на ошибки и мою реакцию — увидите разницу. Я с радостью воспринимаю и принимаю критику, но только если это критика, а не выпендреж.
eumorozov
Если я пытаюсь умничать, то как быть с тем, что, например, Ubuntu для установки в каждом языке создает свои каталоги:
~/Рабочий Стол
vs~/Desktop
,~/Загрузки
vs~/Downloads
, и т.д. для каждого языка. У каждого пользователя, который при установке выбрал язык отличный от английского, будут другие названия каталогов.Ну и вообще, ваш подход напоминает анекдот про машину для бритья. Что лица у всех разные… до первого использования машины.
Удачи в реальном мире с таким подходом!
Aleksashka1990 Автор
Ну так у меня и прописан вариант для русской версии установки. Я не заявлял нигде, что этот код будет работать на всех локализациях Linux. Вы читайте внимательно — в первую очередь я делал это для себя. У меня русская версия Archlinux, Windows в виртуалке. На основании этого и делал. Зачем в таком случае требовать универсальности? Если её изначально не заявлено на данный момент.
eumorozov
Критику никто не любит, и критиковать правильно никто не умеет.
Просто получается, что вы выложили скрипт:
Ну и в чем тогда смысл? Как пример хорошего кода на Python — он таким не является. Как инструмент, который может быть полезен другим — тогда надо было сделать более универсальным. Чтобы покритиковали — так мы и критикуем. :)