Python Заметки
2.22K subscribers
62 photos
2 videos
2 files
229 links
Интересные заметки и обучающие материалы по Python

Контакт: @paulwinex

⚠️ Рекламу на канале не делаю!⚠️

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
В Python 3.14 появится реализация PEP 750 и новый способ форматирования: t-strings. Это так называемые Template Strings.

Синтаксис такой же как с f-strings, но форматирование происходит не сразу.
Вместо строки создаётся объект Template, который внутри себя содержит исходную информацию, сырую строку (template.strings) и переменные (template.values).
Это позволяет произвести дополнительную обработку данных перед форматированием, например для усиления безопасности.
В примерах можно увидеть как строка с HTML кодом дополнтиельно обрабатывается чтобы избежать инъекции JS кода за счет экранирования служебных символов.

Конечно, этим примером возможности не ограничивюатся. Более подробно про функционал будет понятно ближе к релизу в конце года. Сейчас доступно в сборках 3.14.0a7+ из этой ветки.

Простой пример создания шаблона

name = "World"
template = t"Hello {name}!"


Что является шорткатом для
from string.templatelib import Template, Interpolation

template = Template(
"Hello ",
Interpolation(value="World", expression="name"),
"!"
)


В обоих случаях объект получим идентичный
print(isinstance(template, Template))
# True
print(template.strings)
# ("Hello ", "!")
print(template.values)
#(name,)


Больше примеров ➡️ здесь

#pep
👍8🤔4👎2🔥1😢1
Недавно возникла такая задача: требовалось из Python скрипта запустить дочерний процесс, тоже Python скрипт, и получить от него некоторые данные. В моём случае это был некий словарь который мог быть сериализован в JSON формат, но это не так важно.

Какие есть варианты это сделать?

1️⃣ Передать дочернему процессу путь к файлу куда и будет записан результат.
После завершение дочернего процесса просто читаем данные из файла.

легко и понятно, все так умеют делать
можно перемещаться по файлу через seek
можно прочитать когда-нибудь потом
обращение к файловой системе, бывает относительно не быстро
какое-то время файл будет доступен любому процессу, небезопасно
только полная запись данных перед чтением (на самом деле есть вариант чтения во время записи, но это не то что мы хотим делать😖)


2️⃣ TCP/UDP сокет

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


3️⃣ Парсить аутпут дочернего процесса.

быстро, так как пайпы работают через оперативную память
нет обращения к файловой системе и всех действий с этим связанных
пайп привязан к файловым дескрипторам конкретных процессов, и доступ к нему могут получить только те процессы, которые унаследовали этот дескриптор (или получили другим способом)
передача данных в режиме стрима
неудобно если дочерний процесс пишет логи в stdout, нужна какая-то логика выделения только нужного или как-то отключать логи в надежде что никто другой туда ничего не напишет.
нельзя перемещаться через seek

Если у вас взаимодействие с дочерним процессом, то есть самый простой вариант - кастомный пайп!

Это как stdout или stderr, но только еще один канал в котором не будет никаких логов и сообщений об ошибках.
Для простоты примера сделаем один пайп. Дочерний процесс должен что-то прислать в родительский процесс.

👮‍♂️РОДИТЕЛЬСКИЙ ПРОЦЕСС

1. Создаем новый пайп
import os. subprocess

read_fd, write_fd = os.pipe()
# важный момент! добавляем возможность наследовать дескриптор дочерним процессом. Обязательно после Python 3.4+ (PEP 446)
os.set_inheritable(write_fd, True)


