Swift | Вопросы собесов
2.13K subscribers
28 photos
954 links
Download Telegram
🤔 Какие плюсы и минусы layout'а?

Каждый способ создания макетов (layout'а) в iOS-разработке имеет свои преимущества и недостатки.

🚩Interface Builder (IB)

Визуальное редактирование
Удобный графический интерфейс для быстрой настройки и визуального просмотра изменений.
Интеграция с Auto Layout
Простая настройка ограничений (constraints) для адаптивного интерфейса.
Быстрое прототипирование
Возможность быстрого создания и изменения макетов без написания кода.
Меньшая гибкость
Ограниченные возможности для создания сложных и динамических макетов.
Конфликты при совместной работе
Трудности при слиянии изменений в storyboard или xib файлах в больших командах.
Производительность
Более медленное время загрузки по сравнению с программными подходами.

🚩Auto Layout

Адаптивность
Поддержка различных устройств и ориентаций экрана.
Мощные инструменты
Возможность создания сложных и адаптивных макетов с помощью ограничений.
Интеграция с Interface Builder
Упрощенная настройка ограничений через визуальный интерфейс.
Крутая кривая обучения
Может быть сложно освоить, особенно для новичков.
Управление сложными макетами
Требует тщательного планирования и может стать сложным при работе с большим количеством ограничений.

🚩Programmatic Layout

Полный контроль
Возможность точной настройки макета с помощью кода.
Гибкость
Легкость создания динамических и условных макетов.
Управление версиями
Легче управлять изменениями в коде по сравнению с визуальными файлами.
Большая трудоемкость
Требует больше времени и усилий для настройки, особенно для сложных макетов.
Меньшая наглядность
Отсутствие визуального редактора может затруднить представление конечного результата.

🚩Stack Views

Упрощение макетов
Легкость создания и управления сложными макетами с минимальными усилиями.
Интеграция с Auto Layout
Автоматическое управление ограничениями для вложенных элементов.
Адаптивность
Поддержка различных ориентаций и размеров экранов.
Ограниченная гибкость
Менее гибкие по сравнению с чистым Auto Layout или программными подходами.
Поддержка
Не всегда подходят для всех типов макетов, особенно для более сложных компоновок.

🚩SwiftUI

Декларативный синтаксис
Простота и понятность кода благодаря декларативному подходу.
Превью в реальном времени
Мгновенное обновление интерфейса при изменении кода.
Интеграция с Swift
Современные возможности языка и тесная интеграция с экосистемой Apple.
Кроссплатформенность
Поддержка различных платформ (iOS, macOS, watchOS, tvOS).
Требования к версии iOS
Поддержка только iOS 13 и выше, что может ограничить использование на старых устройствах.
Зрелость фреймворка
Некоторые функции еще находятся в стадии разработки, и могут быть ограничены возможности по сравнению с UIKit.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В какой момент создается Object?

1. Объект создаётся в памяти при вызове конструктора с использованием ключевого слова new.
2. Для некоторых объектов, таких как строки, создание может происходить в пуле объектов (например, String Pool).
3. Объект также может быть создан с помощью механизмов рефлексии или десериализации.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5🤔3
🤔 Как работает Synchronized "под капотом"?

1. Synchronized использует механизм мониторинга объектов (мониторы).
2. При входе в синхронизированный метод или блок поток захватывает монитор объекта, блокируя доступ другим потокам.
3. После выхода из метода или блока монитор освобождается, позволяя другим потокам войти.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2😁1👀1
🤔 Как работает метод lazy?

Метод lazy откладывает инициализацию переменной до её первого обращения. Это позволяет оптимизировать использование ресурсов, так как вычисление значения происходит только в момент необходимости. Например, это полезно, если вычисление связано с дорогостоящими операциями, которые не всегда используются. Lazy подходит для свойств, которые могут быть не нужны в течение всего жизненного цикла объекта.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Как в DDD работать с зависимостями?

В Domain-Driven Design (DDD) управление зависимостями важно для создания четко разделенных и легко управляемых компонентов. Это достигается следующими методами:

🚩Методы управления зависимостями в DDD

🟠Инъекция зависимостей
Инъекция зависимостей позволяет передавать зависимости через конструкторы или сеттеры, снижая связанность и упрощая тестирование.
interface UserRepository {
findById(id: string): Promise<User | null>;
}

class UserService {
private userRepository: UserRepository;

constructor(userRepository: UserRepository) {
this.userRepository = userRepository;
}

public async getUserById(id: string): Promise<User | null> {
return await this.userRepository.findById(id);
}
}


🟠Сервисы домена
Сервисы домена содержат бизнес-логику, не принадлежащую конкретным сущностям или значимым объектам, и могут взаимодействовать с несколькими объектами или внешними сервисами.
class PaymentService {
private paymentGateway: PaymentGateway;

constructor(paymentGateway: PaymentGateway) {
this.paymentGateway = paymentGateway;
}

public async processPayment(order: Order): Promise<boolean> {
return await this.paymentGateway.charge(order.totalAmount);
}
}


🟠Анти-коррупционные слои
Анти-коррупционные слои защищают доменную модель от внешних влияний и преобразуют данные в понятные форматы.
class ExternalUserService {
public getUserData(id: string): ExternalUser {
// Получение данных от внешнего сервиса
}
}

class UserService {
private externalUserService: ExternalUserService;

constructor(externalUserService: ExternalUserService) {
this.externalUserService = externalUserService;
}

public getUser(id: string): User {
const externalUser = this.externalUserService.getUserData(id);
return new User(externalUser.id, externalUser.name, externalUser.email);
}
}


🟠Контейнеры зависимостей
Контейнеры управляют созданием и жизненным циклом зависимостей, упрощая конфигурацию и разрешение зависимостей.
import "reflect-metadata";
import { Container, injectable, inject } from "inversify";

@injectable()
class UserRepositoryImpl implements UserRepository {
public async findById(id: string): Promise<User | null> {
// Реализация метода
}
}

@injectable()
class UserService {
private userRepository: UserRepository;

constructor(@inject("UserRepository") userRepository: UserRepository) {
this.userRepository = userRepository;
}

public async getUserById(id: string): Promise<User | null> {
return await this.userRepository.findById(id);
}
}

// Конфигурация контейнера
const container = new Container();
container.bind<UserRepository>("UserRepository").to(UserRepositoryImpl);
container.bind<UserService>(UserService).toSelf();

// Разрешение зависимостей
const userService = container.get<UserService>(UserService);


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1😁1
🤔 Lazy потокобезопасный?

Свойства с модификатором lazy потокобезопасны, так как инициализация происходит единожды и атомарно при первом доступе. Это позволяет избежать гонок данных, если свойство используется из нескольких потоков. Однако, если lazy используется в сочетании с объектами, поддержка потокобезопасности может зависеть от контекста. Для сложных случаев стоит дополнительно синхронизировать доступ.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁3👾1
🤔 Какие объекты участвуют в зависимостях?

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

🚩Объект-зависимый и объект-зависимость

Когда один объект (например, ViewController) зависит от другого (NetworkManager), первый становится клиентом, а второй – зависимостью.
class NetworkManager {
func fetchData() {
print("Данные загружены")
}
}

class ViewController {
let networkManager: NetworkManager

init(networkManager: NetworkManager) {
self.networkManager = networkManager
}

func loadData() {
networkManager.fetchData()
}
}


🚩Протоколы как способ ослабления зависимостей

Жесткие зависимости можно ослабить, используя протоколы.
protocol NetworkService {
func fetchData()
}

class NetworkManager: NetworkService {
func fetchData() {
print("Данные загружены")
}
}

class ViewController {
let networkService: NetworkService

init(networkService: NetworkService) {
self.networkService = networkService
}

func loadData() {
networkService.fetchData()
}
}


🚩Зависимости в архитектурах (MVVM, VIPER, DI)

В MVVM зависимость между ViewController и ViewModel.
В VIPER модули зависят друг от друга, но слабо связаны через протоколы.
В DI (Dependency Injection) зависимости передаются снаружи, что повышает тестируемость и гибкость.

🚩Менеджеры зависимостей

Чтобы управлять внешними зависимостями (библиотеками), используются
Swift Package Manager (SPM)
CocoaPods
Carthage

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В каких случаях нужно использовать LinkedList?

LinkedList применяют, когда требуется часто добавлять или удалять элементы, особенно в середине или начале структуры. В отличие от массива, вставка и удаление в LinkedList эффективны по времени, так как не требует сдвига элементов. Однако доступ к элементам по индексу медленный, так как приходится проходить всю цепочку. LinkedList эффективен в сценариях с динамическим изменением структуры данных.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
🤔 Зачем нужен оператор future?

В языке Swift нет встроенного оператора future, но если речь идет о концепции Future из асинхронного программирования, то давай разберемся, зачем она нужна и как используется.

🚩Что такое Future?

Future (или Promise в некоторых реализациях) — это объект, который представляет значение, которое станет доступным в будущем после завершения асинхронной операции. Это удобно, когда нужно работать с кодом, который выполняется не мгновенно
Запросы в сеть (API)
Чтение файлов
Долгие вычисления
В Swift Future чаще всего используется в рамках Combine.

🚩Как работает Future в Swift (на примере Combine)?

В Combine есть структура Future, которая позволяет создать асинхронную операцию и подписаться на ее результат:
import Combine

// Функция, которая возвращает Future
func fetchData() -> Future<String, Error> {
return Future { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
let success = Bool.random() // Симулируем успех или ошибку

if success {
promise(.success("Данные загружены!"))
} else {
promise(.failure(NSError(domain: "Ошибка загрузки", code: -1, userInfo: nil)))
}
}
}
}

