Все мы любим pathlib за его краткость, логичность и ООП-подход.
История его появления в стандартных библиотеках это пример как надо интегрировать новые принципы в архитектуру языка программирования или любого приложения.
Расскажу кратко, по этапам:
🔸 Сначала у нас был os.path. Это функциональный подход который выглядит громоздко и многословно.
🔸Возникла задача адаптации всех стандартных методов для работы с данной библиотекой. Чтобы каждый из них смог понять объект Path и правильно его обработать. И самое интересное, как это было реализовано.
🔸 Если объект Path конвертнуть в строку
Просить юзеров конвертить в str когда нужно? Тоже нет, не pythonic-way.
В результате в Python 3.6 появляется новый абстрактный класс os.PathLike и понятие path-like object, который понимают все стандартные методы работы с файлами. Теперь, при написании библиотеки для работы с путями, ваша задача следовать правилам этого типа чтобы аккуратно вписаться в экосистему Python-путей.
А правила там простые, magic-метод ˍˍfspathˍˍ (file system path), который возвращает валидный путь.
Все методы для обработки файлов используют os.fspath() для объекта пути перед его использованием.
А если наследоваться не получается то можно воспользоваться методом register
🔸 Вывод
В этой истории показательно то, что вместо внесений изменений под конкретный случай (читай костыль), разработчики создали подходящие условия для всех.
То есть не библиотека диктует правила как с ней обходиться, а язык создаёт правила как нужно подстроиться библиотеке чтобы все были довольны. В результате разработчики не только вписали удобную библиотеку в привычный нам код, но и мы получили возможность писать свои альтернативные системы работы с путями, которые понимаются всеми стандартными методами.
Это принцип за который я сам всегда всеми руками ЗА.
Низкоуровневые решения не должны заниматься частными случаями. Если мы попробуем подстроиться под каждый необычный случай то получим жуткую кашу из if-else, try-except или еще чего похуже.
Когда вас просят поправить ваш api, потому что вот тут в таком-то случае у юзера всё ломается, остановитесь на секунду и подумайте, а точно ли вам нужно делать именно то что просят?
Если ваше решение начинается с if, это неверное решение!
#libs #tricks #pathlib
История его появления в стандартных библиотеках это пример как надо интегрировать новые принципы в архитектуру языка программирования или любого приложения.
Расскажу кратко, по этапам:
🔸 Сначала у нас был os.path. Это функциональный подход который выглядит громоздко и многословно.
#пример переименования🔸 В версии 3.4 появилась библиотека pathlib которая поменяла ход игры. Теперь работаем с путями как с объектами. Кода стало меньше, счастья больше.
import os
my_path = '/path/to/file.ext'
dir_name = os.path.dirname(my_path)
new_name = 'file2' + os.path.splitext(my_path)[1]
new_path = os.path.join(dir_name, new_name)
os.rename(my_path, new_path)
# пример переименования с pathlib🔸 С приходом этой сущности появились и проблемы, старые методы для работы с путями просто не понимают этот тип. Они работают только со строками.
from pathlib import Path
my_path = Path('/path/to/file.ext')
new_path = my_path.with_name('file2').with_suffix(my_path.suffix)
my_path.rename(new_path)
my_path = Path('/path/to/file.ext')
open(my_path)
TypeError: invalid file: PosixPath('...')
То же самое с subprocess и остальными.🔸Возникла задача адаптации всех стандартных методов для работы с данной библиотекой. Чтобы каждый из них смог понять объект Path и правильно его обработать. И самое интересное, как это было реализовано.
🔸 Если объект Path конвертнуть в строку
str(Path) то мы получим правильный путь. Получается, что надо просто добавить форсированную конвертацию аргументов в str везде где это нужно? Нет!, так мы только всё усложим.Просить юзеров конвертить в str когда нужно? Тоже нет, не pythonic-way.
В результате в Python 3.6 появляется новый абстрактный класс os.PathLike и понятие path-like object, который понимают все стандартные методы работы с файлами. Теперь, при написании библиотеки для работы с путями, ваша задача следовать правилам этого типа чтобы аккуратно вписаться в экосистему Python-путей.
А правила там простые, magic-метод ˍˍfspathˍˍ (file system path), который возвращает валидный путь.
Все методы для обработки файлов используют os.fspath() для объекта пути перед его использованием.
class MyPath(os.PathLike):Это сработает и без наследования от os.PathLike, Достаточно и только метода ˍˍfspathˍˍ. Но лучше всё же наследоваться, чтобы добавить дополнительные проверки субклассов.
def __init__(self, val):
self.val = val
def __fspath__(self):
return self.val
path = MyPath('/path/to/file/ext')
f = open(path, 'w') # PROFIT!!!
А если наследоваться не получается то можно воспользоваться методом register
os.PathLike.register(MyPath)Кстати, именно так и поступили в pathlib
🔸 Вывод
В этой истории показательно то, что вместо внесений изменений под конкретный случай (читай костыль), разработчики создали подходящие условия для всех.
То есть не библиотека диктует правила как с ней обходиться, а язык создаёт правила как нужно подстроиться библиотеке чтобы все были довольны. В результате разработчики не только вписали удобную библиотеку в привычный нам код, но и мы получили возможность писать свои альтернативные системы работы с путями, которые понимаются всеми стандартными методами.
Это принцип за который я сам всегда всеми руками ЗА.
Низкоуровневые решения не должны заниматься частными случаями. Если мы попробуем подстроиться под каждый необычный случай то получим жуткую кашу из if-else, try-except или еще чего похуже.
Когда вас просят поправить ваш api, потому что вот тут в таком-то случае у юзера всё ломается, остановитесь на секунду и подумайте, а точно ли вам нужно делать именно то что просят?
Если ваше решение начинается с if, это неверное решение!
#libs #tricks #pathlib
GitHub
cpython/os.py at main · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Как получить справку в Python имея только консоль?
🔸 Основная справка
🔸 Справка по объектам
Сначала нужно зайти в REPL (интерактивная консоль Python) и там вводить такой код
Специальный инструмент для работы с документаций в Python.
Доступные команды:
как использовать pydoc
То есть это не статичные HTML страницы из файлов, а сгенерированные на лету из докстрингов.
запустить веб сервер с документацией на порту 8000
Если вы на локальном хосте, то можно открыть браузер введя команду b, или добавить такой же флаг в команду чтобы браузер открылся сам сразу.
Запустить веб сервер и сразу открыть браузер
PS. Чтобы с помощью функции help() получить справку по ключевым словам, следует их писать в виде строки
🔸 Основная справка
python3 -h
Команда вводится в консоль. Даёт информацию по флагам и переменным интерпретатора.🔸 Справка по объектам
Сначала нужно зайти в REPL (интерактивная консоль Python) и там вводить такой код
>>> import some
>>> help(some)
Функция help() достаёт докстринги и распечатывает в консоль. Это самый очевидный способ получить справку по объекту не выходя из консоли. Но в такой метод нельзя передать директивы, например import или def>>> help(import)
SyntaxError: invalid syntax
🔸 модуль pydocСпециальный инструмент для работы с документаций в Python.
Доступные команды:
как использовать pydoc
python3 -m pydoc
показать справку по функции или классуpython3 -m pydoc os.path.join
показать справку по ключевым словам языкаpython3 -m pydoc <keyword>
напримерpython3 -m pydoc import
python3 -m pydoc def
справка по модулямpython3 -m pydoc <modulename>
поиск по документацииpython3 -m pydoc -k <request>
Есть еще одна интересная возможность — запускать веб сервер документации как оболочка для pydoc. То есть это не статичные HTML страницы из файлов, а сгенерированные на лету из докстрингов.
запустить веб сервер с документацией на порту 8000
python3 -m pydoc -p 8000
Теперь можете зайти на этот хост по указанному порту и получите простой сайт с документацией.Если вы на локальном хосте, то можно открыть браузер введя команду b, или добавить такой же флаг в команду чтобы браузер открылся сам сразу.
Запустить веб сервер и сразу открыть браузер
python3 -m pydoc -p 8000 -b
____________________PS. Чтобы с помощью функции help() получить справку по ключевым словам, следует их писать в виде строки
help('import')
#libsДопустим у нас есть какой-то список
Для сортировки этого списка у нас есть два пути:
🔸 функция sorted()
🔸 метод list.sort()
Надеюсь, уловили разницу? Но это было лишь вступление чтобы был ясна следующая тема.
На самом деле я хотел рассказать про операторы "=" и "+=" по отношению к спискам.
Все мы привыкли что запись
Дело в том, что со списками оператор "+" работает аналогично функции sorted(), то есть возвращает новый объект, после чего оператор "=" записывает значение в переменную. В то время как "+=" работает аналогично методу list.sort() — изменяет исходный список.
Вот небольшой пример для проверки:
Можете пройтись функцией id() чтобы точно всё проверить.
Данная фишка не сработает с кортежами, так как они неизменяемые. Оба варианта создают новый объект.
Пример, где это может вызвать неоднозначность. Класс, в атрибутах которого указывается список каких-то дефолтных полей. Во время создания инстанса мы можем их расширять через аргументы.
В конструкторе класса в первом случае мы создаём новый атрибут инстанса
Во втором случае мы меняем именно атрибут класса
#tricks
Для сортировки этого списка у нас есть два пути:
🔸 функция sorted()
>>> a = [3, 1, 2]Думаю, всем очевидно что теперь
>>> b = sorted(a)
>>> print(a, b)
[3, 1, 2] [1, 2, 3]
a и b это разные объекты. Так работает sorted(), то есть получает один список, и возвращает другой список с изменениями. Исходный список не изменяется.🔸 метод list.sort()
>>> a = [3, 1, 2]Метод list.sort() не возвращает новый список. Он вообще ничего не возвращает. Он просто сортирует исходный список.
>>> b = a.sort()
>>> print(a, b)
[1, 2, 3], None
Надеюсь, уловили разницу? Но это было лишь вступление чтобы был ясна следующая тема.
На самом деле я хотел рассказать про операторы "=" и "+=" по отношению к спискам.
Все мы привыкли что запись
x += 3Это просто более короткая версия записи
x = x + 3Но это не всегда так.
Дело в том, что со списками оператор "+" работает аналогично функции sorted(), то есть возвращает новый объект, после чего оператор "=" записывает значение в переменную. В то время как "+=" работает аналогично методу list.sort() — изменяет исходный список.
Вот небольшой пример для проверки:
>>> a = [1, 2]Во второй строке
>>> b = a
>>> a = a + [3, 4]
>>> print(a, b)
[1, 2, 3, 4] [1, 2]
a и b ссылаются на один и тот же обеъект. Но после присвоения результата оператора сложения в переменную a мы создали новый объект и переписали ссылку a.>>> a = [1, 2]А в этом примере переменная
>>> b = a
>>> a += [3, 4]
>>> print(a, b)
[1, 2, 3, 4] [1, 2, 3, 4]
a не перезаписалась, оператор отработал с исходным объектом. Поэтому мы изменили и b тоже.Можете пройтись функцией id() чтобы точно всё проверить.
Данная фишка не сработает с кортежами, так как они неизменяемые. Оба варианта создают новый объект.
Пример, где это может вызвать неоднозначность. Класс, в атрибутах которого указывается список каких-то дефолтных полей. Во время создания инстанса мы можем их расширять через аргументы.
class MyClass:Класс имеет два статических атрибута.
L1 = [0]
L2 = [0]
def __init__(self, fields):
self.L1 = self.L1 + fields
self.L2 += fields
В конструкторе класса в первом случае мы создаём новый атрибут инстанса
L1 который своим именем перекрывает атрибут класса. Такое значение L1 будет только у этого инстанса.Во втором случае мы меняем именно атрибут класса
L2, то есть это будет видно во всех инстансах данного класса.>>> obj1 = MyClass(fields=[1])
>>> print(obj1.L1, obj1.L2)
[0, 1] [0, 1]
>>> obj2 = MyClass(fields=[2])
>>> print(obj2.L1, obj2.L2)
[0, 2] [0, 1, 2]
>>> obj3 = MyClass(fields=[3])
>>> print(obj3.L1, obj3.L2)
[1, 3] [0, 1, 2, 3]
В атрибут класса L2 добавляется элемент при каждом создании инстанса.#tricks
У нас есть список с некоторыми значениями. Предположим что это какие-то дата-классы.
Нам требуется их отсортировать и сложить в словарь, где ключами будут порядковые номера.
Список:
Порядковый номер нам посчитал enumerate, значение сразу записали в словарь под этим номером, в результате для тела цикла действий не осталось 😁
#tricks
Нам требуется их отсортировать и сложить в словарь, где ключами будут порядковые номера.
Список:
values = ['a', 'c', 'f', 'e', 'b', 'g', 'd']"Сложная" функция получения ключа сортировки:
def get_key(obj):Есть вплоне очевидные способы это сделать, но я покажу неочевидный, который совершенно не советую к использованию!
return obj
rating = dict()Вопросы вызывают два момента. Что там делает
for i, rating[i] in enumerate(sorted(values, key=get_key)):
pass
rating[i] и почему цикл ничего не делает? Да-да, pass тут не для краткости примера. Это рабочий код который заполнит словарь rate.>>> print(rating)Запись
{0: 'a', 1: 'b', 2: 'c', ...}
rating[i] заменяет нам имя переменной для цикла. В простом случае нам бы пришлось писать так.for i, value in enumerate(sorted(values, key=get_key)):Но вместо создания переменной
rating[i] = value
value мы сразу записываем очередной элемент в словарь подставляя обращение к словарю по ключу вместо переменной. Python сам за нас выполняет выражение rating[i] = value на каждой итерации.Порядковый номер нам посчитал enumerate, значение сразу записали в словарь под этим номером, в результате для тела цикла действий не осталось 😁
rating = {i:x for i, x in enumerate(sorted(values, key=get_key))}
Но я очень НЕ советую писать такой неочевидный код. Лучше всего старый добрый генератор!#tricks
Думаете что curses крутая библиотека но сложная? Urwid тоже не очень помогает?
Тогда посмотрите на высокоуровневую библиотеку py_cui. Это обертка для curses которая предлагает простую схему построения интерфейсов основанную на GridLayout. Он позволяет располагать различные виджеты плиткой, что-то вроде интерфейса Metro.
Библиотека реализует простой принцип навигации:
🔸 переключение между плитками: стрелки на клавиатуре
В обычном режиме вы можете "гулять" по сетке выбирая нужный виджет.
🔸 активация виджета: Enter
В этом режиме вы входите в контекст виджета и можете с ним взаимодействовать
🔸 деактивация виджета: Esc
По нажатию на Esc вы возвращаетесь в режим выбора виджета
Посмотрите примеры и зацените приложение для работы с GIT. А еще на нем можно простые игры писать.
PS. При желании набор символов для рисования границ виджетов можно изменить.
#libs
Тогда посмотрите на высокоуровневую библиотеку py_cui. Это обертка для curses которая предлагает простую схему построения интерфейсов основанную на GridLayout. Он позволяет располагать различные виджеты плиткой, что-то вроде интерфейса Metro.
Библиотека реализует простой принцип навигации:
🔸 переключение между плитками: стрелки на клавиатуре
В обычном режиме вы можете "гулять" по сетке выбирая нужный виджет.
🔸 активация виджета: Enter
В этом режиме вы входите в контекст виджета и можете с ним взаимодействовать
🔸 деактивация виджета: Esc
По нажатию на Esc вы возвращаетесь в режим выбора виджета
Посмотрите примеры и зацените приложение для работы с GIT. А еще на нем можно простые игры писать.
PS. При желании набор символов для рисования границ виджетов можно изменить.
#libs
Я очень часто работаю в REPL. Удобная штука для разработки, поисков, тестов, дебага...
Иногда случается такая ситуация, когда я делаю вызов какой-либо функции и вижу распечатку результата в консоли. И только потом понимаю что нужно было это сохранить в переменную!
В этом случае выручает одна интересная особенность интерактивной консоли, это переменная "_" (нижнее подчеркивание).
Python по умолчанию сохраняет в неё результат последнего вызова если этот результат не был никуда сохранён.
🔸 Если у вас не REPL, то есть простой запуск скрипта.
🔸 Если вы самостоятельно объявили эту переменную или сделали импорт с этим именем.
Иногда случается такая ситуация, когда я делаю вызов какой-либо функции и вижу распечатку результата в консоли. И только потом понимаю что нужно было это сохранить в переменную!
>>> get_some()А почему бы не выполнить еще раз но уже сохранив в переменную?
<some result>
>>> result = get_some()Да, чаще всего так и делаю, но иногда это неудобно или недопустимо. Например, если результат считается долго или каждый раз он будет другой.
В этом случае выручает одна интересная особенность интерактивной консоли, это переменная "_" (нижнее подчеркивание).
Python по умолчанию сохраняет в неё результат последнего вызова если этот результат не был никуда сохранён.
>>> get_some()То есть, сразу после вызова достаточно скопировать значение из этой переменной
<some result>
>>> print(_)
<some result>
>>> get_some()Это не сработает в двух случаях:
<some result>
>>> result = _
>>> print(result)
<some result>
🔸 Если у вас не REPL, то есть простой запуск скрипта.
🔸 Если вы самостоятельно объявили эту переменную или сделали импорт с этим именем.
>>> _ = False#tricks
>>> get()
<some result>
>>> print(_)
False
Как прочитать файл из ZIP архива не распаковывая этот архив?
Недавно была задача достать данные из JSON файла который лежит в ZIP архиве.
Первое, что приходит в голову – распечатать архив в TEMP и найти нужный файл. Но с Python можно сделать проще: прочитать нужный файл в архиве не извлекая всё содержимое.
Например, есть некий архив
Вот код который это сделает:
Недавно была задача достать данные из JSON файла который лежит в ZIP архиве.
Первое, что приходит в голову – распечатать архив в TEMP и найти нужный файл. Но с Python можно сделать проще: прочитать нужный файл в архиве не извлекая всё содержимое.
Например, есть некий архив
archive.zip. Где-то внутри есть файл config.json который нам надо прочитать.Вот код который это сделает:
from zipfile import ZipFile#tricks #libs
from pathlib import Path
import json
def get_json_from_zip(archive, file_name):
zip = ZipFile(archive)
for zipname in zip.namelist():
if Path(zipname).name == file_name:
with zip.open(zipname) as f:
return json.load(f)
config_name = 'config.json'
archive_path = 'archive.zip'
conf = get_json_from_zip(archive_path, config_name)
Как узнать кто вызвал функцию?
Порой требуется выяснить, кто именно вызвал конкретную функцию?
Конечно, можно запустить дебаг и выполнять построчно логику отслеживая откуда мы пришли в конкретную точку. Но это долго.
Чтобы просто узнать имя функции которая вызвала текущую функцию можно сделать так.
Traceback удобен тем, что показывает цепочку вызовов, которая привела к ошибке.
Но модуль traceback также позволяет распечатать стек вызовов не выбрасывая исключение.
Порой требуется выяснить, кто именно вызвал конкретную функцию?
Конечно, можно запустить дебаг и выполнять построчно логику отслеживая откуда мы пришли в конкретную точку. Но это долго.
Чтобы просто узнать имя функции которая вызвала текущую функцию можно сделать так.
# myscript1.pyЗапускаем
import inspect
def function1():
# распечатаем имя вызывающей функции
print('Called from:', inspect.stack()[1][3])
def function2():
function1()
function2()
python3 myscript1.pyНо так мы увидим лишь имя предыдущей функции. А как узнать полный список вызовов?
Called from: function2
Traceback удобен тем, что показывает цепочку вызовов, которая привела к ошибке.
Но модуль traceback также позволяет распечатать стек вызовов не выбрасывая исключение.
# myscript2.pyЗапускаем файл
import traceback
def function1():
...
traceback.print_stack()
...
def function2():
function1()
function2()
python myscript2.py#tricks
File "myscript.py", line 9, in <module>
function2()
File "myscript.py", line 7, in function2
function1()
File "myscript.py", line 4, in function1
traceback.print_stack()
Какие ассоциации у вас вызывает число 404?
Сразу вспоминается ошибка 404 Not Found (не найдено).
Именно такой номер имеет PEP 404 Python 2.8 Un-release Schedule для несуществующего релиза Python 2.8.
В нём нам сообщают что релиз 2.8 никогда не выйдет и даются советы как перейти на ветку 3.х.
#pep #2to3
Сразу вспоминается ошибка 404 Not Found (не найдено).
Именно такой номер имеет PEP 404 Python 2.8 Un-release Schedule для несуществующего релиза Python 2.8.
В нём нам сообщают что релиз 2.8 никогда не выйдет и даются советы как перейти на ветку 3.х.
#pep #2to3
Роняем Python в одну строку
Как имитировать ошибку Segmentation Fault в коде и уронить процесс интерпретатора?
🔸 Форсированно завершаем процесс через kill и exit code 11.
🔸 Делаем что-то что вызовет ошибку со страшным сообщением что всё сломалось! Перегружаем стек вызова рекурсией, заведомо увеличив лимит до не приличия высоко.
Может кому-то хотите устроить подлянку 👹, а может тестируете дебагер.
Таким падением можно "указать" юзеру что он зашел куда не следует чтобы больше так не делал 😨
Всё это надуманные примеры. Но тем не менее, теперь вы теперь знаете как это сделать😉.
PS. Не советую использовать этот код в рабочих проектах!
#tricks
Как имитировать ошибку Segmentation Fault в коде и уронить процесс интерпретатора?
🔸 Форсированно завершаем процесс через kill и exit code 11.
__import__('os').kill(__import__('os').getpid(), 11)
Но это просто быстрый выход. 🔸 Делаем что-то что вызовет ошибку со страшным сообщением что всё сломалось! Перегружаем стек вызова рекурсией, заведомо увеличив лимит до не приличия высоко.
__import__('sys').setrecursionlimit(1<<30);f=lambda f:f(f);f(f)
🔸Ломаем парсер AST__import__('ast').literal_eval('1+1'*10**6)
Все эти вызовы приводят к такой ошибке:Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)Хм... а зачем это может быть нужно?
Может кому-то хотите устроить подлянку 👹, а может тестируете дебагер.
Таким падением можно "указать" юзеру что он зашел куда не следует чтобы больше так не делал 😨
Всё это надуманные примеры. Но тем не менее, теперь вы теперь знаете как это сделать😉.
PS. Не советую использовать этот код в рабочих проектах!
#tricks
Интересная библиотека tqdm, добавляющая прогресс бар для вашей итерации в CLI скриптах.
Что показывает прогресс бар?
- прогресс в процентах
- прогресс в количестве итераций
- потраченное и оставшееся время выполнения
- скорость выполнения в итерациях в секунду
Использовать очень просто, оберните итератор в tqdm и получите прогресс обработки!
#libs
Что показывает прогресс бар?
- прогресс в процентах
- прогресс в количестве итераций
- потраченное и оставшееся время выполнения
- скорость выполнения в итерациях в секунду
Использовать очень просто, оберните итератор в tqdm и получите прогресс обработки!
from tqdm import tqdmВсё сломается если внутри цикла есть какая-либо печать в stdout
for i in tqdm(iterable_obj):
# do something
#libs
Существуют фреймворки для создания десктоп приложений на базе WebGUI (HTML+CSS+JS).
Например Electron, NW.js и другие. Вся логика у них пишется на JavaScript.
Если хотите писать такие приложения с логикой на Python, то для вас есть аналог, это библиотека Eel.
🔸 Создавайте десктоп-GUI из HTML страниц со всем доступным для них функционалом.
🔸 Вызывайте Python-функции из JavaScript.
🔸 Вызывайте JavaScript функции из Python.
🔸 Есть встроенный сборщик приложения в Standalone через PyInstaller.
Eel запускает сервер и создаёт минималистичный chromium-браузер, который работает с этим сервером.
(Вы также можете открыть GUI вашего приложения и в обычном браузере, пока само приложение запущено)
Я за полчаса собрал нехитрое приложение, выполняющее Python-код введённый в HTML форме.
🌎 https://github.com/paulwinex/eel-webform-example
Для десктоп приложений идеально подойдет концепция SPA (Single Page App).
Мой элементарный пример на Vue.js
🌎 https://github.com/paulwinex/eel-vuejs-example
➡️ На почитать
#libs
Например Electron, NW.js и другие. Вся логика у них пишется на JavaScript.
Если хотите писать такие приложения с логикой на Python, то для вас есть аналог, это библиотека Eel.
🔸 Создавайте десктоп-GUI из HTML страниц со всем доступным для них функционалом.
🔸 Вызывайте Python-функции из JavaScript.
🔸 Вызывайте JavaScript функции из Python.
🔸 Есть встроенный сборщик приложения в Standalone через PyInstaller.
Eel запускает сервер и создаёт минималистичный chromium-браузер, который работает с этим сервером.
(Вы также можете открыть GUI вашего приложения и в обычном браузере, пока само приложение запущено)
Я за полчаса собрал нехитрое приложение, выполняющее Python-код введённый в HTML форме.
🌎 https://github.com/paulwinex/eel-webform-example
Для десктоп приложений идеально подойдет концепция SPA (Single Page App).
Мой элементарный пример на Vue.js
🌎 https://github.com/paulwinex/eel-vuejs-example
➡️ На почитать
#libs
Посмотрите на эту функцию
Похоже на пример из урока. Скорее всего будет SyntaxError из-за троеточия (...).
Но если вы выполните этот код... ОШИБКИ НЕ БУДЕТ 😳!
Почему❓
Всё дело в том что троеточие это одна из стандартных констант в Python, называется Ellipsis. Такая же как None, True или False.
Это не какой-то тип данных, для него нет каких-либо специальных операторов. Нигде в стандартной библиотеке он не используется.
Хотя, может в распечатке объектов с бесконечной рекурсией (но это не точно).
Прежде всего, стоит заметить что этот объект кажется достаточно неуместным для Python.
Ведь Python такой логичный и минималистичный и тут вдруг бесполезная константа которая нигде не встречается!
Но всё же давайте посмотрим как эта константа может быть использована.
🔸 Особый синтаксис срезов
В документации написано что Ellipsis используется для "extended slicing syntax".
Это значит что он используется в слайсинге, но не в стандартном. Его можно применить в расширенном слайсинге, то есть когда переопределяете метод
Ну и ничто не мешает нам использовать (...) в качестве "синтаксического сахара" для реализации какой-то особой логики.
Например так:
🔸 Аналог для pass
Сравните две записи
Но, несмотря на то, что Ellipsis был доступен еще с древних версий 2.Х, такая запись сработает только в Python3. Для 2го троеточие доступно только в квадратных скобках.
🔸 Заглушка для значений по умолчанию
Всё, больше идей пока нет)
На последок вот такая странная фиговина. Что покажет этот print() ???
#tricks
def my_func():
x = 1
y = 2
...
return x+y
Если я запущу её в таком виде, какую ошибку получу?Похоже на пример из урока. Скорее всего будет SyntaxError из-за троеточия (...).
Но если вы выполните этот код... ОШИБКИ НЕ БУДЕТ 😳!
Почему❓
Всё дело в том что троеточие это одна из стандартных констант в Python, называется Ellipsis. Такая же как None, True или False.
Это не какой-то тип данных, для него нет каких-либо специальных операторов. Нигде в стандартной библиотеке он не используется.
Хотя, может в распечатке объектов с бесконечной рекурсией (но это не точно).
>>> x = []
>>> x.append(x)
>>> print(x)
[[...]]
Так где же применять эту штуку?Прежде всего, стоит заметить что этот объект кажется достаточно неуместным для Python.
Ведь Python такой логичный и минималистичный и тут вдруг бесполезная константа которая нигде не встречается!
Но всё же давайте посмотрим как эта константа может быть использована.
🔸 Особый синтаксис срезов
В документации написано что Ellipsis используется для "extended slicing syntax".
Это значит что он используется в слайсинге, но не в стандартном. Его можно применить в расширенном слайсинге, то есть когда переопределяете метод
__getitem__ в ваших классах.>>> class C(object):
>>> def __getitem__(self, item):
>>> return item
Если вы попробуем получить нечто по индексу то в item прилетает этот индекс>>> C()[1]
1
Но если передать в квадратные скобки несколько аргументов, то в item прилетает кортеж>>> C()[1, 10]
(1, 10)
Вам остаётся решить как обработать такие аргументы и вернуть "срез" вашего типа.Ну и ничто не мешает нам использовать (...) в качестве "синтаксического сахара" для реализации какой-то особой логики.
>>> C()[1, ..., 10]
(1, Ellipsis, 10)
>>> C()[3:...]
slice(3, Ellipsis, None)
Вам остаётся лишь решить как именно обработать такой запрос.Например так:
>>> class C(object):
>>> def __getitem__(self, item):
>>> if item is Ellipsis:
>>> return "RETURN ALL"
>>> return "RETURN BY INDEX"
>>> C()[...]
RETURN ALL
Именно так сделано в numpy🔸 Аналог для pass
Сравните две записи
def some_func():
pass
def some_func():
...
По-моему, вторая запись более ясно говорит, что здесь что-то имеется в виду и это надо дописать 😉.Но, несмотря на то, что Ellipsis был доступен еще с древних версий 2.Х, такая запись сработает только в Python3. Для 2го троеточие доступно только в квадратных скобках.
🔸 Заглушка для значений по умолчанию
def func(x=...):
...
Альтернатива None? Совсем уж сомнительно 😖, но работает как визуальный триггер для разработчика)Всё, больше идей пока нет)
На последок вот такая странная фиговина. Что покажет этот print() ???
#tricks
Функция round() может округлять не только дробную часть но и целую. Для этого нужно использовать отрицательное значение.
>>> round(12345.6789, 2)#tricks
12345.68
>>> round(12345.6789, -2)
12300.0
Как создать новый тип объекта?
Очень просто! Cоздайте новый класс.
Но бывают случаи когда мы заранее не знаем какие атрибуты и методы будут у класса. То есть требуется динамическое создание класса во время исполнения кода.
Для чего?
Самое очевидное использование это Mock-объекты. Когда нам нужно подменить оригинальный объект заглушкой. Регулярно используется в авто тестах и в генераторах документации. Есть даже специальный класс для этого unittest.mock.Mock. Можем на лету создавать класс и настраивать его поведение.
Второй аргумент это список родительских классов.
Третий, это неймспейс объекта, то есть его методы и атрибуты.
В неймспейсе можно указывать как просто данные так и функции.
Однажды, для совместимости со старым кодом потребовалось немного изменить имеющийся класс. Был переименован метод и нужно было временно сделать заглушку. Писать отдельный класс не хотелось, а нужен был именно класс а не инстанс, к тому же не надолго. Вот тут и пригодился данный трик.
Был случай, когда я написал собственную реализацию запросов на сервер. Некий аналог requests, но очень простой, на чистых сокетах.
Это требовалось потому, что в исполняемой среде была версия Python, в которой никак не хотел работать оригинальный requests, да и слишком тяжёлый он был. При этом интерфейс ответа требовалось сохранить как в requests, но не всегда а по запросу, то есть динамически.
Пример из практики #2
Для некоторых, не совсем логичных, но всё же целей, требовалось воссоздавать типы, которые прилетели в виде JSON-данных.
Структура типа и данные инстанса были в этом JSON. Оставалось только пересобрать класс и создать инстанс. Некое подобие pickle, только немного иначе.
Но это уже достаточно сложные примеры для поста. Пожалуй, код опустим)))😊
#tricks
Очень просто! Cоздайте новый класс.
class MyType:
pass
Такой способ подходит для создания класса, статично записанного в скрипт.Но бывают случаи когда мы заранее не знаем какие атрибуты и методы будут у класса. То есть требуется динамическое создание класса во время исполнения кода.
Для чего?
Самое очевидное использование это Mock-объекты. Когда нам нужно подменить оригинальный объект заглушкой. Регулярно используется в авто тестах и в генераторах документации. Есть даже специальный класс для этого unittest.mock.Mock. Можем на лету создавать класс и настраивать его поведение.
>>> from unittest.mock import Mock
>>> dyn_obj = Mock()
>>> dyn_obj.return_value = 123
>>> print(dyn_obj())
123
Также можно создать инлайн тип с помощью функции type(). MyType = type("MyType", (object,),
{"func": lambda: 123,
"attr": 321})
Первый аргумент это имя нового объекта. Второй аргумент это список родительских классов.
Третий, это неймспейс объекта, то есть его методы и атрибуты.
В неймспейсе можно указывать как просто данные так и функции.
Однажды, для совместимости со старым кодом потребовалось немного изменить имеющийся класс. Был переименован метод и нужно было временно сделать заглушку. Писать отдельный класс не хотелось, а нужен был именно класс а не инстанс, к тому же не надолго. Вот тут и пригодился данный трик.
# старый класс
class OldClass:
def old_method(self):
return 123
# вариант с переопределением старого класса
class OldClass(OldClass):
def new_method(self):
return self.old_method()
# тоже самое но с динамически созданным типом непосредственно в том месте где он требуется
OldClass = type('NewClass', (OldClass,), {'new_method': lambda self: self.old_method()})
>>> obj = OldClass()
>>> print (obj.new_method(), obj.old_method())
(123, 123)
Пример из практики #1Был случай, когда я написал собственную реализацию запросов на сервер. Некий аналог requests, но очень простой, на чистых сокетах.
Это требовалось потому, что в исполняемой среде была версия Python, в которой никак не хотел работать оригинальный requests, да и слишком тяжёлый он был. При этом интерфейс ответа требовалось сохранить как в requests, но не всегда а по запросу, то есть динамически.
Пример из практики #2
Для некоторых, не совсем логичных, но всё же целей, требовалось воссоздавать типы, которые прилетели в виде JSON-данных.
Структура типа и данные инстанса были в этом JSON. Оставалось только пересобрать класс и создать инстанс. Некое подобие pickle, только немного иначе.
Но это уже достаточно сложные примеры для поста. Пожалуй, код опустим)))😊
#tricks
Типы файлов, которые может прочитать Python используя только стандартные библиотеки.
Сделал подборку модулей из стандартной библиотеки для чтения/записи различных типов файлов.
В данную подборку не вошли такие модули как pickle или zipapp, так как это форматы внутренней кухни Python.
Рассматриваем только внешние форматы.
———————————
Module: aifc
Type: aiff, aifc
Details: Audio Interchange File Format
Этот тип аудио файлов используется для записи звука в высоком качестве. Файл AIFC представляет собой сжатую версию формата AIFF.
Module: binhex
Type: hqx (BinHex4)
Details: Кодирование бинарных файлов в шестнадцатеричный текстовый формат и обратно (Binary-to-hexadecimal).
Module: bz2
Type: bzip2
Details: Архивация по алгоритму bzip2
Module: chunk
Type: aiff/aiff-c, rmff
Details: Audio Interchange File Format, Real Media File Format
Module: html
Type: html
Details: Утилиты для работы с форматом HTML
Module: json
Type: json
Details: Поддержка формата JSON.
Module: lzma
Type: xz, lzma
Details: Архивация данных с алгоритмом LZMA и поддержка старого формата XZ
Module: msilib
Type: msi
Details: Создание файлов типа Microsoft Installer, инсталляторы для Windows.
Module: sqlite3
Type: sqlite
Details: Интерфейс для работы с реляционной базой данных SQLite
Module: sunau
Type: au
Details: Интерфейс для взаимодействия с аудио файлами SunAU
Module: tarfile
Type: tar
Details: Чтение и запись файловых контейнеров в формате TAR, с поддержкой сжатия gzip, bz2 и lzma.
Module: wave
Type: wav
Details: Чтение и запись аудио файлов без сжатия формата WAV.
Module: xml
Type: xml
Details: Интерфейс для манипуляций с текстовым форматом XML.
Module: zipfile
Type: zip
Details: Чтение и запись архивов ZIP.
Module: zlib
Type: gz
Details: Чтение и запись архивов GZ с помощью библиотеки zlib
Module: csv
Type: csv
Details: Comma Separated Values, формат для записи табличных данных.
Module: configparser
Type: ini
Details: Чтение и запись формата конфигов Microsoft Windows INI file
Module: netrc
Type: netrc
Details: Парсинг файлов netrc для хранения данных аутентификации пользователей.
———————————
Надеюсь, кому-то данная информация поможет не изобретать велосипеды)))
Напоминаю, что в список попали только стандартные библиотеки которые идут в поставке с Python.
#libs
Сделал подборку модулей из стандартной библиотеки для чтения/записи различных типов файлов.
В данную подборку не вошли такие модули как pickle или zipapp, так как это форматы внутренней кухни Python.
Рассматриваем только внешние форматы.
———————————
Module: aifc
Type: aiff, aifc
Details: Audio Interchange File Format
Этот тип аудио файлов используется для записи звука в высоком качестве. Файл AIFC представляет собой сжатую версию формата AIFF.
Module: binhex
Type: hqx (BinHex4)
Details: Кодирование бинарных файлов в шестнадцатеричный текстовый формат и обратно (Binary-to-hexadecimal).
Module: bz2
Type: bzip2
Details: Архивация по алгоритму bzip2
Module: chunk
Type: aiff/aiff-c, rmff
Details: Audio Interchange File Format, Real Media File Format
Module: html
Type: html
Details: Утилиты для работы с форматом HTML
Module: json
Type: json
Details: Поддержка формата JSON.
Module: lzma
Type: xz, lzma
Details: Архивация данных с алгоритмом LZMA и поддержка старого формата XZ
Module: msilib
Type: msi
Details: Создание файлов типа Microsoft Installer, инсталляторы для Windows.
Module: sqlite3
Type: sqlite
Details: Интерфейс для работы с реляционной базой данных SQLite
Module: sunau
Type: au
Details: Интерфейс для взаимодействия с аудио файлами SunAU
Module: tarfile
Type: tar
Details: Чтение и запись файловых контейнеров в формате TAR, с поддержкой сжатия gzip, bz2 и lzma.
Module: wave
Type: wav
Details: Чтение и запись аудио файлов без сжатия формата WAV.
Module: xml
Type: xml
Details: Интерфейс для манипуляций с текстовым форматом XML.
Module: zipfile
Type: zip
Details: Чтение и запись архивов ZIP.
Module: zlib
Type: gz
Details: Чтение и запись архивов GZ с помощью библиотеки zlib
Module: csv
Type: csv
Details: Comma Separated Values, формат для записи табличных данных.
Module: configparser
Type: ini
Details: Чтение и запись формата конфигов Microsoft Windows INI file
Module: netrc
Type: netrc
Details: Парсинг файлов netrc для хранения данных аутентификации пользователей.
———————————
Надеюсь, кому-то данная информация поможет не изобретать велосипеды)))
Напоминаю, что в список попали только стандартные библиотеки которые идут в поставке с Python.
#libs
Функция sub в regex может принимать функцию в качестве аргумента repl.
📄 Из документации:
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single match object argument, and returns the replacement string.
То есть для каждого совпадения будет вызвана функция для вычисления замены вместо замены на одну и ту же строку для всех совпадений.
Иными словами, для замены разных совпадений на разные строки не потребуется запускать re.sub() много раз для каждой строки замены. Достаточно определить функцию, которая вернёт строку для каждого из совпадений.
Описание слишком запутанное🤔, давайте лучше рассмотрим на простом примере:
Создаем карту замены. То есть какие строки на какие требуется менять.
Используя данные этого объекта мы вычисляем замену on-the-fly!
(Данный паттерн ищет отдельные слова в тексте)
#libs #regex
📄 Из документации:
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single match object argument, and returns the replacement string.
То есть для каждого совпадения будет вызвана функция для вычисления замены вместо замены на одну и ту же строку для всех совпадений.
Иными словами, для замены разных совпадений на разные строки не потребуется запускать re.sub() много раз для каждой строки замены. Достаточно определить функцию, которая вернёт строку для каждого из совпадений.
Описание слишком запутанное🤔, давайте лучше рассмотрим на простом примере:
Создаем карту замены. То есть какие строки на какие требуется менять.
remap = {
'раз': '1',
'два': '2',
'три': '3',
'четыре': '4',
'пять': '5',
}
Пишем функцию поиска строки для замены. Единственным аргументом будет объект re.Match.Используя данные этого объекта мы вычисляем замену on-the-fly!
def get_str(match: re.Match):Пример текста.
word = match.group(1)
return remap.get(word.lower()) or word
text = '''Раз Два Три Четыре ПятьТеперь запускаем re.sub и вместо строки замены (repl) подаём имя функции.
Вместе будем мы считать
Пять Четыре Три Два Раз
Мы считать научим вас
'''
(Данный паттерн ищет отдельные слова в тексте)
>>> print(re.sub(r'(\w+)', get_str, text))Думаю, достаточно наглядно 🤓
1 2 3 4 5
Вместе будем мы считать
5 4 3 2 1
Мы считать научим вас
#libs #regex
Startup скрипт для REPL.
Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые
- автоматизировать одни и те же действия которые вы повторяете при старте REPL
- кастомизировать сам REPL
- объявление энвайромента или констант для ручного дебага или тестирования
итд...
В общем, как-то ускорить свою работу с REPL.
Как пример поведения, команда shell_plus в пакете django-extensions, которая перед запуском шела импортит всё самое необходимое.
Есть два способа это сделать
🔸 Флаг i
Запишите все действия в скрипт, например startup.py, и запускайте консоль такой командой:
🔸 Переменная окружения PYTHONSTARTUP
Еще до старта самого Python объявляем переменную PYTHONSTARTUP, в которую следует указать путь к скрипту. Сразу после запуска REPL но до первого приглашения к вводу команды ваш скрипт исполнится.
Всё это относится только к интерактивной консоли REPL!
Если вам нужен аналогичный функционал для обычного запуска приложения, то его нет. Так как это легко реализовать штатными средствами, добавив нужные импорты в начало вашего приложения.
#tricks
Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые
- автоматизировать одни и те же действия которые вы повторяете при старте REPL
- кастомизировать сам REPL
- объявление энвайромента или констант для ручного дебага или тестирования
итд...
В общем, как-то ускорить свою работу с REPL.
Как пример поведения, команда shell_plus в пакете django-extensions, которая перед запуском шела импортит всё самое необходимое.
Есть два способа это сделать
🔸 Флаг i
Запишите все действия в скрипт, например startup.py, и запускайте консоль такой командой:
python -i startup.py
Флаг -i означает, что после выполнения скрипта инетрпретатор не завершит процесс а перейдёт в интерактивный режим, то есть обычный RELP.🔸 Переменная окружения PYTHONSTARTUP
Еще до старта самого Python объявляем переменную PYTHONSTARTUP, в которую следует указать путь к скрипту. Сразу после запуска REPL но до первого приглашения к вводу команды ваш скрипт исполнится.
Windows
set PYTHONSTARTUP=C:\path\to\startup.py
Linux
export PYTHONSTARTUP=/path/to/startup.py
Теперь запуск REPL будет исполнять указанный файлВсё это относится только к интерактивной консоли REPL!
Если вам нужен аналогичный функционал для обычного запуска приложения, то его нет. Так как это легко реализовать штатными средствами, добавив нужные импорты в начало вашего приложения.
#tricks
Выполнение функции перед выходом.
Как вызвать функцию перед выходом из программы в Python?
Для этого нужно использовать модуль atexit. Он регистрирует функции, которые выполняются перед завершением процесса интерпретатора.
🔸 Этот функционал работает и в обычном режиме без интерактивной консоли.
#tricks #libs
Как вызвать функцию перед выходом из программы в Python?
Для этого нужно использовать модуль atexit. Он регистрирует функции, которые выполняются перед завершением процесса интерпретатора.
import atexitТеперь попробуйте завершить процесс и увидите сообщение
def before_exit():
print('BEFORE EXIT')
atexit.register(before_exit)
>>> exit()🔸 Для регистрации такой функции в REPL пригодится startup скрипт.
BEFORE EXIT
🔸 Этот функционал работает и в обычном режиме без интерактивной консоли.
#tricks #libs
Стартап-скрипт это удобное место для изменения внешнего вида REPL. Например замена символов строки приглашения или добавление автокомплитов по TAB.
Давайте заменим символы строки приглашения. Для этого нужно поменять переменные sys.ps1 и sys.ps2. Символами может быть даже эмодзи.
Напишите это в стартап-скрипте:
___________
Еще пример python-startup скрипта с просторов гитхаба.
И пример от меня, рандомный смайл на каждой строке 😂
#tricks #libs
Давайте заменим символы строки приглашения. Для этого нужно поменять переменные sys.ps1 и sys.ps2. Символами может быть даже эмодзи.
Напишите это в стартап-скрипте:
import sys
sys.ps1 = '⏩ '
sys.ps2 = '⏳ '
А как насчет динамически изменяемой строки? Это тоже можно. Создаём класс, наследованный от строки и определяем что он возвращает при печати.import sys
class PS1(object):
def __init__(self):
self.s = True
def __str__(self):
self.s = not self.s
return '\033[97m█◣ \033[0m ' if self.s else '\033[34m█◤ \033[0m '
sys.ps1 = PS1()
sys.ps2 = '▼ '
Попробуйте поработать с этим)___________
Еще пример python-startup скрипта с просторов гитхаба.
И пример от меня, рандомный смайл на каждой строке 😂
#tricks #libs