2. Запускаем дочерний процесс передавая ему номер файла
process = subprocess.Popen(
[sys.executable, child_script, str(write_fd)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
close_fds=False # важный момент! это нужно, чтобы дочерний процесс сохранил все открытые дескрипторы, а не только стандартные потоки
)
os.close(write_fd) # закрываем дескриптор чтобы у родителя не висел открытый конец записи, иначе в читающем конце не наступит EOF


3. Читаем данные
with os.fdopen(read_fd, 'r') as data_pipe:
data = data_pipe.read()
print('RECEIVED:', data)

Чтение прекратится когда файл закроется, за это отвечает контекстный менеджер with в дочернем процессе.

Стандартные пайпы тоже можно прочитать
stdout_log, stderr_log = process.communicate()
print(stdout_log)
print(stderr_log)


👶 Переходим к коду дочернего процесса.

1. Получаем номер дескриптора
write_pipe_fd = int(sys.argv[-1])


Пишем в него данные
with os.fdopen(write_pipe_fd, 'w') as data_pipe:
data_pipe.write('Hello!')
data_pipe.flush()


Вот и всё, мы сделали коммуникацию между двумя процессами через кастомный пайп ⭐️
Быстро, легко, безопасно!

С помощью двух пайпов можно ораганизовать передачу сообщений между процессами в обе стороны.

Пример с JSON можно глянуть здесь↗️

#tricks
🔥12👍42
Быстрый встроенный профайлинг на Linux с помощью time

time python -c 'for i in range(10**7): i**2'


Покажет время выполнения процесса
real    0m2,470s
user 0m2,405s
sys 0m0,074s

real - Общее время, прошедшее с момента запуска до завершения программы. Включая время ожидания I\O или переключения контекста.
user - Количество времени, которое CPU потратил на выполнение кода самой программы в пользовательском режиме.
sys - Количество времени, которое CPU потратил на выполнение системных вызовов (операций ядра, таких как чтение/запись файлов, управление памятью) от имени программы.

Но это встроенная команда из моей оболочки. Есть такая же GNU-утилита и она может показывать больше информации. Но нужно вызывать по абсолютному пути, так как builtin команда имеет бОльший приоритет.

/usr/bin/time -v python -c 'for i in range(10**7): i**2'

Command being timed: "python -c for i in range(10**7): i**2"
User time (seconds): 2.38
System time (seconds): 0.07
Percent of CPU this job got: 100%
...

Кроме времени исполнения будет также показано много другой полезной информации
- эффективность использования CPU (в %)
- максимальный объем занятой памяти
- обращения к файлам
- код выхода

И другие сведения.

#tricks
🔥82👍2
7.09.2025 состоялся релиз Pithon 3.14!

На фоне хайпа про NoGIL всё позабыли про другие фичи. Особенно про Multiple Interpreters, который обещает изоляцию процессов но с эффективностью потоков! На сколько действительно это будет эффективно мы узнаем позже, потому что сейчас это лишь первый релиз с ограничениями и недоработками.

Но что там про NoGIL? Теперь этот режим не экспериментальный, а официально поддерживаемый, но опциональный.
Чтобы запустить без GIL нужна специальная сборка. И перед стартом нужно объявить переменную PYTHON_GIL=0

Для вас я собрал готовый репозиторий где достаточно запустить скрпит, который всё сделает:
▫️ соберет релизный Python 3.14 в новый Docker-образ
▫️ запустит тесты в контейнере (GIL, NoGIL, MultiInterpreter)
▫️ распечатает результаты

Тест очень простой, усложняйте сами)
Вот какие результаты у меня:
=== Running ThreadPoolExecutor GIL ON
TOTAL TIME: 45.48 seconds
=== Running ThreadPoolExecutor GIL OFF
TOTAL TIME: 6.14 seconds
=== Running basic Thread GIL ON
TOTAL TIME: 45.54 seconds
=== Running basic Thread GIL OFF
TOTAL TIME: 4.74 seconds
=== Running with Multi Interpreter
TOTAL TIME: 18.30 seconds


Если сравнивать GIL и NoGIL, то на мои 32 ядра прирост х7-x10 (почему не х32? 🤷). При этом нам обещают что скорости будут расти с новыми релизами.
Режим без GIL похож (визуально) на async, тоже параллельно, тоже не по порядку. Но это не IO! и от того некоторый диссонанс в голове 😵‍💫, нас учили не так!

Интересно, что чистый Thread работает быстрей чем ThreadPoolExecutor без GIL.

Ну и где-то плачет один адепт мульти-интерпретаторов😭 Теперь нужно искать где они могут пригодиться с такой-то скоростью. Скорее всего своя область применения найдется.

Отдельно я затестил память и вот что вышло на 32 потока:
ThreadPoolExecutor GIL ON
305.228 MB
ThreadPoolExecutor GIL OFF
500.176 MB
basic Thread GIL ON
90.668 MB
basic Thread GIL OFF
472.444 MB
with Multi Interpreter
1267.788 MB

Пока не знаю как к этому относиться)

В целом - радует направление развития!