// Используем Future
let future = fetchData()

let cancellable = future.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Завершено без ошибок")
case .failure(let error):
print("Ошибка: \(error.localizedDescription)")
}
}, receiveValue: { value in
print("Получены данные: \(value)")
})


🚩Когда использовать Future?

Когда нужна одноразовая асинхронная операция (например, запрос в сеть)
Когда используешь Combine и хочешь обернуть асинхронный код в реактивный стиль
Если в будущем планируешь объединять несколько асинхронных операций (композиция Future)

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1
🤔 Что использовать вместо наследования для расширения класса?

Вместо наследования часто используют композицию, делегирование или протоколы. Композиция позволяет создавать сложные объекты, комбинируя другие объекты, а не полагаясь на жёсткую иерархию классов. Делегирование используется для передачи ответственности от одного объекта другому. Протоколы позволяют описать поведение, которое может быть реализовано разными классами, обеспечивая гибкость.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Чем отличаются слабые и сильные ссылки?

В Swift ссылки (references) на объекты могут быть сильными (strong) и слабыми (weak). Они отличаются способом управления памятью и временем жизни объектов, на которые ссылаются.

🚩Сильные ссылки (Strong References)

Сильная ссылка (strong reference) удерживает объект в памяти. Пока существует хотя бы одна сильная ссылка на объект, он не будет удалён из памяти.
Сильные ссылки используются по умолчанию и обеспечивают, что объект остаётся доступным до тех пор, пока он необходим.
class Person {
var name: String
init(name: String) {
self.name = name
}
}

