Русский
Русский
English
Статистика
Реклама

Пайтон

Декораторы Python хватит это терпеть

02.06.2021 12:04:43 | Автор: admin
Конец страданиям.Конец страданиям.

Всем привет! В этой статье я расскажу об инструменте, разработанном мной, который изменяет работу декораторов в Python и делает их более Питоничными.

Я не буду рассказывать про области применения декораторов. Есть множество статей на эту тему.

Для начала, давайте вспомним: что же такое декораторы в Пайтон.

Если совсем просто, то это удобный способ передать одну функцию в другую и получить третью. В этом определении нет ни одного слова правды, но мы вернёмся к этому позже.

Давайте разбираться!

Как работают декораторы

def decorator_function(wrapped_func):    def wrapper():        print('Входим в функцию-обёртку')        print('Оборачиваемая функция: ', wrapped_func)        print('Выполняем обёрнутую функцию...')        wrapped_func()        print('Выходим из обёртки')    return wrapper

Так выглядит функция-декоратор. Как вы можете увидеть, она принимает в качестве аргумента другую функцию. Затем с этой функцией что-то делают внутри вложенной функции-обёртки и возвращают из декоратора уже обёртку вместо исходной функции.

Теперь можно декорировать:

@decorator_functiondef hello_world():        print('Hello world!')hello_world()

Здесь декоратор получает функцию hello_world, и подменяет её своей вложенной функцией wrapper.

Вывод:

Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

Важно помнить!

Декоратор исполняется только один раз: при объявлении оборачиваемой функции. При дальнейшем вызове функции исполняется только вложенная функция wrapper.

Мы это увидим, если добавим две строчки в наш декоратор:

def decorator_function(wrapped_func):    print('Входим в декоратор')    def wrapper():        ...    print('Выходим из декоратора')    return wrapper
@decorator_functiondef hello_world():        print('Hello world!')
Входим в декораторВыходим из декоратора
hello_world()
Входим в функцию-обёрткуОборачиваемая функция:  <function hello_world at 0x0201B2F8>Выполняем обёрнутую функцию...Hello world!Выходим из обёртки

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

У функции, которую мы декорируем, могут быть аргументы. Принимает их вложенная функция wrapper:

def decorator_function(wrapped_func):    def wrapper(*args):        ...        wrapped_func(args)        ...    return wrapper  @decorator_functiondef hello_world(text):        print(text)    hello_world('Hello world!')

А ещё, аргументы могут быть переданы непосредственно в декоратор:

def fictive(decorator_text):    def decorator_function(wrapped_func):                def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper        return decorator_function@fictive(decorator_text='Hello, ')def hello_world(text):        print(text)hello_world('world!')

Здесь аргумент decorator_text передаётся при декорировании в строке 11 и попадает в функцию fictive, строка 1. Таким образом, появился ещё один уровень вложенности только для того, чтобы принять аргументы декоратора.

Вывод:

Hello, world!

Пойдём дальше. А что, если декоратор может быть, в одних случаях с аргументами, в других - без аргументов? Поехали!

def fictive(_func=None, *, decorator_text=''):    def decorator_function(wrapped_func):        def wrapper(*args):            print(decorator_text, end='')            wrapped_func(*args)        return wrapper    if _func is None:        return decorator_function    else:        return decorator_function(_func)      @fictivedef hello_world(text):        print(text)hello_world('Hello, world!')@fictive(decorator_text='Hi, ')def say(text):        print(text)say('world!')

Вывод:

Hello, world!Hi, world!

Как Вам код? Вспомним, мантру Питонистов из начала статьи:

Декораторы - это удобный способ передать...

Ничего, на помощь придёт DecoratorHelper! Но, перед этим, ещё пара слов о декораторах.