#release
12👍4🔥2👎1
Использование Pydantic сегодня стало нормой, и это правильно. Но иногда на ревью вижу, что используют его не всегда корректно.
Например, метод BaseModel.model_dump() по умолчанию не преобразует стандартные типы, такие как datetime, UUID или Decimal, в простой сериализуемый для JSON вид. Тогда пишут кастмоный сериализатор для этих типов чтобы функция json.dump() не падала с ошибкой.

import uuid
from datetime import datetime
from decimal import Decimal
from uuid import UUID
from pydantic import BaseModel

class MyModel(BaseModel):
id: UUID
date: datetime
value: Decimal


obj = MyModel(
id=uuid.uuid4(),
date=datetime.now(),
value='1.23'
)
print(obj.model_dump())
# не подходит для json.dump
# {
# 'id': UUID('4f8c1bc4-25fd-40cd-9dbe-2c73639b0dc1'),
# 'date': datetime.datetime(2025, 12, 12, 12, 12, 12, 111111),
# 'value': Decimal('1.23')
# }
# добавляем свой кастомный сериализатор
json.dumps(obj.model_dump(), cls=MySerializer)
# {
# 'id': '4f8c1bc4-25fd-40cd-9dbe-2c73639b0dc1',
# 'date': '2025-12-12T12:12:12.111111',
# 'value': '1.23'
# }


В данном случае класс MySerializer обрабатывает datetime, UUID и Decimal. Например так:
class MySerializer(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return str(o)
elif isinstance(o, datetime):
return o.isoformat()
elif isinstance(o, UUID):
return str(o)
return super().default(o)


Специально для тех, кто всё еще так делает - в этом нет необходимости!
Pydantic может это сделать сам, просто нужно добавить параметр mode="json".
json.dumps(obj.model_dump(mode="json"))
# {
# 'id': '4f8c1bc4-25fd-40cd-9dbe-2c73639b0dc1',
# 'date': '2012-12-12T12:12:12.111111',
# 'value': '1.23'
# }


#pydantic #libs
9👍7🔥5
Всех 3Dшников спраздником!!!🔥💫💥
14🎉5👍1
Три способа выполнить множество задач с asyncio

Функция для примера:
async def do_it(n):
await asyncio.sleep(random.uniform(0.5, 1))
return n


1. Последовательный вызов
async def main():
for i in range(100):
result = await do_it(i)

Такой вызов имеет смысл только тогда, когда результат одной задачи требуется для вызова следующей.
Если они независимы, то это антипаттерн, так как аналогичен простому синхронному вызову по очереди.

2. Упорядоченный результат
async def main():
tasks = [do_it(i) for i in range(100)]
results = await asyncio.gather(*tasks)

Выполняет корутины конкурентно и возвращает результат в виде списка.
Полезен когда требуется получить результаты в том же порядке в котором задачи отправлены.

3. Результат по мере готовности
tasks = [asyncio.create_task(do_it(i)) for i in range(100)]
for cor in asyncio.as_completed(tasks):
result = await cor

Так же выполняет корутины конкурентно, но не гарантирует порядок. Результат возвращается по мере готовности, каждый отдельно.
Полезен когда нужно обработать любой ответ как можно скорее.

#async
8👍6
Функция asyncio.wait() это еще один способ вызвать множество асинхронных задач.
Она работает в нескольких режимах.

1. Самый простой - ждем завершения всех задач
async def main():
tasks = [asyncio.create_task(do_it(i)) for i in range(10)]
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.ALL_COMPLETED
)
for task in done:
try:
print(task.result())
except Exception as e:
print(e)

Очень похоже на gather, но работает не так.
▫️возвращает не результаты, а два сета с объектами Task у которых можно забрать результат через task.result() если они в списке done
▫️не гарантирует порядок результатов так как оба объекта это set
▫️не выбрасывает исключение когда оно появляется, а сохраняет его в Task. Исключение появится когда попробуете забрать резултьтат.

2. Ждем завершения первой задачи, даже если там ошибка.
async def main():
tasks = [asyncio.create_task(do_it(i)) for i in range(3)]
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED
)
# в done может быть несколько задач!
for task in done:
try:
print(task.result())
except Exception as e:
print(f"Fail: {e}")
# Оставшиеся задачи в pending, как правило, нужно отменить, иначе они будут продолжать работать
for task in pending:
task.cancel()

В сете done будут таски которые успели завершится, причем как успешно так и нет.

3. До первой ошибки.
Тоже самое, но с аргументом FIRST_EXCEPTION
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_EXCEPTION
)

Функция завершается как только первая задача упадет с ошибкой.