var person1: Person? = Person(name: "Alice")
var person2: Person? = person1 // person2 имеет сильную ссылку на тот же объект, что и person1

person1 = nil // Объект все еще удерживается в памяти благодаря person2
print(person2?.name) // Output: Alice

person2 = nil // Теперь объект будет удалён из памяти, так как нет сильных ссылок


🚩Слабые ссылки (Weak References)

Слабая ссылка (weak reference) не удерживает объект в памяти. Если объект больше не имеет сильных ссылок, он будет удалён из памяти, даже если на него существуют слабые ссылки.
Слабые ссылки используются для предотвращения циклических ссылок, которые могут привести к утечкам памяти.
class Person {
var name: String
init(name: String) {
self.name = name
}
}

class Apartment {
var tenant: Person?
init(tenant: Person?) {
self.tenant = tenant
}
}

var alice: Person? = Person(name: "Alice")
var apartment: Apartment? = Apartment(tenant: alice)

// Создание слабой ссылки для предотвращения циклической зависимости
class Tenant {
var name: String
weak var apartment: Apartment? // Слабая ссылка
init(name: String) {
self.name = name
}
}

let tenant = Tenant(name: "Bob")
apartment?.tenant = alice

alice = nil // Объект Person будет удалён из памяти, так как больше нет сильных ссылок
print(apartment?.tenant?.name) // Output: nil


🚩Основные различия между сильными и слабыми ссылками

🟠Удержание в памяти
Сильные ссылки: Удерживают объект в памяти. Объект будет освобождён только тогда, когда все сильные ссылки на него будут удалены.
Слабые ссылки: Не удерживают объект в памяти. Объект будет освобождён, когда не останется сильных ссылок.

🟠Использование
Сильные ссылки: Используются по умолчанию для обеспечения сохранности объектов в памяти.
Слабые ссылки: Используются для предотвращения циклических ссылок и утечек памяти, особенно в структурах данных с взаимосвязанными объектами.

