Стать Python-разработчиком после PHP оказалось сложнее, чем подняться на Оштен (гора Кавказского хребта, 2804 метра). Нет, подняться на Оштен вполне посильная задача, нужна небольшая подготовка. Вот и я думал, что три года опыта коммерческой разработки на PHP мне дадут крылья. Это должна была быть "небольшая подготовка", чтобы сразу оказаться на середине горы или хотя бы у её подножия. Все оказалась слегка не так. Больше половины компаний не отвечают. Возможно, увидели, что у меня нет Python в разделе с опытом работы. Остается только догадываться. Кто-то не сообщает об оценке тестового задания. Каков мой настрой? Что ж, его качает, но все же просто дайте мне точку опоры (пару лет опыта на Python), и я переверну Землю!
Если ты сейчас решаешь похожий вопрос – советую продолжать учиться, практиковаться и договариваться о новых собеседованиях. Чтобы не растерять знания по дороге, я сделал для себя небольшую базу знаний с ответами на основные вопросы, которые касаются Python. Я на них часто отвечаю при первой беседе с компанией и на техническом интервью. Для подготовки этой базы я изучал публичные собеседования на должность Junior Python-разработчика и опыт технических интервьюеров. Помогли и собственные карандашные записи со встреч. Ты спросишь: "Зачем нужны эти знания, ты можешь мне сказать?" А я отвечу как дневник одного злого волшебника: "Нет. Но я могу показать". На одном собеседовании технический специалист сказал, что я знаю о Python больше, чем некоторые программисты уровня Middle. Жаль, что тогда мне не хватило знаний о базах данных.
К каждому вопросу я подобрал пример кода. Это поможет лучше разобраться и запомнить, как оно там все в этом змеином языке устроено.
Какие основные типы данных есть в Python?
В Python основные типы данных делятся на две группы: неизменяемые (immutable) и изменяемые (mutable). К неизменяемым типам данных относятся:
-
None
-
bool (True или False)
-
int (так же float, long, complex)
-
str
-
tuple – кортеж
-
frozenset – неизменяемое множество (содержит уникальные значения)
К изменяемым типам данных относятся:
-
list – список
-
dict – словарь
-
set – множество (содержит уникальные значения)
Значения 0
и False
, а так же 1
и True
считаются эквивалентными, поэтому они объединяются при создании множества (set или frozenset).
# tuple (кортеж)
newTuple = (1, 3.14, "Harry", True)
# frozenset (неизменяемое множество)
newFrz = frozenset([1, 3.14, "Harry", True, 3.14])
print(type(newFrz), newFrz)
# <class 'frozenset'> frozenset({1, 3.14, 'Harry'})
# list (список)
newList = [1, 3.14, "Harry", True]
# dict (словарь)
newDict = {
True: 1,
"pi": 3.14,
"name": "Harry",
1: True
}
# set (множество)
newSet = set([1, 3.14, "Harry", True, 3.14, 1])
print(type(newSet), newSet)
# <class 'set'> {1, "Harry", 3.14}
Чем отличаются операторы ==
и is
?
==
сравнивает два операнда по значениям, а is
– по их адреса памяти. Наглядно это разницу можно увидеть с помощью функции id(object)
, которая возвращает уникальный идентификатор (адрес памяти) для указанного объекта.
list1 = [1, 3.14, "Harry", True]
list2 = [1, 3.14, "Harry", True]
print(id(list1), id(list2), list1 == list2, list1 is list2)
# 937664 937984 True False
list3 = list1
print(id(list1), id(list3), list1 == list3, list1 is list3)
# 937664 937664 True True
В приведенном примере неслучайно используется изменяемый тип данных. Для неизменяемых типов переменные с одинаковым значением будут иметь одинаковый адрес памяти.
a = 3.14
b = 3.14
print(id(a), id(b), a == b, a is b)
# 170944 170944 True True
Как в Python передаются аргументы в функцию (изменяемые и неизменяемые)?
Изменяемые аргументы передаются по ссылкам, а неизменяемые – по значениям. Далее пример функции, принимающей аргумент изменяемого типа (list). Вызовем эту функцию три раза и выведем результат:
def some_function(some_arg: list = []):
some_arg.append(1)
return some_arg
print(some_function()) # [1]
print(some_function()) # [1, 1]
print(some_function()) # [1, 1, 1]
В данном примере, поскольку list является изменяемым типом и передается в функцию ссылкой, аргумент не будет каждый раз при вызове функции иметь пустой список.
Но если переменная some_arg
имеет, например, тип int (неизменяемый тип), то в функцию передается именно значение переменной, а не ссылка на неё. Каждый раз при вызове функции аргументу some_arg
будет присваиваться 0
. Поэтому, вызвав новую функцию три раза, результат будем следующим:
def some_function(some_arg: int = 0):
some_arg = some_arg + 1
return some_arg
print(some_function()) # 1
print(some_function()) # 1
print(some_function()) # 1
Адреса памяти аргумента some_arg
в обоих примерах можно так же получить с помощью функцииid(object)
.
Что такое *args
и **kwargs
? Чем представлены?
*args
– аргумент, который принимает в себя неограниченное количество позиционных аргументов функции. В Python *args
представлен как кортеж (tuple). Пример функции с *args
:
def some_function(*some_args):
for i, x in enumerate(some_args):
print(f'[{i}] = {x}')
some_function(10, 25, 33)
# [0] = 10
# [1] = 25
# [2] = 33
**kwargs
– аргумент, который принимает в себя неограниченное количество аргументов функции с помощью ключевых слов. В Python **kwargs
представлен как словарь (dict). Пример функции с **kwargs
:
def some_function2(**some_args):
for i, x in some_args.items():
print(f'[{i}] = {x}')
some_function2(one=10, two=25, three=33)
# [one] = 10
# [two] = 25
# [three] = 33
Что такое аннотации типов? Зачем они нужны? Когда выполняются?
Аннотация типов – это подсказка о типе данных к переменной или к аргументу функции. Пример:
price: int = 5
title: str
def some_function(x: str, y: int) -> str:
return f'x = {x}, y = {y}'
Применяется, во-первых, для того, чтобы программист, который будет читать ваш код, знал, какие переменные какие типы данных ожидают. Во-вторых, в современных IDE (например, в PyCharm) подсвечиваются ошибки, связанные с типами данных переменных и аргументов функции.
Аннотации типов выполняются не в runtime.
Что происходит при операции a = b
?
Все зависит от того, какого типа переменная a
. При присваивании b
значения a
, переменная b
всегда будет ссылаться на тот же адрес памяти, что и a
. При изменении переменной a
возможны два варианта поведения переменной b
:
-
если
a
изменяемого типа, то при измененииa
будет изменена иb
. Адрес памяти для обеих переменных не меняется; -
если
a
неизменяемого типа, то при измененииa
переменнаяb
не будет изменена. Переменнойa
будет присвоен новый адрес памяти, переменнаяb
адрес памяти не изменит.
Пример, который отражает оба случая:
a = [7, 24, 38]
b = a
print("a", id(a), a) # a 1398940 [7, 24, 38]
print("b", id(b), b) # b 1398940 [7, 24, 38]
a[1] = 202
print("a", id(a), a) # a 1398940 [7, 202, 38]
print("b", id(b), b) # b 1398940 [7, 202, 38]
a = 38
b = a
print("a", id(a), a) # a 9792512 38
print("b", id(b), b) # b 9792512 38
a = 24
print("a", id(a), a) # a 8599631 24
print("b", id(b), b) # b 9792512 38
Что такое тернарный оператор? Как записывается?
Тернарный оператор – это обычная конструкция if
, которая для удобства читаемости и лаконичности синтаксиса записана в одну строку. Пример кода:
result = "A больше B" if a > b else "A не больше B"
Как оценивается сложность алгоритмов? Что такое нотация Big O?
Сложность алгоритмов оценивается с помощью нотации Big O (большое О). Измерять скорость алгоритма во времени не показательно, поскольку скорость работы любого алгоритма в том числе зависит от мощности компьютера. Поэтому используется оценка алгоритма с точки зрения атомарных операций, которые происходят внутри него. Основные сложности (в порядке возрастания):
-
O(1)
– константная -
O(log2(n))
– логарифмическая -
O(n)
– линейная -
O(n * log(n))
– квазилинейная -
O(n^2)
– квадратичная -
O(n!)
– факториальная
Какая сложность основных операций в списке (list) и словаре (dict)?
List |
Dict |
|
Удаление |
О(n) |
О(1) |
Вставка |
О(n) |
О(1) |
Чтение |
О(1) |
О(1) |
Поиск |
О(n) |
О(n) |
На собеседовании у меня случилась дискуссия с техническим специалистом. Спор касался способа хранения в памяти списка (list). Я утверждал, что в памяти список представлен массивом, а не связанным списком. Моим аргументом была скорость работы основных операций списка (list), который характерен для массива, а не для связанного списка. К слову, мы не решили кто прав.
В рамках этой статьи я описал ответы на несколько основных вопросов, которые мне задают на собеседованиях на должность Junior Python-разработчика. Это первая часть материала. К тому же, это моя первая статья на Хабре. Проба пера. Надеюсь, что материал оказался для вас полезным и интересным. Я планирую продолжить его и описать еще часть ответов на основные вопросы.
Желаю вам всего доброго и мирного неба над головой.
Шалость удалась!
Автор: Влад Бойко