Учтите, что в любом случае done вы можете обранужить несколько задач, как с ошибками так и успешные.

↗️ Полный листинг примеров здесь

#async
👍72
Отдельно разберём TaskGroup, который пришел на замену gather в Python 3.11.

Ключевые отличия
▫️create_task() возвращает объект asyncio.Task, у которого есть соответствюущие методы управления. То есть у нас больше контроля
▫️это контекстный менеджер, который гарантирует что все таски будут остановлены по выходу из контекста
▫️ошибка автоматически отменяет незавершенные задачи,
▫️except* передает нам ExceptionGroup, в котором каждую ошибку можно обработать отдельно
import asyncio
import random

async def do_it() -> str:
if random.random() < 0.1:
raise ValueError('Oops')
delay = random.uniform(0.5, 1.5)
await asyncio.sleep(delay)
return delay

async def main():
try:
async with asyncio.TaskGroup() as tg:
for _ in range(10):
tasks.append(tg.create_task(do_it()))
for t in tasks:
print(t.result())
except *ValueError as e:
for err in e.exceptions:
print(err)

asyncio.run(main())


Рекомендую изучить страницу Coroutines and Tasks из документации, где представлено больше интересных примеров и механизмов
- таймауты
- отмена задач
- создание задач из другого потока

#async
4👍1
С Новым Годом! 🎄☃️❄️

Снова этот рубеж подведения итогов и определения планов на следующее 365 дней.

Что же мы успели застать в 2к25?

🔸 AI снова делает скачёк в развитии, как по качеству, так и по затратам на ресурсы
Продолжая расшатывать все рынки
🔸 Эпичный прорыв цен на железо (из-за первого факта). Сначала оперативка, потом и остальные подтянулись.
Успели закупиться вовремя?
🔸 Новый виток "борьбы с интернетом" в РФ
Работать всё сложней
🔸 Опенсорсный проект MinIO закрылся
Теперь только в облаке и только за денежку

Но не всё так плохо!

🔸 Всё больше уверенных мнений, что AI нас не заменит
Но всем нужно адаптироваться к новым реалиям и инструментам
🔸 uv ворвался в прод
Так и стандартом станет скоро
🔸 Вышел Django 6
Достаточно ли изменений для мажорной версии?
🔸 Вышел PIthon 3.14 с NO-GIL режимом
Раньше это считалось невозможным!
🔸 В том же 3.14 мы получили полноценные Субинтерпретаторы и JIT
И другие оптимизации
🔸 Язык Rust теперь официально второй язык ядра Linux
Хоть и не без проблем
🔸 Проекту pythonotes 6 лет 🎂
Скоро в школу)

Мир вокруг меняется постоянно и всё с большей скоростью. Не ищите виновных, просто адаптируйтесь и постоянно учитесь. И всё будет пучком! 😎

Оглядываясь назад в прошлое, задумайтесь, можете ли вы сказать тому себе из прошлого СПАСИБО за то, что вы имеете в настоящем?
И хорошенько подумайте в этом настоящем, что нужно делать уже сейчас, чтобы вы из будущего стали лучшей версией себя настоящего и гордились своей версией из прошлого за заботу о будущем.

Sir Christopher Edward Nolan :)


#offtop
🎉107
В работе с медиа файлами часто требуется определить не просто расширение, а его, скажем так, "категорию". Тоесть определить это видео, аудио или картинка. Примерно в 10 случаях из 10 в ревью я вижу обычный хардкодинг с большим мапингом и соответствующим поиском по нему.

file_type_by_ext = {
'video': ['.mp4', '.mov', '.mkv', ...],
'audio': ['.mp3', '.wav', '.ogg', ...],
'image': ['.jpg', '.png', '.exr', ...]
}


Для таких случаев есть простой способ - стандартная библиотека mimetypes.

import mimetypes
mimetypes.guess_type("example.txt")
# ('text/plain', None)

Причём ей не нужен файл, достаточно просто имени строкой.

Первый элемент кортежа это MIME-тип (Multipurpose Internet Mail Extensions Type) - стандартный способ идентификации формата файла.

Формат: type/subtype

type - общая категория данных (text, video, image)
subtype - конкретный формат внутри категории

mimetypes.guess_type("photo.jpg")
# ('image/jpeg', None)
mimetypes.guess_type("render.mp4")
# ('video/mp4', None)