Мифы декораторов

  1. Декораторы удобны. Думаю, с этим мы уже разобрались.

  2. В декораторы нужно передавать функции. Передавать можно не только функции, но и любые callable объекты. Это такие объекты, у которых определён дандер метод (магический метод) __call__. Этот метод отвечает за операции, которые будут произведены при вызове объекта (когда вы ставите скобочки после имени объекта: object()). Вместо функции может быть метод или класс.

  3. Декораторы - это функции. И опять: это может быть любой callable объект.

  4. Декоратор возвращает функцию. Декоратор может возвращать что угодно. Стоит лишь помнить, что если декоратор возвращает не callable объект, то вызывать его не получится.

  1. Передавать можно не только функции, но и аргументы. Вместо функции может быть метод или некоторые классы.


DecoratorHelper: решение проблем

Устанавливаем модуль:

pip install DecoratorHelper

Импортируем и используем как декоратор:

from DecoratorHelper import DecoratorHelper@DecoratorHelperdef hello_world(text):        print(text)hello_world('Hello, world!')

Что это даёт?

  1. Вы больше не думаете над тем, будут ли аргументы у декоратора. DecoratorHelper думает об этом вместо Вас.

  2. Вы получаете удобный, Питоничный доступ ко всем аргументам, самой функции, к тому, что будет происходить до и после выполнения функции.

В итоге Вы получаете вместо функции объект, который имеет следующие атрибуты:

  • self.function - оборачиваемая функция

  • self.decorator_args- аргументы декоратора. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.function_args- аргументы функции. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.

  • self.pre_function - то, что будет происходить перед выполнением функции (так можно превратить функцию в коллбэк).

  • self.post_function - то, что будет происходить после выполнения функции (так можно добавить функции в коллбэк).

Как использовать?

Перепишем приведённый ранее код декоратора, который может принимать/не принимать аргументы:

from DecoratorHelper import DecoratorHelperdef fictive(object):    object.pre_function = lambda : print(*object.decorator_args[:-1], end='')    return object    @fictive@DecoratorHelper('Hello, ')def hello_world(text):        print(text)hello_world('world!')

Как мы можем видеть, тело декоратора сократилось в 8 раз. Profit!

Ограничение! Первым аргументом нельзя передавать callable объекты, иначе всё сломается :) Думаю, для большинства задач, это не смертельно...

Что дальше?

В следующих версиях планируется:

  • Улучшенная обработка аргументов.

  • Встроенный счётчик вызовов.

  • Возможность превратить объект в синглтон.

  • Возможность превратить объект в буилдер.

  • Может быть, возможность подключить асинхронность.

    И всё это в максимально удобном формате: singleton = True.

P. S. Если в комментариях будет интерес к теме, напишу вторую статью о том, как DecoratorHelper устроен. Но сразу скажу, что это уровень Junior+.

Подробнее..

Сортируем файлы с помощью Python

11.06.2021 16:11:37 | Автор: admin

Штош. Скорее всего, у многих в папке загрузок собиралась куча разных инсталляторов, архивов и прочих файлов. И вот наступает момент, когда из этой кучи экскрементов нужно найти какой-то файл. Вот я и подумал, почему бы не написать скрипт сортировщика по расширениям файлов на Python?

Пишем код

Для начала импортируем стандартный модуль os для работы с операционной системой. С помощью этого модуля мы будем создавать папки и перемещать файлы.

import os

Создадим переменную для пути папки или диска, в которой мы будем сортировать файлы. В моих любимых окошечках путь к файлу записывается через backslash, который в Python является специальным символом экранирования. Поэтому мы либо пишем 2 бэкслэша, либо добавляем перед строкой букву r, тем самым подавляя экранирование.

main_path = 'd:\\down'# main_path = r'd:\down

Чтобы создать папку, используем метод os.mkdir()

os.mkdir(main_path + '\\aboba')

Создаем много папок

Напишем функцию для создания папок из списка названий. Для каждого названия проверяем существование папки с помощью метода os.path.exists().

# also creates folders from dictionary keysdef create_folders_from_list(folder_path, folder_names):    for folder in folder_names:        if not os.path.exists(f'{folder_path}\\{folder}'):            os.mkdir(f'{folder_path}\\{folder}')

Теперь давайте создадим словарь extensions. Ключи - названия папок. Значения - расширения файлов для каждой отдельной папки.