🟠Обнуление
Сильные ссылки: Не обнуляются автоматически при удалении объекта из памяти.
Слабые ссылки: Автоматически обнуляются, когда объект, на который они ссылаются, удаляется из памяти.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Чем отличается композиция от наследования?

Композиция создаёт объект из других объектов, предоставляя функциональность через их взаимодействие. Наследование же связывает классы через жёсткую иерархию, где дочерний класс наследует поведение родительского. Композиция даёт большую гибкость, так как не требует изменения базовых классов. Наследование подходит, если есть чёткая иерархическая структура, но оно увеличивает связность кода.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что относится к типам данных?

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

🤔 Основные типы данных

🟠Примитивные типы данных
Представляют целые числа. Int, UIn
       let age: Int = 25


Представляют дробные числа. Float, Double
       let pi: Double = 3.14159


Представляют логические значения true или false. Bool
       let isActive: Bool = true


Представляют отдельные символы. Character
       let letter: Character = "A"


🟠Составные типы данных (Composite data types)
Представляют последовательности символов. String
       let greeting: String = "Hello, World!"


Представляют упорядоченные коллекции элементов одного типа. Array<T>
       let numbers: [Int] = [1, 2, 3, 4, 5]


Представляют коллекции пар ключ-значение. Dictionary<Key, Value>
       let user: [String: String] = ["name": "Alice", "age": "30"]


Представляют коллекции уникальных элементов. Set<T>
       let uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]


🟠Пользовательские типы данных (User-defined data types)
Представляют тип данных с набором связанных значений. enum
       enum CompassPoint {
case north
case south
case east
case west
}


Представляют группы связанных значений. struct
       struct Person {
var name: String
var age: Int
}
let person = Person(name: "Alice", age: 30)


Представляют объекты с состоянием и поведением. class
       class Car {
var model: String
var year: Int

init(model: String, year: Int) {
self.model = model
self.year = year
}
}
let car = Car(model: "Tesla", year: 2021)


🟠Специальные типы данных
Представляют группы нескольких значений различных типов. (Type1, Type2, ...)
       let coordinates: (Int, Int) = (10, 20)


Представляют значение, которое может быть либо некоторым значением, либо nil. Optional<T>
       var optionalName: String? = "Alice"


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Чем отличается класс user от класса data?

Класс user представляет собой объект с полями и методами для описания функциональности. Класс data (или его аналог) чаще всего содержит только данные и минимум логики, используется для передачи информации между слоями приложения. В Swift структуры часто применяются вместо data-классов, так как они работают как типы значений. Класс user может иметь сложную логику, тогда как data сосредоточен на представлении информации.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔3
🤔 Что такое autoclosure?

@autoclosure
— это специальный атрибут в Swift, который автоматически превращает переданное выражение в замыкание. Это позволяет отложить выполнение выражения до момента, когда оно действительно понадобится.

🤔Зачем нужен @autoclosure?

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

🚩Пример без

Допустим, у нас есть функция, которая принимает замыкание
func logMessage(_ message: () -> String) {
print("Лог: \(message())")
}

// Вызываем функцию, передавая замыкание
logMessage { "Сообщение: \(2 + 2)" }

🚩Пример с

Теперь используем `@autoc чтобы сделать вызов функции проще
func logMessage(_ message: @autoclosure () -> String) {
print("Лог: \(message())")
}

// Теперь аргумент можно передавать без {}
logMessage("Сообщение: \(2 + 2)")

🚩Где это полезно?

🟠`assert` и precondition
Стандартные функции Swift используют @autoclosure, чтобы избежать вычисления аргументов, если проверка не нужна:
assert(2 + 2 == 4, "Ошибка: 2 + 2 не равно 4!")

🟠Ленивое выполнение в кастомных функциях
Допустим, у нас есть функция, которая выполняет блок только если включен режим отладкие в замыкание. Это позволяет отложить выполнение выражения до момента, когда оно действительно понадобится.

🤔Зачем нужен `@autoclosure`?

Обычно его используют для улучшения читаемости кода, особенно когда нужно.`@autoc но если нужно сохранить замыкание для будущего выполнения, это можно сделать рый автоматически превращает переданное выражение в замыкание. Это позволяет отложить выполнение выражения до момента, когда оно действительно понадобится.

🤔Зачем нужен `@autoclosure`?

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

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
🤔 Что такое инкрементальная сборка?