Второй элемент это тип кодировки содержимого, обычно для контейнеров типа gz и аналогичных.
mimetypes.guess_type("file.tar.gz")
# ('application/x-tar', 'gzip')
mimetypes.guess_type("backup.tar.bz2")
# ('application/x-tar', 'bzip2')


Итого, узнать категорию файла одной строкой:
mimetypes.guess_type('myfile.mov')[0].split('/')[0]
# video

Конечно при условии, что тип будет распознан, иначе будет None а не строка. Но об этом в следующий раз.

#libs #tricks
👍20
import mimetypes
mimetypes.guess_type("example.fbx")
# (None, None)

Формат не распознан, так как не зарегистрирован в системе.
Регистрация происходит с помощью функции mimetypes.init(). Эта функция автоматически вызывается при первом обращении.
Для каждой OS работает по-разному. В Windows читает реестр, в Linux достает всё из файла /etc/mime.types, в MacOS читает из системной БД.

На linux можно попробовать распознать тип через вызов
file --mime-type -b <filename>

эта команда попробует прочитать метадату самого файла, то есть должен быть доступ к файлу. Но это не гарантия успеха.

Можно попробовать использовать нестрогое соответствие IANA с помощью флага strict=False. Тогда будут учтены старые и нестандартные типы. Обычно они с префиксом x-

Новые типы можно добавлять самостоятельно.
mimetypes.add_type('application/x-fbx', '.fbx') # с точкой
mimetypes.guess_type("example.fbx")
# ('application/x-fbx', None)


Либо вызвать init() еще раз передав список текстовых файлов с нужными вам типами (без точки)
# my-mime-types.txt
application/x-fbx fbx
application/x-ogo ogo
application/x-aga aga

mimetypes.init(['my-mime-types.txt'])
mimetypes.guess_type("example.ogo")
# ('application/x-ogo', None)


Есть и обратная операция - получить расширение файла из mime-типа
mimetypes.guess_extension('image/jpeg')
# .jpg

Или все подходящие расширения
mimetypes.guess_all_extensions('image/jpeg')
# ['.jpg', '.jpe', '.jpeg', '.jfif']


Советую почитать полную документацию
Также обратите внимание на библиотеку content-types для работы с mime-типами, где больше возможностей.

#libs #tricks
🔥4
Все знают синтаксический сахар с операторами +=, -= и тд
x += 1

Где под капотом он превращается в
x = x + 1

Останется ли переменная х той же переменной после +=?
Конечно нет, это же неизменяемый тип
x = 1
print(id(x))
# 135373664533280
x += 1
print(id(x))
# 135373664533312


Теперь провернём тоже самое со списком
ls = [1, 2]
print(id(ls))
# 135373622585344
ls = ls + [3]
print(id(ls))
# 135373619036608

Ожидаемо работает так же, ведь мы создали новую переменную.
А теперь попробуем иначе:
ls = [1, 2]
print(id(ls))
# 135373622585344
ls += [3]
print(id(ls))
# 135373622585344
print(ls)
# [1, 2, 3]

И, внезапно, это работает не так как с int, со списками оператор += работает как extend()!
То же самое будет с *=, объект останется тем же.
ls = [1, 2]
print(id(ls))
# 135373622585344
ls *= 2
print(id(ls))
# 135373622585344
print(ls)
# [1, 2, 1, 2]

Следует помнить о такой важной разнице!
(Особенно на собесах 😉)

#tricks
👍141
Не запуская код определите, что покажет терминал если выполнить следующее:

_A__b = 'c'
class A:
def get(self):
return __b
print(A().get())


Ответ: Несмотря на то, что ваш IDE покажет ошибку, ошибки не будет. Распечатается "c"

Объяснение:

1. Mangling
За это отвечает механизм mangling - искажение имени. Так работают приватные атрибуты классов.
При создании атрибута по правилу: минимум 2 "_" в начале и максимум 1 "_" в конце" имя автоматически становится вида _{classname}{attr}
В нашем случае атрибутов класса не создается, но это не отменяет Mangling при обращении к другим объектам внутри класса.

2. Обращение к атрибуту
Когда внутри класса происходит обращение к любому объекту с именем по указанному выше правилу, его имя на уровне байт кода также преобразуется.

3. Поиск
Далее происходит поиск такой переменной по неймспейсам в порядке LEGB - Local, Enclosing, Global, Built-in.
И не трудно догадаться что мы находим нужный атрибут в Global, В итоге получаем результат!