# key names will be folder names!extensions = {    'video': ['mp4', 'mov', 'avi', 'mkv', 'wmv', '3gp', '3g2', 'mpg', 'mpeg', 'm4v',               'h264', 'flv', 'rm', 'swf', 'vob'],    'data': ['sql', 'sqlite', 'sqlite3', 'csv', 'dat', 'db', 'log', 'mdb', 'sav',              'tar', 'xml'],    'audio': ['mp3', 'wav', 'ogg', 'flac', 'aif', 'mid', 'midi', 'mpa', 'wma', 'wpl',              'cda'],    'image': ['jpg', 'png', 'bmp', 'ai', 'psd', 'ico', 'jpeg', 'ps', 'svg', 'tif',               'tiff'],    'archive': ['zip', 'rar', '7z', 'z', 'gz', 'rpm', 'arj', 'pkg', 'deb'],    'text': ['pdf', 'txt', 'doc', 'docx', 'rtf', 'tex', 'wpd', 'odt'],    '3d': ['stl', 'obj', 'fbx', 'dae', '3ds', 'iges', 'step'],    'presentation': ['pptx', 'ppt', 'pps', 'key', 'odp'],    'spreadsheet': ['xlsx', 'xls', 'xlsm', 'ods'],    'font': ['otf', 'ttf', 'fon', 'fnt'],    'gif': ['gif'],    'exe': ['exe'],    'bat': ['bat'],    'apk': ['apk']}

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

Получаем пути подпапок и файлов

Пишем функцию для получения путей подпапок. Для каждого объекта в методе os.scandir() проверяем, является ли он каталогом.

def get_subfolder_paths(folder_path) -> list:    subfolder_paths = [f.path for f in os.scandir(folder_path) if f.is_dir()]    return subfolder_paths

Можно создать функцию получения имен подпапок, которая нам не пригодится дальше. Почему бы и нет? На самом деле, как сниппет для дальнейших проектов она может быть полезна.

Нам нужно взять пути подпапок, разделить по бэкслэшам строку и взять последний элемент.

def get_subfolder_names(folder_path) -> list:    subfolder_paths = get_subfolder_paths(folder_path)    subfolder_names = [f.split('\\')[-1] for f in subfolder_paths]    return subfolder_names

Теперь получим пути всех файлов в папке, скопируем функцию get_subfolder_paths() и добавим в условие генератора not.

def get_file_paths(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    return file_paths

Для полного счастья не хватает только функции получения имен файлов.

def get_file_names(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    file_names = [f.split('\\')[-1] for f in file_paths]    return file_names

Сортируем файлы

Приступаем к функции сортировки. Получаем пути файлов в переменную file_paths. Создаем переменную ext_list со списком метода словаря extensions.items(). Обращение к списку по индексу возвращает нам пару ключ-значение в виде списка, первый элемент которого - это ключ или название папки в нашем проекте, а второй элемент - это значение, то есть расширения файлов для этой папки.

def sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())

Теперь создадим цикл для каждого пути файла в списке. Вытащим отдельно расширение и имя файла.

for file_path in file_paths:  extension = file_path.split('.')[-1]  file_name = file_path.split('\\')[-1]

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

for dict_key_int in range(len(ext_list)):  if extension in ext_list[dict_key_int][1]:    print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')    os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')

Сделать это можно при помощи изменения пути файла методом os.rename("Путь файла сейчас", "Будущий путь файла")

Готовая функция сортировки файлов:

def sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())    for file_path in file_paths:        extension = file_path.split('.')[-1]        file_name = file_path.split('\\')[-1]        for dict_key_int in range(len(ext_list)):            if extension in ext_list[dict_key_int][1]:                print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')                os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')

Удаляем пустые папки

Остался последний штрих - удаление пустых папок. Все просто. Создаем функцию. Получаем пути подпапок. Проверяем, какой список возвращает метод os.listdir("folder_path") для каждой подпапки. Если возвращается пустой список, значит удаляем папку с помощью os.rmdir("folder_path")

def remove_empty_folders(folder_path):    subfolder_paths = get_subfolder_paths(folder_path)    for p in subfolder_paths:        if not os.listdir(p):            print('Deleting empty folder:', p.split('\\')[-1], '\n')            os.rmdir(p)