Инкрементальная сборка позволяет компилировать только изменённые файлы, а не весь проект. Это значительно ускоряет процесс сборки больших приложений. Используется в современных компиляторах, таких как Xcode, для оптимизации времени разработки. Инкрементальная сборка эффективна, если изменения затрагивают небольшую часть кода.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Inam относится ли к value тайпам?

enum (перечисления) относится к типам значений (value types). Это означает, что при передаче перечисления функции или при его присвоении переменной создается новая копия этого перечисления. Это работает аналогично другим типам значений, таким как struct и базовые типы данных (Int, String, Double и т.д.).

🚩Особенности

🟠Копирование при присвоении
Когда переменной перечисления присваивается значение другой переменной перечисления, создается независимая копия этого значения.

🟠Безопасность типов
enum поддерживает безопасность типов, что позволяет создавать код, легкий для понимания и поддержки.

🟠Связанные значения
enum может хранить связанные значения различных типов.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}


🟠Методы
enum может иметь методы, которые предоставляют функциональность, связанную с перечислением.
enum Planet {
case earth, mars, venus

func isHabitable() -> Bool {
switch self {
case .earth:
return true
default:
return false
}
}
}


Пример
enum TrafficLight {
case red, yellow, green
}

var light = TrafficLight.red
var lightCopy = light
lightCopy = .green

print(light) // Выводит "red"
print(lightCopy) // Выводит "green"


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁31👍1
🤔 Что известно про Firebase Remote Config?

Это инструмент для управления поведением приложения и его интерфейсом без выпуска обновлений. Разработчики могут изменять параметры в реальном времени, например, активировать функции или изменять дизайн. Это особенно полезно для A/B тестирования или настройки функций для разных групп пользователей. Remote Config интегрируется с другими инструментами Firebase, такими как Analytics.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Что значит половина массива?

Половина массива — это подмножество элементов массива, которое составляет примерно 50% его длины. В зависимости от контекста, это может быть:
Первая половина – элементы от начала массива до середины.
Вторая половина – элементы от середины до конца массива.
Любая подгруппа, близкая к 50% – например, при нечетном количестве элементов можно взять либо на один элемент больше, либо меньше.

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let middleIndex = array.count / 2 // Делим пополам

let firstHalf = Array(array[..<middleIndex]) // Первая половина
let secondHalf = Array(array[middleIndex...]) // Вторая половина

print(firstHalf) // [1, 2, 3, 4, 5]
print(secondHalf) // [6, 7, 8, 9, 10]


🚩Зачем это нужно?

Разбиение данных для обработки (например, сортировка "разделяй и властвуй").Разделение элементов для параллельной обработки.
Разбиение коллекций при пагинации.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔2
Forwarded from easyoffer
💡 В EasyOffer 2.0 появится фильтрация вопросов по грейдам и типам интервью!

📊 Например, вот вероятности ТОП-30 вопросов, которые задают на HR-скрининге Python-разработчику уровня Middle/Senior. Данные основаны на 53 реальных интервью.

97% Какие у тебя зарплатные ожидания
73% Какие у тебя есть вопросы
44% Какие критерии при выборе будущей работы
41% Расскажи о себе
38% Почему ищешь работу
35% Расскажи про свой опыт
35% Расскажи про проект на предыдущей работе
32% Почему уволился с предыдущей работы
29% Где территориально сейчас живешь/находишься
23% Есть ли другие предложения по работе
17% Есть ли военный билет
17% Почему хочешь сменить работу
17% Как проводишь свободное время
17% Расскажи про задачи на предыдущей работе
17% Сколько коммерческого опыта работы с Python
17% С какими БД работал
14% Находишься ли в активном поиске работы
14% С каким стеком работаешь
14% Почему решил откликнуться на нашу вакансию
14% Какой текущий статус поиска работы
11% Почему решил стать программистом
11% С какими фреймворками работал
11% Какую зарплату получал на предыдущей работе
11% Работаешь ли в настоящий момент
11% На какой грейд себя оцениваешь
11% Как быстро можешь приступить к работе после получения офера
11% Расскажи про свои pet-проекты
8% Какие знаешь типы данных в Python
8% Что такое декоратор в Python
8% Что ищешь на новой работе

🚀 Скоро стартует краудфандинговая кампания, которая поможет ускорить разработку EasyOffer 2.0.
Первые спонсоры получат уникальные лимитированные награды!

📢 Если вам это интересно, подписывайтесь на канал 👉 этот телеграм канал