Проверить можно так:
import dis
dis.dis(A.get)
# 4 RESUME 0
#
# 5 LOAD_GLOBAL 0 (_A__b)
# RETURN_VALUE

Либо удалите переменную _A__b и запустите еще раз, поулчите ошибку:
NameError: name '_A__b' is not defined


Как думаете, это норма или баг?

#tricks
5😢3
В Gunicorn добавили ASGI. Пока что beta.

gunicorn myapp:app --worker-class asgi


То есть теперь вместо "gunicorn воркеры + uvicorn ранеры" будет всё из одной библиотеки? Коненчо, если скорость не будет драматически ниже.

#libs
🔥3
Потоковая обработка часто встречается при работе с большими файлами или когда данные приходят частями. В Python есть множество инструментов для работы с такими данными. Самый известный - итератор файла по строкам. В веб-приложениях это стандарт для передачи файлов. Далее приведу несколько примеров.

Чтение файлов
with open('huge-file.txt') as file:
for line in file:
process_line(line)

Это позволяет нам читать текстовый файл по строкам не загружая всё в память.
Конечно, если позволяет формат данных. С JSON такое не сработает (ijson может в этом помочь).

Запись файла чанками
with open('file-to-save.txt',
'w') as file:
for line in iter_data():
file.write(line)


Частные случаи есть в разных библиотеках. Например DictWriter и DictReader из модуля csv позволяет работать с конкретным форматом данных а не просто текст.
import csv

with open('data.csv', 'r') as file:
reader = csv.DictReader(file)
for row in reader:
print(row)

with open('data.csv', 'a',
newline='') as f:
writer = csv.DictWriter(f,
fieldnames=['col1', 'col2']
)
for row in iter_objects():
writer.writerow(row)


Отдельно интересен ZipFile, позволяющий "открыть" файл сразу внутри архива и записывать его частями
import zipfile as zf

with zf.ZipFile(
'archive.zip',
'w',
compression=zf.ZIP_DEFLATED) as zf:
with zf.open(
'large_data.bin',
mode='w') as in_file:
with open(
'large_data.bin',
'rb') as source:
for chunk in iter(
lambda: source.read(1024),
b''):
in_file.write(chunk)


Создание хеша для большого файла
import hashlib

sha256 = hashlib.sha256()
with open(
'large-file.bin',
'rb') as f:
for block in iter(
lambda: f.read(1024), b''
):
sha256.update(block)
hash_sum = sha256.hexdigest()


Сжатие данных в файл отдельными чанками
import gzip

with gzip.open('data.gz', 'wb') as f:
for bin_chunk in iter_bin_data():
f.write(bin_chunk)


Чтение с записью в файл
with gzip.open('data.gz', 'rb') as f_in:
with open(
'extracted_data.txt',
'wb') as f_out:
for chunk in iter(
lambda: f_in.read(1024),
b''):
f_out.write(chunk)


Подсчет объектов из стрима. Добавление обновляет счетчики.
from collections import Counter

c = Counter()
for data in iter_objects():
c.update(data)


Это не все доступные примеры, их еще много. Каждый из них позволяет обрабатывать данные из потока не ожидая весь набор и не загружая их в оперативку.
Это очень полезная техника, которую я призываю использовать по назначению!

#tricks #libs
👍12
reload_flag=""
if [[ -n "${DEBUG}" ]]; then
reload_flag="--reload"
fi

if [[ -n "${WORKER_COUNT}" ]]; then
workers=${WORKER_COUNT}
else
workers=2
fi

gunicorn --workers ${workers} \
--bind 0.0.0.0:8000 \
${reload_flag} main.wsgi

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

:- для установки значений по умолчанию
${WORKER_COUNT:-2}

Если переменная не объявлена, то будет дефолтное значение 2.

:+ подставляет указанный текст, если переменная не пуста
${DEBUG:+--reload}

Если что-то есть в переменной то распечатается текст после символа +, в противном случае - ничего. Удобно для опциональных флагов, как в нашем примере.

Итого наш скрипт может выглядеть так:
gunicorn --workers ${WORKER_COUNT:-2} \
--bind 0.0.0.0:8000 \
${DEBUG:+--reload} main.wsgi


Есть еще два оператора.

:= не только подставить дефолтное значение, но и присвоить его переменной, если она пуста
# никаких переменных еще нет
VAL1=${VAL2:=hello}
# теперь доступны обе
echo $VAL1 $VAL2
# hello hello


