Если ты работаешь с данными и устал от тормозов в Pandas, попробуй Polars. Это новая библиотека для анализа данных, написанная на Rust, но с Python-интерфейсом. Быстрее, безопаснее, удобнее — особенно для больших наборов данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3👎1
Многие думают, что в Python не нужно заботиться о памяти — мол, всё делает сборщик мусора. Но это не совсем так. Понимание механизма управления памятью поможет писать эффективный и надёжный код.
В статье ты узнаешь:
__slots__ и как они помогают экономить памятьPlease open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1
__call__ и __getitem__ — красиво, читаемо, PythonicХочешь, чтобы твой код выглядел как язык сам по себе?
Python позволяет строить мини-языки внутри кода — без библиотек, просто на магии dunder-методов.
__call__Он делает объект вызываемым, как функцию:
class Hello:
def __call__(self, name):
return f"Привет, {name}!"
h = Hello()
print(h("Мир")) # Привет, Мир!
Полезно, когда хочется сделать объекты лаконичными. Можно даже прокидывать параметры как в обычной функции.
__getitem__Позволяет обращаться к объекту через квадратные скобки:
class SecretBox:
def __getitem__(self, key):
return f"🔒 Внутри: {key}"
box = SecretBox()
print(box["password"]) # 🔒 Внутри: password
Ты можешь использовать это для красивого синтаксиса без точек и скобок.
class SQL:
def __call__(self, table):
return f"SELECT * FROM {table}"
def __getitem__(self, field):
return f'"{field}"'
q = SQL()
print(q("users")) # SELECT * FROM users
print(q["email"]) # "email"
Теперь у нас объект, который и вызывает таблицу, и красиво оформляет поля.
class API:
def __init__(self, base):
self.base = base
def __getitem__(self, endpoint):
return f"{self.base}/{endpoint}"
def __call__(self, endpoint, **params):
query = "&".join(f"{k}={v}" for k, v in params.items())
return f"{self.base}/{endpoint}?{query}"
api = API("https://api.server")
print(api["users"]) # https://api.server/users
print(api("users", id=5, active=True)) # https://api.server/users?id=5&active=True
Такой подход часто используют в клиентах к REST API, чтобы код выглядел как естественный DSL.
class K:
def __getitem__(self, key):
return lambda d: d.get(key)
k = K()
data = {"name": "Neo", "role": "Hacker"}
print(k["name"](data)) # Neo
print(k["role"](data)) # Hacker
Так строятся мини-функции доступа к полям. Особенно круто в map/filter.
__call__ и __getitem__ — это способ превратить скучные объекты в выразительный синтаксис. Так строятся мини-языки прямо в Python: понятные, компактные и без лишнего шума.Please open Telegram to view this post
VIEW IN TELEGRAM
❤7🔥2
frozendict, namedtuple, frozen dataclassХочешь, чтобы данные нельзя было случайно затереть?
В проде часто нужны неизменяемые структуры — особенно в конфигурациях, кэше, многопоточке.
namedtuple — старый, но быстрыйfrom collections import namedtuple
User = namedtuple("User", ["id", "name"])
u = User(1, "Neo")
print(u.name) # Neo
# u.name = "Morpheus" # ❌ AttributeError
@dataclass(frozen=True) — удобно и читаемоfrom dataclasses import dataclass
@dataclass(frozen=True)
class User:
id: int
name: str
u = User(2, "Trinity")
print(u.name) # Trinity
# u.name = "Smith" # ❌ FrozenInstanceError
__init__, __repr__, сравнение и хеш.frozendict — словарь, который не меняетсяfrom types import MappingProxyType
config = MappingProxyType({
"host": "localhost",
"port": 8080
})
print(config["host"]) # localhost
# config["host"] = "127.0.0.1" # ❌ TypeError
Это нативная альтернатива
frozendict, без внешних либ. Используется, когда нужен read-only dict.frozendict через стороннюю либуfrom frozendict import frozendict # pip install frozendict
settings = frozendict(api="v1", retries=3)
print(settings["api"]) # v1
# settings["api"] = "v2" # ❌ TypeError
Здесь dict ведёт себя как set — хешируем и защищён.
# ✅ Кэш: ключи не должны меняться
cache_key = frozendict(user="admin", page=1)
# ✅ Конфиги и константы
@dataclass(frozen=True)
class DBConf:
host: str
port: int
# ✅ Многопоточность — безопасная передача
frozen=True, namedtuple, MappingProxyType и frozendict — всё это про чистые, безопасные, защищённые структуры.Если ты не хочешь, чтобы кто-то случайно изменил важные данные — используй их без сомнений.Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2🔥2
Ты ждал switch в Python?
Python 3.10 привёз не просто switch, а настоящий мощный match — с распаковкой, шаблонами, guard’ами и даже классами.
def handle_command(cmd):
match cmd:
case "start":
print("Запуск...")
case "stop":
print("Остановка.")
case _:
print("Неизвестная команда.")
_ — это как default в switch. Срабатывает, если ничего не подошло.
def handle_coords(point):
match point:
case [0, 0]:
print("Начало координат")
case [x, 0]:
print(f"На оси X: x = {x}")
case [0, y]:
print(f"На оси Y: y = {y}")
case [x, y]:
print(f"Где-то в пространстве: ({x}, {y})")
Можно писать как шаблоны — работает магия распаковки прямо в кейсах.
def describe_user(user):
match user:
case {"name": name, "age": age}:
print(f"{name}, возраст {age}")
case {"name": name}:
print(f"Имя: {name}, возраст неизвестен")
Словари матчатся по ключам.
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
def process(obj):
match obj:
case Point(0, 0):
print("Центр")
case Point(x, y):
print(f"Точка: ({x}, {y})")
Так можно матчить прямо по объектам.
def check_number(n):
match n:
case x if x > 0:
print("Положительное")
case x if x < 0:
print("Отрицательное")
case _:
print("Ноль")
Можно фильтровать значения прямо в кейсе.
def analyze(data):
match data:
case [first, *middle, last]:
print(f"Начало: {first}, конец: {last}, середина: {middle}")
*middle — как в списках: «всё, что посередине».
def react(event):
match event:
case ("click", x, y) if x > 0 and y > 0:
print("Клик по области")
case ("keypress", key):
print(f"Нажата клавиша {key}")
case _:
print("Неизвестное событие")
Можно матчить по типу события, распаковывать и фильтровать — мощь.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Хочешь писать свои CLI‑утилиты без кучи зависимостей? Новый проект Clite от искусного автора на Хабре — как раз то, что нужно. Это minimal‑вес без Typer, Click и прочего — всего \~300 строк кода, и при этом поддержка type hints и декораторов знакомых из FastAPI/Flask!
@app.command() и аннотации для аргументовPlease open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2
Хочешь выйти за рамки Pandas и Flask? В этой статье — необычные библиотеки, которые позволяют рисовать, генерировать графику и создавать интерактивную визуализацию красивого уровня.
В статье ты узнаешь:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2🔥2
__slots__ в Python — ускоряем и экономим на классахЕсли ты создаёшь кучу объектов, каждый по умолчанию несёт с собой `__dict__`, который ест память.
А теперь представь: у тебя 100 000 таких объектов. Зачем лишний мусор?
__slots__: обычный классclass User:
def __init__(self, name, email):
self.name = name
self.email = email
users = [User(f"user{i}", f"u{i}@mail.com") for i in range(100_000)]
Каждый объект несёт
__dict__. Замерим:import sys
u = User("test", "t@mail.com")
print(sys.getsizeof(u)) # 56
print(sys.getsizeof(u.__dict__)) # 232
__slots__class User:
__slots__ = ['name', 'email']
def __init__(self, name, email):
self.name = name
self.email = email
users = [User(f"user{i}", f"u{i}@mail.com") for i in range(100_000)]
u = User("test", "t@mail.com")
print(sys.getsizeof(u)) # 48__dict__:print(hasattr(u, '__dict__')) # False
u.age = 42
# ❌ AttributeError: 'User' object has no attribute 'age'
__slots__ — это жёстко, экономно и быстро.Ты получаешь фиксированную структуру, меньше памяти и ускорение доступа к атрибутам.Не юзай их везде — только там, где реально важно.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2
Хэш-таблица это разреженный массив (массив, в котором имеются незаполненные позиции). В стандартных англоязычных учебниках ячейки хэш-таблицы называются "bucket". В хэш-таблице
dict каждому элементу соотвествует ячейка, содержащая два поля: ссылку на ключ и ссылку на значение элемента. Поскольку размер всех ячеек одинаков, доступ к отдельной ячейке производится по смещению.Хэширование ключа
Когда вы добавляете элемент в словарь, Python вычисляет хэш-значение ключа с помощью встроенной функции
hash(). Это хэш-значение определяет, в какую "ячейку" таблицы (индекс в массиве) поместить пару ключ: значение.Быстрый доступ
При обращении по ключу, Python снова вычисляет хэш ключа и сразу "прыгает" к нужной ячейке — доступ происходит в среднем за O(1).
Коллизии
Если два разных ключа имеют одинаковый хэш (редко, но возможно), Python использует открытую адресацию — ищет ближайшую свободную ячейку.
Распределение
Внутри словаря используется массив (хранилище) и специальный алгоритм для равномерного распределения элементов, чтобы минимизировать коллизии.
Рехэширование
Когда словарь сильно заполняется, Python автоматически увеличивает размер таблицы и перераспределяет элементы (это дорогостоящая операция, но происходит не часто).
📌 Ключевые особенности:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Поиск будет быстрее в
dict и set, потому что это хэш-таблицы, доступ к элементу которых выполняется за O(1). Для list и tuple поиск будет выполняться в среднем за O(n).Исключение работает только для очень маленьких списков длиной до 5 элементов. В этом случае интерпретатору будет быстрей пробежаться по списку, чем считать хеш.
В Python 2 методы словаря
keys, values, items возвращают список. Тоесть перед итерацией по словарю (или сету) интерпретатор сначала создает новый список, что занимает дополнительное время и память, но после создания это уже обыкновенный список. Тоесть в Python 2 итерация по словарям и сетам выполняется дольше, за счет создания нового списка и копирования в него элементов.В Python 3 эти методы создают объект-представление. Это определенно происходит быстрее чем создание нового списка в Python2. Но итерирование по такому представлению должно происходить немного дольше, чем по списку из-за того что данные в словарях хранятся разреженно. В подтверждение вышесказанного (Python 3):
>>> l = list(range(1000000))
>>> d = dict.fromkeys(l)
>>> s = set(l)
>>> def iter_list():
... for i in l:
... pass
...
>>> def iter_dict():
... for i in d:
... pass
...
>>> def iter_set():
... for i in s:
... pass
...
>>> timeit.timeit(iter_list, number=1000)
6.727667486004066
>>> timeit.timeit(iter_dict, number=1000)
9.293120226997416
>>> timeit.timeit(iter_set, number=1000)
8.627948219014797
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
В таких языках как C++ есть переменные, хранящиеся на стеке и в динамической памяти. При вызове функции мы помещаем все аргументы на стек, после чего передаём управление функции. Она знает размеры и смещения переменных на стеке, соответственно может их правильно интерпретировать. При этом у нас есть два варианта: скопировать на стек память переменной или положить ссылку на объект в динамической памяти (или на более высоких уровнях стека). Очевидно, что при изменении значений на стеке функции, значения в динамической памяти не поменяются, а при изменении области памяти по ссылке, мы модифицируем общую память, соответственно все ссылки на эту же область памяти «увидят» новое значение.
В python отказались от подобного механизма, заменой служит механизм связывания (assignment) имени переменной с объектом, например при создании переменной:
var = "john"Интерпретатор создаёт объект
"john" и «имя» var, а затем связывает объект с данным именем. При вызове функции, новых объектов не создаётся, вместо этого в её области видимости создаётся имя, которое связывается с существующим объектом. Но в python есть изменяемые и неизменяемые типы. Ко вторым, например, относятся числа: при арифметических операциях существующие объекты не меняются, а создаётся новый объект, с которым потом связывается существующее имя. Если же со старым объектом после этого не связано ни одного имени, оно будет удалено с помощью механизма подсчёта ссылок. Если же имя связано с переменной изменяемого типа, то при операциях с ней изменяется память объекта, соответственно все имена, связанные с данной областью памяти «увидят» изменения.Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1
Итератор (
iterator) – это объект, который представляет поток данных. Повторяемый вызов метода __next__() (next() в Python 2) итератора или передача его встроенной функции next() возвращает последующие элементы потока.Если больше не осталось данных, выбрасывается исключение
StopIteration. После этого итератор исчерпан и любые последующие вызовы его метода __next__() снова генерируют исключение StopIteration.Итераторы обязаны иметь метод
__iter__, который возвращает сам объект итератора, так что любой итератор также является итерабельным объектом и может быть использован почти везде, где принимаются итерабельные объекты.Итераторы представлены абстрактным классом
collections.abc.Iterator:
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
__next__ возвращает следующий доступный элемент и вызывает исключение StopIteration, когда элементов не осталось. __iter__ возвращает self. Это позволяет использовать итератор там, где ожидается итерируемых объект, например for. __subclasshook__ проверяет наличие у класса метода __iter__ и __next__.Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🤩2
Нужно изменить поведение библиотеки, не трогая её исходники? Используй monkey patching — подмена функций и методов прямо во время выполнения.
📦 Сценарий: хотим переопределить
requests.get, чтобы логировать каждый вызов.pip install requests
import requests
response = requests.get("https://httpbin.org/get")
print(response.status_code)
requests.get.import requests
_original_get = requests.get
def patched_get(*args, **kwargs):
print("📡 Вызов requests.get с аргументами:", args, kwargs)
return _original_get(*args, **kwargs)
requests.get = patched_get
# Тест
response = requests.get("https://httpbin.org/get")
print(response.status_code)
requests.get будет логироваться, без изменения кода библиотеки.🧱 Работает и с классами:
import datetime
# Переопределим now(), чтобы всегда возвращать фиксированную дату
datetime_original = datetime.datetime
class PatchedDateTime(datetime.datetime):
@classmethod
def now(cls, tz=None):
return cls(2000, 1, 1)
datetime.datetime = PatchedDateTime
print(datetime.datetime.now()) # 2000-01-01 00:00:00
Monkey patch — это хак. Используй его, если:
with? Тогда уже нужен unittest.mock.patch.Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3👏2
Airflow, Prefect, Luigi? Необязательно. В Python можно собрать пайплайн без всего этого — просто на генераторах и декораторах. Поток, шаги, трансформации — всё своё.
extract: читаем строки из файла
def extract_lines(path):
with open(path) as f:
for line in f:
yield line.strip()
@transform: оборачиваем функцию в шаг пайплайна
def transform(fn):
def wrapper(source):
for item in source:
yield fn(item)
return wrapper
fn, результат — снова генератор.
@transform
def to_int(x):
return int(x)
@transform
def square(x):
return x * x
@transform
def add_1(x):
return x + 1
def filter_even(source):
for item in source:
if item % 2 == 0:
yield item
def load_to_list(source):
return list(source)
📡 Сборка пайплайна
stream = extract_lines("numbers.txt")
stream = to_int(stream)
stream = square(stream)
stream = add_1(stream)
stream = filter_even(stream)
result = load_to_list(stream)
print(result)
numbers.txt
1
2
3
[2, 10]
def debug(tag):
def inner(source):
for item in source:
print(f"{tag}: {item}")
yield item
return inner
stream = debug("AFTER SQUARE")(stream) — и увидишь, что происходит на каждом шаге.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Это видео объясняет, зачем в Python нужны функции: как они помогают структурировать код, избегать повторений и упрощают масштабирование. Подойдёт начинающим — разбираются примеры с аргументами, возвратом значений и практическими задачами. Отличный старт для понимания, как писать чистый и читаемый код.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2
В Python можно подменить любой импорт. Любой. Всё, что нужно — засунуть объект в
sys.modules до того, как кто-то сделает import.requestsimport sys
class FakeRequests:
def get(self, url):
return type("Response", (), {"text": "[FAKE DATA]"})
sys.modules["requests"] = FakeRequests()
import requests получит подделку. Даже чужая библиотека не узнает, что её обманули.import requests
print(requests.get("https://example.com").text)
[FAKE DATA]
.get().jsonimport types
fake_json = types.ModuleType("json")
fake_json.dumps = lambda obj: "'not json'"
fake_json.loads = lambda s: {"msg": "fake"}
import sys
sys.modules["json"] = fake_json
import json возвращает этот фейковый модуль. Можно заменить поведение стандартных функций.import json
print(json.dumps({"hello": "world"}))
print(json.loads("whatever"))
'not json'
{'msg': 'fake'}
class BlockedPsycopg:
def connect(self, *args, **kwargs):
raise RuntimeError("DB access blocked")
sys.modules["psycopg2"] = BlockedPsycopg()
psycopg2, словит ошибку. Без вариантов.sys.modules — это карта всех импортов. Поменяешь объект в ней — поменяешь поведение на всём проекте. Даже стандартные модули можно обмануть.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥1👏1
Посты на какие темы вы хотели бы увидеть на нашем канале? Пишите в комметариях.
❤5👍1
В Python код — это тоже данные. А значит, его можно прочитать, изменить и выполнить.
Ты буквально пишешь скрипт, который переписывает другой (или самого себя).
with open("script.py", "r") as f:
code = f.read()
code = code.replace("DEBUG = False", "DEBUG = True")
with open("script.py", "w") as f:
f.write(code)snippet = "\nprint('🐍 Я добавлен автоматически')\n"
with open("target.py", "a") as f:
f.write(snippet)exec() для динамического выполненияcode = """
def dynamic_func():
print("Hello from dynamic code!")
"""
exec(code)
dynamic_func()
exec позволяет выполнить строку с кодом — она "оживает" во время выполненияТеперь немного глубже. Не просто текст — а синтаксическое дерево кода.
import ast
tree = ast.parse("x = 2\ny = x + 3")
for node in ast.walk(tree):
print(type(node).__name__)
ast.parse() превращает код в дерево объектов: ты можешь его анализировать или менятьimport ast
import astor
code = "x = 2\ny = x + 3"
tree = ast.parse(code)
tree.body.append(ast.parse("print(y)").body[0])
new_code = astor.to_source(tree)
print(new_code)
print(y) в конец кода — это уже настоящая трансформация кода на летуast, если хочешь безопасность и контроль. exec() — мощно, но опасно.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥1😐1
Когда ты получаешь ввод от пользователя — это не просто строка, это потенциальный вектор атаки. Важно отслеживать такие значения и не допускать их в критические операции (shell, SQL, шаблоны) без фильтрации.
Решение — taint tracking: пометка «заражённых» данных и распространение этого статуса в коде.
str с флагом зараженияclass TaintedStr(str):
def __new__(cls, content, tainted=True):
obj = super().__new__(cls, content)
obj.tainted = tainted
return obj
def __add__(self, other):
other_taint = getattr(other, "tainted", False)
return TaintedStr(super().__add__(other), self.tainted or other_taint)
def strip(self):
return TaintedStr(super().strip(), self.tainted)
def lower(self):
return TaintedStr(super().lower(), self.tainted)
Мы создаём обёртку вокруг
str, которая сохраняет флаг tainted=True, и автоматически переносит его при операциях (+, strip, lower и т.д.).user_input = TaintedStr(" DROP TABLE users; ")
print(user_input) # DROP TABLE users;
print(user_input.tainted) # Truecleaned = user_input.strip().lower()
print(cleaned) # drop table users;
print(cleaned.tainted) # True
strip() и lower() строка остаётся «заражённой». Это важно: механическая очистка не гарантирует безопасность.def run_shell(command):
if getattr(command, "tainted", False):
raise RuntimeError("Заблокировано: команда содержит внешние данные.")
print("OK:", command)
run_shell(cleaned) # RuntimeError
tainted, выбрасываем исключение. Это защищает от инъекций.def sanitize(value):
return TaintedStr(value.strip(), tainted=False)
trusted = sanitize(user_input)
run_shell(trusted) # OK
q1 = TaintedStr("SELECT * FROM users WHERE name = '", tainted=False)
q2 = TaintedStr("admin'; DROP TABLE users;", tainted=True)
q3 = TaintedStr("';", tainted=False)
query = q1 + q2 + q3
print(query.tainted) # Truetainted. Это основной принцип распространения.def taint_propagates(fn):
def wrapper(*args, **kwargs):
tainted = any(getattr(arg, "tainted", False) for arg in args)
result = fn(*args, **kwargs)
if isinstance(result, str):
return TaintedStr(result, tainted)
return result
return wrapper
@taint_propagates
def build_path(username):
return f"/home/{username}"
tainted, и возвращается строка — помечаем результат как tainted.def render_template(tmpl, context):
if getattr(tmpl, "tainted", False):
raise RuntimeError("Нельзя рендерить шаблон с внешними данными")
return tmpl.format(**context)
safe = TaintedStr("Hello, {name}", tainted=False)
danger = TaintedStr("{name}", tainted=True)
render_template(safe, {"name": "Alice"}) # OK
render_template(danger, {"name": "Alice"}) # RuntimeError
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍1
Веб‑разработка на Python активно развивается: появляются новые инструменты, улучшающие производительность, асинхронность и удобство. Хочешь всегда оставаться в тренде? Эта статья для тебя!
В статье ты узнаешь:
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍5
Nginx пишет логи вот в таком виде (по умолчанию):
192.168.1.5 - - [10/Jun/2025:10:42:17 +0000] "GET /index.html HTTP/1.1" 200 123 "-" "curl/7.68.0"
Реальный парсинг, без grep, на Python.
import re
log_line = '''192.168.1.5 - - [10/Jun/2025:10:42:17 +0000] "GET /index.html HTTP/1.1" 200 123 "-" "curl/7.68.0"'''
pattern = re.compile(
r'(?P<ip>\d+\.\d+\.\d+\.\d+)\s.*?"(?P<method>GET|POST|PUT|DELETE)\s(?P<path>/\S*)\sHTTP/[\d.]+"\s(?P<status>\d{3})'
)
match = pattern.search(log_line)
print(match.groupdict())
{'ip': '192.168.1.5', 'method': 'GET', 'path': '/index.html', 'status': '200'}with open("/var/log/nginx/access.log") as f:
for line in f:
m = pattern.search(line)
if m and m["status"].startswith("5"):
print(f'{m["ip"]} → {m["status"]} на {m["path"]}')from collections import Counter
ips = []
with open("/var/log/nginx/access.log") as f:
for line in f:
m = pattern.search(line)
if m:
ips.append(m["ip"])
for ip, count in Counter(ips).most_common(5):
print(f"{ip} — {count} запросов")
$request_time (если ты включил в лог формат)Если ты прописал
$request_time в log_format, строка лога может выглядеть так:192.168.1.5 - - [10/Jun/2025:10:42:17 +0000] "GET /api/data HTTP/1.1" 200 345 "-" "-" 1.245
pattern = re.compile(
r'(?P<ip>\d+\.\d+\.\d+\.\d+).*?"(?P<method>\w+)\s(?P<path>\S+).*"\s(?P<status>\d+).*\s(?P<time>\d+\.\d+)$'
)
with open("/var/log/nginx/access.log") as f:
for line in f:
m = pattern.search(line)
if m and float(m["time"]) > 1.0:
print(f"{m['path']} — медленно: {m['time']} сек")
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3🔥2👏1