import time
def compute_intensive_task(n):
result = 0
for i in range(n):
result += i * i
return result
start_time = time.time()
result = compute_intensive_task(10000000) # CPU-bound задача
end_time = time.time()
print(f"Результат: {result}")
print(f"Время выполнения: {end_time - start_time} секунд")
# Для запуска с pypy3: pypy3 example.py
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤4🔥3
def test_sum():
assert sum([1, 2, 3]) == 6
assert sum([-1, 1]) == 0
—Понятны даже джуниору
—Легко дебажить
—Отлично подходят для документирования кода
—Быстро выполняются
—Покрывают только те кейсы, о которых мы подумали
—Часто пропускаем edge-cases
—Может быть много копипасты
from hypothesis import given
import hypothesis.strategies as st
@given(st.lists(st.integers()))
def test_sum_properties(numbers):
assert sum(numbers) == sum(reversed(numbers))
assert sum(numbers + [0]) == sum(numbers)
—Находит неочевидные баги
—Меньше кода, больше покрытие
—Заставляет думать о свойствах функций, а не о конкретных значениях
—Сложнее придумывать правильные свойства
—Медленнее выполняются
—Может быть сложно понять, почему тест упал
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
# Создать новое окружение с Python 3.9
conda create -n my_env python=3.9
# Активировать окружение
conda activate my_env
# Установить пакеты в активное окружение
conda install numpy pandas scipy
# Просмотреть список установленных пакетов
conda list
# Деактивировать окружение
conda deactivate
# Удалить окружение
conda remove -n my_env --all
# Создать окружение из файла environment.yml
conda env create -f environment.yml
# Экспортировать окружение в файл environment.yml
conda env export > environment.yml
# Обновить все пакеты в активном окружении
conda update --all
# Поиск пакета
conda search numpy
environment.yml, экспортировать текущее окружение в файл environment.yml , обновлять все пакеты в окружении и искать нужный пакет. Использование виртуальных окружений с conda — лучшая практика для управления зависимостями проектов и предотвращения конфликтов.Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9❤5🔥3
Path.from pathlib import Path
# Создание объекта Path
file_path = Path("my_file.txt")
directory_path = Path("my_directory")
# Проверка существования файла/директории
if file_path.exists():
print(f"Файл {file_path} существует")
else:
print(f"Файл {file_path} не существует")
if directory_path.exists():
print(f"Директория {directory_path} существует")
if directory_path.is_dir():
print(f"Это директория")
# Создание директории
directory_path.mkdir(parents=True, exist_ok=True) # parents=True создает все родительские директории при необходимости, exist_ok=True предотвращает ошибку, если директория уже существует
# Создание файла и запись в него
file_path.write_text("Hello, pathlib!")
# Чтение из файла
file_content = file_path.read_text()
print(f"Содержимое файла: {file_content}")
# Переименование файла
new_file_path = Path("my_new_file.txt")
file_path.rename(new_file_path)
# Удаление файла
new_file_path.unlink()
# Перебор файлов в директории
for file in directory_path.iterdir():
print(file)
# Получение абсолютного пути
absolute_path = directory_path.resolve()
print(f"Абсолютный путь: {absolute_path}")
# Создание вложенных директорий и файла
nested_dir = Path("my_directory/nested/deeper")
nested_dir.mkdir(parents=True, exist_ok=True)
nested_file = nested_dir / "nested_file.txt" # / используется для объединения путей
nested_file.write_text("Content in nested file")
import shutil
# Удаление директории и ее содержимого
shutil.rmtree(directory_path) # rmtree из shutil используется для удаления непустых директорий
Path, проверяется существование файлов и директорий, создаются и удаляются файлы и директории, производится чтение и запись данных, переименование файлов, перебор файлов в директории и получение абсолютного пути. Также показан пример создания вложенных директорий и файла внутри них с помощью оператора /. И наконец, показано, как удалить директорию и ее содержимое с помощью shutil.rmtree.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤4👍4
Forwarded from IT memer
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🤣6❤2😁2
httpx. В Python httpx предоставляет мощный и удобный асинхронный интерфейс для работы с HTTP-запросами. Асинхронный режим позволяет эффективно обрабатывать множество запросов параллельно, не блокируя основной поток выполнения, что особенно полезно при взаимодействии с медленными или перегруженными серверами.import asyncio
import httpx
async def fetch_page(client: httpx.AsyncClient, url: str):
try:
response = await client.get(url, timeout=10.0)
response.raise_for_status()
return {"url": url, "status_code": response.status_code, "content": response.text}
except httpx.HTTPError as exc:
return {"url": url, "error": f"HTTP Error: {exc}"}
except httpx.TimeoutException:
return {"url": url, "error": "Request timed out"}
except Exception as e:
return {"url": url, "error": f"An unexpected error occurred: {e}"}
async def main():
urls = [
"https://www.example.com",
"https://www.google.com",
"https://www.python.org",
"https://this-is-an-invalid-url.com", # Пример некорректного URL
"https://jsonplaceholder.typicode.com/todos/1" # Пример API
]
async with httpx.AsyncClient(http2=True) as client: # Использование HTTP/2
tasks = [fetch_page(client, url) for url in urls]
results = await asyncio.gather(*tasks)
for result in results:
print(result)
async def make_requests_with_limits(urls, max_concurrent=5):
semaphore = asyncio.Semaphore(max_concurrent)
async def fetch_with_semaphore(url):
async with semaphore:
return await fetch_page(httpx.AsyncClient(), url)
async with httpx.AsyncClient() as client:
tasks = [fetch_with_semaphore(url) for url in urls]
results = await asyncio.gather(*tasks)
for result in results:
print(result)
if __name__ == "__main__":
asyncio.run(main())
# Пример с ограничением числа одновременных запросов
asyncio.run(make_requests_with_limits(["https://www.example.com"] * 10))
fetch_page теперь принимает httpx.AsyncClient в качестве аргумента, что позволяет использовать один клиент для всех запросов внутри main. Это способствует более эффективному использованию соединений. Добавлен пример с некорректным URL и API-запросом для демонстрации обработки ошибок и различных типов ответов. Также показано, как использовать HTTP/2 с помощью httpx.AsyncClient(http2=True). Дополнительно, представлен пример make_requests_with_limits с использованием asyncio.Semaphore для ограничения количества одновременных запросов, что позволяет контролировать нагрузку на сервер и предотвращать ошибки, связанные с превышением лимитов.Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥3
from tqdm import tqdm
import time
# Простой пример с range
for i in tqdm(range(100)):
time.sleep(0.02) # Имитация длительной операции
# С ручным обновлением
with tqdm(total=100) as pbar:
for i in range(10):
time.sleep(0.1)
pbar.update(10) # Обновление индикатора на 10 единиц
# С описанием
for i in tqdm(range(100), desc="Обработка данных"):
time.sleep(0.02)
# Вложенные циклы
from tqdm import trange
for i in trange(4, desc="1st loop"):
for j in trange(5, desc="2nd loop", leave=False):
for k in trange(50, desc="3rd loop", leave=False):
time.sleep(0.001)
# С pandas
import pandas as pd
df = pd.DataFrame({'a': range(10000)})
# Применение tqdm к pandas DataFrame (apply с progress_apply)
tqdm.pandas() # Необходимо для progress_apply
df['b'] = df['a'].progress_apply(lambda x: x*x) # Индикатор выполнения для apply
# Пример с файлами
import os
with open("large_file.txt", "w") as f:
for i in trange(10000, desc="Создание файла"):
f.write("This is a line of text.\n")
with open("large_file.txt", "r") as f:
for line in tqdm(f, total=os.path.getsize("large_file.txt"), unit="B", unit_scale=True, unit_divisor=1024, desc="Чтение файла"):
# Обработка каждой строки файла
pass
range, ручным обновлением, описанием, вложенными циклами, интеграцией с pandas DataFrame с помощью progress_apply и обработкой файлов. Обратите внимание на использование tqdm.pandas() для работы с pandas. Пример с файлами показывает, как отображать прогресс чтения/записи с учетом размера файла.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3🔥2
import asyncio
import aioredis
async def redis_example():
try:
# Подключение к Redis
redis = await aioredis.create_redis_pool(
'redis://localhost',
db=0,
encoding='utf-8', # Декодирование в UTF-8
minsize=5, # Минимальное количество соединений в пуле
maxsize=10 # Максимальное количество соединений в пуле
)
# Работа со строками
await redis.set('name', 'John Doe')
name = await redis.get('name')
print(f"Name: {name}") # Вывод: Name: John Doe
# Работа со списками
await redis.rpush('my_list', 'one', 'two', 'three')
list_length = await redis.llen('my_list')
list_items = await redis.lrange('my_list', 0, -1)
print(f"List length: {list_length}") # Вывод: List length: 3
print(f"List items: {list_items}") # Вывод: List items: ['one', 'two', 'three']
# Работа с хэшами
await redis.hset('user:1', 'name', 'Alice')
await redis.hset('user:1', 'age', 30)
user_data = await redis.hgetall('user:1')
print(f"User data: {user_data}") # Вывод: User data: {'name': 'Alice', 'age': '30'}
# Работа с множествами
await redis.sadd('my_set', 'apple', 'banana', 'cherry')
set_members = await redis.smembers('my_set')
print(f"Set members: {set_members}") # Вывод: Set members: {'apple', 'banana', 'cherry'}
# Использование pipeline
async with redis.pipeline(transaction=True) as pipe: # transaction=True для атомарности операций
await pipe.set('key1', 'value1')
await pipe.get('key1')
await pipe.hset('user:2', 'name', 'Bob')
results = await pipe.execute()
print(f"Pipeline results: {results}")
redis.close()
await redis.wait_closed()
except aioredis.RedisError as e:
print(f"Ошибка Redis: {e}")
if __name__ == "__main__":
asyncio.run(redis_example())
aioredis.create_redis_pool, работа с различными типами данных (строки, списки, хэши, множества), и использование pipeline для пакетной обработки команд в транзакции. encoding='utf-8' в параметрах подключения обеспечивает декодирование в UTF-8. minsize и maxsize управляют размером пула соединений. Обработка исключения aioredis.RedisError помогает выявить проблемы с Redis. Важно запускать асинхронный код внутри asyncio.run.Please open Telegram to view this post
VIEW IN TELEGRAM
1❤5👍4🔥1🥰1
— Быстрый и эффективный подсчёт статистик по данным, таких как сумма, среднее, медиана, дисперсия.
— Аппроксимация распределений и плотностей вероятности.
— Вычисление квантилей, сглаживания, гистограмм.
— Тестирование статистических гипотез, оценка p-value.
— Генерация случайных чисел из разных распределений.
— Первичного анализа и визуализации данных.
— Статистических тестов в научных исследованиях.
— Построения прототипов моделей машинного обучения.
— Анализа пользовательских действий и событий.
— Симуляции процессов на основе статистических моделей.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Инициализация драйвера Chrome (убедитесь, что ChromeDriver установлен и находится в PATH)
driver = webdriver.Chrome()
try:
# Открытие страницы
driver.get("https://www.example.com")
# Явное ожидание элемента с ID "my-element" (до 10 секунд)
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "my-element"))
)
# Вывод текста элемента
print(element.text)
finally:
# Закрытие браузера
driver.quit()
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤6🔥4🤔1
click и предоставляет простой и интуитивно понятный способ определения команд, аргументов и опций.import typer
app = typer.Typer()
@app.command()
def hello(name: str):
"""
Простое приветствие.
"""
print(f"Привет, {name}!")
@app.command()
def goodbye(name: str, formal: bool = False):
"""
Прощание. Можно сделать формальным.
"""
if formal:
print(f"До свидания, {name}!")
else:
print(f"Пока, {name}!")
if __name__ == "__main__":
app()
python main.py --help, вы получите автоматически сгенерированную справку по использованию приложения.Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤4🔥3👻1
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
code = 'print("Hello, world!")'
highlighted_code = highlight(code, PythonLexer(), HtmlFormatter())
print(highlighted_code)
# <div class="highlight"><pre><span></span><span class="nb">print</span><span class="p">(</span><span class="s2">"Hello, world!"</span><span class="p">)</span>
# </pre></div>
highlight, PythonLexer и HtmlFormatter. Затем определяется исходный код, который нужно подсветить. Функция highlight принимает код, лексер (в данном случае PythonLexer для Python) и форматтер (в данном случае HtmlFormatter для HTML) и возвращает подсвеченный код.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍5❤4
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.layer2 = nn.Linear(128, 10)
def forward(self, x):
x = self.layer1(x)
x = self.relu(x)
return self.layer2(x)
Создаем модель одной строчкой! 🎯
model = SimpleNet().to('cuda' if torch.cuda.is_available() else 'cpu')
import tensorflow as tf
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu', input_shape=(784,)),
tf.keras.layers.Dense(10)
])
# Компилируем модель
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
1. Подготовка данных (самая нудная, но важная часть)
2. Определение архитектуры (тут можно пофантазировать)
3. Обучение модели (запасайтесь терпением и мощным железом)
# PyTorch стиль
optimizer = torch.optim.Adam(model.parameters())
criterion = nn.CrossEntropyLoss()
for epoch in range(10):
for batch_x, batch_y in dataloader:
optimizer.zero_grad()
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
# Разбиваем данные
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Следим за метриками
val_loss = []
for epoch in range(epochs):
model.train()
# ... обучение ...
model.eval()
with torch.no_grad():
val_predictions = model(X_val)
v_loss = criterion(val_predictions, y_val)
val_loss.append(v_loss.item())
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥4👍3🤣1
Plotly - это как швейцарский нож в мире визуализации. Вот простой пример создания интерактивного графика:
import plotly.express as px
import pandas as pd
# Создаём тестовые данные
df = pd.DataFrame({
'Месяц': ['Янв', 'Фев', 'Март', 'Апр', 'Май'],
'Продажи': [100, 150, 200, 180, 250],
'Прибыль': [20, 30, 40, 35, 50]
})
# Создаём интерактивный график
fig = px.line(df, x='Месяц', y=['Продажи', 'Прибыль'],
title='Динамика продаж и прибыли',
template='plotly_dark')
# Добавляем hover-эффекты
fig.update_traces(mode='lines+markers', hovertemplate='%{y:,.0f}₽')
# Сохраняем как HTML или показываем в браузере
fig.write_html('sales_dashboard.html')
А теперь давайте создадим что-то более продвинутое с Bokeh:
from bokeh.plotting import figure, show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, HoverTool
import numpy as np
# Создаём интерактивный scatter plot
x = np.random.normal(0, 1, 1000)
y = np.random.normal(0, 1, 1000)
source = ColumnDataSource(data=dict(
x=x,
y=y,
size=np.random.uniform(5, 15, 1000),
color=['#%06x' % np.random.randint(0, 0xFFFFFF) for _ in range(1000)]
))
p = figure(width=800, height=600, title='Интерактивный Scatter Plot')
p.scatter('x', 'y', size='size', color='color', alpha=0.6, source=source)
# Добавляем интерактивные подсказки
hover = HoverTool(tooltips=[
('X', '@x{0.000}'),
('Y', '@y{0.000}'),
('Размер', '@size{0.00}')
])
p.add_tools(hover)
show(p)
import plotly.graph_objects as go
fig = go.Figure(
data=[go.Scatter(x=[1, 2, 3], y=[1, 3, 2])],
layout=dict(
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play",
method="animate",
args=[None])]
)]
)
)
from bokeh.plotting import curdoc
from functools import partial
from tornado.ioloop import IOLoop
def update():
source.data['y'] = np.random.rand(100)
curdoc().add_periodic_callback(update, 100) # Обновление каждые 100мс
from dataclasses import dataclass
from typing import List
@dataclass
class DataPoint:
x: float
y: float
category: str
data_points: List[DataPoint] = [] # Эффективнее, чем DataFrame для больших данных
- Plotly: если нужны красивые графики "из коробки" и важна простота использования
- Bokeh: если работаете с большими данными или нужна глубокая кастомизация
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥3
from transformers import pipeline
classifier = pipeline("sentiment-analysis") # Загрузка предобученной модели для анализа тональности текста
results = classifier([
"I love this library!",
"This is a terrible movie.",
"This is a neutral statement."
])
for result in results:
print(result)
# Примерный вывод:
# [{'label': 'POSITIVE', 'score': 0.9998950958251953}]
# [{'label': 'NEGATIVE', 'score': 0.9991175532341003}]
# [{'label': 'NEGATIVE', 'score': 0.9865201115608215}] # Модель может ошибаться, особенно на нейтральных высказываниях
from transformers import pipeline
generator = pipeline('text-generation', model='gpt2') # Загрузка модели для генерации текста
text = generator("Once upon a time, there was a large language model.", max_length=50, num_return_sequences=2)
for generated_text in text:
print(generated_text['generated_text'])
# Примерный вывод (будет отличаться при каждом запуске):
# Once upon a time, there was a large language model. It was trained on a massive dataset of text and code, and it could generate text, translate languages, write different kinds of creative content, and answer your questions in an informative way.
# Once upon a time, there was a large language model. And he lived in a little house made of straw. One day, he was sitting in his house, reading a book
from transformers import pipeline
summarizer = pipeline("summarization")
text = """
The quick brown fox jumps over the lazy dog. This is a test sentence.
It is used to demonstrate text summarization. The fox is brown and quick.
The dog is lazy. Summarization is a useful NLP task.
"""
summary = summarizer(text, max_length=30, min_length=10, do_sample=False)
print(summary[0]['summary_text'])
# Примерный вывод:
# The quick brown fox jumps over the lazy dog. It is used to demonstrate text summarization. The fox is brown and quick. The dog is lazy.
pipeline для создания готовых к использованию объектов для анализа тональности текста, генерации текста и суммаризации. pipeline автоматически загружает необходимые модели и токенизаторы. Обратите внимание, что для первого запуска потребуется скачать предобученные модели, что может занять некоторое время. Также показаны примеры настройки параметров генерации и суммаризации.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3⚡2🔥2
from datasets import load_dataset
# Загрузка датасета "imdb" с Hugging Face Hub
dataset = load_dataset("imdb")
print(dataset)
# Вывод: DatasetDict({
# train: Dataset({
# features: ['text', 'label'],
# num_rows: 25000
# })
# test: Dataset({
# features: ['text', 'label'],
# num_rows: 25000
# })
# unsupervised: Dataset({
# features: ['text', 'label'],
# num_rows: 50000
# })
# })
train_data = dataset['train']
print(train_data[0]) # Доступ к первому примеру в обучающем наборе
# Вывод: {'text': 'A series of escapades demonstrating the adage that what is good for the goose is also good for the gander, some of which occasionally amuses but none of which amounts to much of a story.', 'label': 0}
small_train_dataset = dataset["train"].shuffle(seed=42).select(range(1000)) # Перемешивание и выборка
from datasets import ClassLabel
import random
import pandas as pd
def show_random_elements(dataset, num_examples=10):
picks = []
n = len(dataset)
for _ in range(num_examples):
pick = random.randint(0, n - 1)
picks.append(dataset[pick])
df = pd.DataFrame(picks)
if isinstance(dataset.features['label'], ClassLabel):
df['label'] = df['label'].apply(lambda x: dataset.features['label'].int2str(x))
print(df)
show_random_elements(small_train_dataset)
# Вывод: таблица с 10 случайными примерами
load_dataset. Выводится информация о структуре датасета и показан доступ к отдельным примерам. Также представлен пример перемешивания и выборки подмножества данных для создания меньшего обучающего набора. Функция show_random_elements демонстрирует удобный способ просмотра случайных элементов из датасета в формате таблицы, обрабатывая при этом ClassLabel для отображения понятных названий меток.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥4
Спасибо что читаете, ставите реакции и комментируете наш канал
И самое главное помните что следующий год предоставит вам гораздо больше возможностей для роста. Наш информационный канал поможет вам стать гораздо лучше и достичь всех начинаний которых вы хотите, и также напоминаем что Новый год - это семейный праздник, сделайте всё возможное чтобы встретить его в кругу близких друзей и родных, всего самого наилучшего, в новом 2025 году!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18👍6🔥3
Функция в Python — это самостоятельный блок кода, который выполняет определенную задачу. Представьте её как отдельный инструмент, который можно использовать где угодно в программе. Функции создаются с помощью ключевого слова def и могут принимать аргументы для обработки.
def calculate_area(length, width):
return length * width
# Использование функции
room_area = calculate_area(5, 4)
print(f"Площадь комнаты: {room_area} кв.м.")
Метод — это функция, которая принадлежит определённому классу или объекту. Он имеет доступ к данным объекта и может изменять его состояние. Методы всегда определяются внутри классов и вызываются через экземпляр класса или сам класс.
class BankAccount:
def __init__(self, balance):
self.balance = balance
def deposit(self, amount):
self.balance += amount
return f"Новый баланс: {self.balance}"
# Использование метода
account = BankAccount(1000)
account.deposit(500) # Вызов метода через объект
1. Область видимости:
- Функции работают с переданными им аргументами
- Методы имеют доступ к данным объекта через self
2. Способ вызова:
- Функции вызываются напрямую: function_name()
- Методы вызываются через объект: object.method()
3. Контекст выполнения:
- Функции независимы от контекста
- Методы всегда работают в контексте своего класса
• Для операций, не требующих доступа к состоянию объекта
• При работе с независимыми данными
• Для создания утилитарных операций
• При работе с данными объекта
• Когда логика тесно связана с классом
• Для реализации поведения объекта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤6🔥4
Ниже показан простой пример, который демонстрирует, как использовать JPype для вызова метода Java.
1. Предположим, у вас есть Java-класс
HelloWorld, который выглядит следующим образом:public class HelloWorld {
public static String greeting() {
return "Hello from Java!";
}
}2. Компилируйте этот класс и убедитесь, что .class файл доступен.
3. Затем вы можете использовать следующий код на Python для вызова метода
greeting:import jpype
# Запуск виртуальной машины Java
jpype.startJVM(jpype.getDefaultJVMPath())
# Загрузка класса
HelloWorld = jpype.JClass("HelloWorld")
# Вызов метода
result = HelloWorld.greeting()
print(result) # Вывод: Hello from Java!
# Остановка виртуальной машины Java
jpype.shutdownJVM()
— Доступ к библиотекам Java из Python.
— Интеграция с существующим Java кодом.
— Использование JVM из Python.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥2👌1
—Простой и понятный синтаксис
—Огромное комьюнити
—Отличная документация
—Ограниченные возможности для 3D
—Не самая высокая производительность
—Современный и чистый API
—Встроенная физика
—Хорошая производительность
—Меньше обучающих материалов
—Относительно молодая библиотека
—Работает на всех платформах, включая мобильные
—Поддержка мультитач
—Сложнее в освоении
—Больше подходит для приложений
—2D-платформеры
—Аркады
—Головоломки
—Карточные игры
—Шутеры с видом сверху
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤5🔥3