:? остановить выполнение с ошибкой, если переменной нет.
echo ${MISS:?is required}
bash: MISS: is required

Код выхода будет 1.

#tricks #linux
👍5
Windows 11 становится всё менее дружелюбна к юзерам а порой и вовсе не юзабельной:

▫️ постоянные ломающие апдейты которые не дают загрузить систему, откатить ломающие апдейты, и давно уже удаляют файлы пользователей без спроса. Тенденция в целом уже достаточно давно, включая глобальные сбои и другие неприятности.

▫️ навязчивое продвижение AI шпионов агентов повсюду в системе которых никто не просил.

▫️ всё больше ресурсов ВАШЕГО компа работают не для вас, а в угоду Microsoft. Мелкомягкие официально предлагают купить железо помощней (чтобы они и дальше могли половину мощности использовать по своему усмотрению) а оно что-то не покупается. Рядовой юзер не понимает зачем менять комп который и так норм работает. А глядя на текущие цены на память наступает ощущение что с этим миром что-то не так.

▫️ люди булшитят винду и активно продвигают переход на Linux порой называя винду кучей слопа или даже вирусом, похищающим файлы с целью выкупа (они реально после аплоада и удаления с локала отключают доступ к файлам и требуют купить подписку). А сам Microsoft переименован в Microslop. Появляются даже тулзы для очистки системы от этого слопа.

▫️ Microsoft уже не скрывает, что ваши данные уже не ваши, даже зашифрованные, ибо ваши пароли давно уже хранятся где надо и доступны кому надо.

▫️ При всех этих факапах они закрыли поддержку Windows 10 не давая возможности откатиться на что-то более стабильное.

То есть сами Microsoft стали катализатором поиска альтернатив.
Сам я уже на Linux уже более 7 лет как на основной системе, дома винда есть только в виртуалке для тестов клиентского софта. Расскажите, как у вас обстоят дела на винде? Вы пользуетесь системой или боретесь с ней?

#offtop
👍4
А что происходит на противоположном фронте?
Вы, вероятно, слышали, что 2026 год называют годом Linux на десктопе (в каких-то узких кругах - годом гейминга на Linux). Всё потому, что экосистема Linux постепенно становится более дружелюбной для обычных десктоп-юзеров (в том числе привыкших к Windows), и не только!

▫️ всё чаще появляются Linux дистрибутивы визуально похожие на Windows (или даже лучше), и множество видео с советами какой дистрибутив попробовать новичкам.

▫️ обновления ядра и любых пакетов в экосистеме Linux всегда привносят оптимизацию и удобство и поддержку свежего железа (привет винде с её обратной тенденцией). Например грядущая версия 7.0, опять с множеством приятных мелочей.

▫️ после 10 лет с последего релиза версии 5 окружение KDE Plasma получила мажорный апдейт версии 6 и активно развивается (уже доросла до 6.5). GNOME тоже не спит и готовит версию 50.

▫️ в Wine добавили патч позволяющий устанавливать продукты Adobe на Linux. Для кого-то это был последний рубеж?😏

▫️ Proton активно развивается, да так, что через эту прослойку игры работают даже быстрей чем нативно на винде.

▫️ с каждым релизом Wine и Proton поддерживается всё больше игр, что можно отслеживать на ProtonDB, и даже случаются бусты производительности.

▫️ Я сам на днях на виндобук поставил ChacyOS после чего игры, которые тянули гдето в 5-10 FPS, стали играбельными! Подтверждено личным опытом! Кстати, есть несколько дистрибутивов заточенные именно под игры.

▫️ Valve выпускают новую пачку железок которые (предположительно) порвут рынок гейминга (как и в прошлый раз) и (определнно точно) работают на Linux. Именно Valve вливает ресурсы в Linux в целом и в Proton в частности.

▫️ Госсектор разных стран давно уже мигрирует на opensource, так как нет доверия системе которая может одномоментно неконтролируемо массово рухнуть или быть удаленно заблокированной (в том числе по политическим причинам).

И ниже небольшой опрос - какая у вас операционка основная?

Ни к чему не призываю, ничего не советую! Просто подмечаю тенденцию и хочется узнать мнения из первых рук 😉

Знаю, что Linux тоже не идеален, знаю что каждой задаче - свой инструмент. Но это не тема поста, так что можно без холиваров)


#offtop #linux