Полный код программы

import osmain_path = 'd:\\down'# key names will be folder names!extensions = {    'video': ['mp4', 'mov', 'avi', 'mkv', 'wmv', '3gp', '3g2', 'mpg', 'mpeg', 'm4v', 'h264', 'flv',              'rm', 'swf', 'vob'],    'data': ['sql', 'sqlite', 'sqlite3', 'csv', 'dat', 'db', 'log', 'mdb', 'sav', 'tar', 'xml'],    'audio': ['mp3', 'wav', 'ogg', 'flac', 'aif', 'mid', 'midi', 'mpa', 'wma', 'wpl', 'cda'],    'image': ['jpg', 'png', 'bmp', 'ai', 'psd', 'ico', 'jpeg', 'ps', 'svg', 'tif', 'tiff'],    'archive': ['zip', 'rar', '7z', 'z', 'gz', 'rpm', 'arj', 'pkg', 'deb'],    'text': ['pdf', 'txt', 'doc', 'docx', 'rtf', 'tex', 'wpd', 'odt'],    '3d': ['stl', 'obj', 'fbx', 'dae', '3ds', 'iges', 'step'],    'presentation': ['pptx', 'ppt', 'pps', 'key', 'odp'],    'spreadsheet': ['xlsx', 'xls', 'xlsm', 'ods'],    'font': ['otf', 'ttf', 'fon', 'fnt'],    'gif': ['gif'],    'exe': ['exe'],    'bat': ['bat'],    'apk': ['apk']}# also creates folders from dictionary keysdef create_folders_from_list(folder_path, folder_names):    for folder in folder_names:        if not os.path.exists(f'{folder_path}\\{folder}'):            os.mkdir(f'{folder_path}\\{folder}')def get_subfolder_paths(folder_path) -> list:    subfolder_paths = [f.path for f in os.scandir(folder_path) if f.is_dir()]    return subfolder_pathsdef get_file_paths(folder_path) -> list:    file_paths = [f.path for f in os.scandir(folder_path) if not f.is_dir()]    return file_pathsdef sort_files(folder_path):    file_paths = get_file_paths(folder_path)    ext_list = list(extensions.items())    for file_path in file_paths:        extension = file_path.split('.')[-1]        file_name = file_path.split('\\')[-1]        for dict_key_int in range(len(ext_list)):            if extension in ext_list[dict_key_int][1]:                print(f'Moving {file_name} in {ext_list[dict_key_int][0]} folder\n')                os.rename(file_path, f'{main_path}\\{ext_list[dict_key_int][0]}\\{file_name}')def remove_empty_folders(folder_path):    subfolder_paths = get_subfolder_paths(folder_path)    for p in subfolder_paths:        if not os.listdir(p):            print('Deleting empty folder:', p.split('\\')[-1], '\n')            os.rmdir(p)if __name__ == "__main__":    create_folders_from_list(main_path, extensions)    sort_files(main_path)    remove_empty_folders(main_path)

Настройка программы под свои нужды

Как вы уже могли понять, программа довольно гибкая, и вы можете настроить ее под себя. Для этого нужно всего-лишь изменить словарь extensions.

Приведу пример. Для каждого видео на свой YouTube канал я создаю каталог, в котором есть папки для футажей, картинок, звука, mkv файлов для последующего конвертирования в mp4 (premiere не любит mkv) и самого проекта.

Вот такой словарь.

main_folder = 'f:\\shtosh python\\new video'# key names will be folder names!extensions = {    'img': ['jpg', 'png', 'bmp', 'gif', 'ico', 'jpeg'],    'audio': ['mp3', 'wav'],    'footage': ['mp4', 'mov', 'avi'],    'mkv': ['mkv'],    'prj': []}

С помощью такого подхода я могу накидать любые файлы для монтажа в одну папку, затем запустить скрипт и легко импортировать все это дело в premiere pro.

Заключение

Штош. Код лежит на GitHub. Берите, изменяйте под себя, пользуйтесь на здоровье. Буду рад любому фидбеку.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru