Изображение гика

Блог питониста

Какие вопросы задают на собеседованиях на python junior'a/middl'a

9 октября 2017 г.

У меня есть некоторый опыт прохождения python - собеседований на позиции junior/middle python разработчика и я им поделюсь. Эти вопросы можно разделить на такие группы: основы python, более глубокие вопросы про python, обще-алгоритмические вопросы, вопросы про другие языки.

Попробую осветить наиболее часто встречающиеся из них. Не секрет, что часто вопросы повторяются, конечно всегда может быть что-то оригинальное, но костяк вопросов остается примерно неизменным. К слову, надо быть готовым к написанию кода на листочке.

Основы python


Какие типы данных в python?

  1. Изменяемые: list, dict, set
  2. Неизменяемые: number, boolean, string, tuple

Объясните следующий код:

1 b = [i for i in range(10)]
2 b = (i for i in range(10))
3 b = {i for i in range(10)}
  • Создание списка от 0 до 9
  • Создание генераторного выражения
  • Создание сета от 0 до 9

Что выведет на экран следующий код?

1 def a():
2     print('test')
3     for i in range(10):
4         yield i
5 
6 
7 for i in a():
8     print(i)

Здесь создается функция - генератор, соответственно 'test' будет напечатано один раз, далее числа от 0 до 9:

test
0
1
2
3
4
5
6
7
8
9

Что здесь происходит?

1 a = [1, 2, 3]
2 b = a
3 a.pop()
4 b.pop()
5 print(a == b == [1])

Здесь в объект b копируется не сам список, а только ссылка на него, соответственно из одного массива будет удалено два последних элемента, последняя строчка напечатает True.

Какие есть элементы функционального программирования в python?

  • map
  • filter
  • reduce
  • lambda

map позволяет применить какую-либо функцию к любому iterable - объекту:

1 alist = ['1', '2', '3']
2 alist = map(int, alist)

В python 2 map вернет список, но в третьей версии вернется объект - итератор. Соответственно все числа преобразуются в int.

filter повзоляет отобрать только определенные элементы (например все числа больше 1). Так же, как и в случае с map, в python 2 возвращается список, но в python 3 вернется объект - итератор.

1 alist = [1, 2, 3]
2 alist = filter(lambda x: x > 1, alist)
3 print(alist) # [2, 3]
4 # Что, в принципе эквивалентно следующему (для python 2):
5 [x for x in alist if x > 1]
6 # Или, для python 3:
7 (x for x in alist if x > 1)

reduce повзоляет произвести какие-либо вычисления над элементами iterable - объекта

1 from functools import reduce
2 _sum = reduce((lambda x, y: x + y), [1, 2, 3, 4])
3 # конечно, гораздо лучше воспользоваться sum:
4 _sum = sum([1, 2, 3, 4])

lambda функцию удобно использовать например для сортировки массива кортежей по, например, второму элементу:

1 alist = [(2, 2, 3), (1, 1)]
2 alist = sorted(alist, key=lambda x: x[1])
3 print(alist) # [(1, 1), (2, 2, 3)]

Как работает код вида:

1 def f(x, l=[]):
2     for i in range(x):
3         l.append(i*i)
4     print(l)
5 
6 
7 f(2)
8 f(3, [3, 2, 1])
9 f(3)

Тут мы имеем функцию с default аргументом - списком, этот список создается один раз, в момент определения функции, при каждом следующем вызове используется тот же список. Но на строке номер 8 мы вызываем функцию с новым списком, который и используется при этом вызове, на строке же номер 9 будет вызвана функция со списком из первого вызова, соответственно, будет напечатано:

[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]

Напишите 3 способа инвертировать список в python.

1 reversed(alist)
2 alist[::-1]
3 alist.reverse()

В первом случае вернется итератор. Первые два способа не изменяют исходный список, а создают новый, третий - изменяет.

Как из [[1, 2], [3, 4]] получить [1, 2, 3, 4]?

1 alist = [[1, 2], [3, 4]]
2 new_list = []
3 for el in alist:
4     for x in el:
5         new_list.append(x)

Или, что аналогично, но гораздо лучше (питоничнее и быстрее):

1 new_list = [x for el in alist for x in el]

Что делает with?

Позволяет, например, делать что-нибудь с файлами, файл будет автоматически закрыт:

1 with open("x.txt") as f:
2     data = f.read()
3     # do something with data

Также with используется в многопоточном программировании для захвата lock'а:

1 with lock:
2     do something

Работа with тесно связана с менеджером контекста. Менеджер контекста инкапсулирует try...except...finally паттерн.

Внутренности python


Что такое декоратор? Напишите свой декоратор, а потом декоратор с параметрами.

Декоратор - это функция, которая меняет другую функцию. Простейший декоратор:

1 def my_dec(func):
2     def wrapper():
3         print('foo')
4         func()
5     return wrapper

Так можно применить декоратор:

1 @my_dec
2 def test():
3     print('bar')

Или, что абсолютно аналогично:

1 def test():
2     print('bar')
3 
4 
5 test = my_dec(test)

Декоратор с параметрами:

 1 def my_dec(func):
 2     def wrapper(*args, **kwargs):
 3         for arg in args:
 4             print(arg)
 5         func()
 6     return wrapper
 7 
 8 
 9 @my_dec
10 def test():
11     print('foo')
12 
13 
14 test('lol')

Более сложный пример с декоратором:

 1 def outer(ttl=None):
 2     def decorator(func):
 3         print(func)
 4 
 5         def wrapper(astr):
 6             print('ttl', ttl)
 7             func(astr)
 8         return wrapper
 9     return decorator
10 
11 
12 @outer(ttl=15*60)
13 def test(astring):
14     print(astring)
15     print('bar')
16 
17 
18 test('lol')

Когда я впервые увидел подобный код, я не мог понять как работает outer, мне казалось, что это какая-то особенная декораторная магия, что-то вроде декоратора вложенного в декоратор. Чтобы разобраться, мне пришлось понять, как сделать то же самое без синтаксического сахара:

 1 def outer(ttl=None):
 2     def decorator(func):
 3         print(func)
 4 
 5         def wrapper(astr):
 6             print('ttl', ttl)
 7             func(astr)
 8         return wrapper
 9     return decorator
10 
11 
12 # @outer(ttl=15*60)
13 def test(astring):
14     print(astring)
15     print('bar')
16 
17 
18 test = outer(ttl=15*60)(test)
19 test('lol')

То есть, по сути, это декоратор (decorator), обернутый в функцию outer, задача которой принять аргумент ttl и передать его "глубже".

Зачем нужны декораторы?

Декораторы используются для ограничения доступа к некоторым ендпоинтам, например @login_required во Flask, также во Flask'e также используются для того, чтобы "привязывать" view к url'ам.

Напишите декоратор, который замеряет время работы функции.

 1 import time
 2 
 3 
 4 def timer(func):
 5     def wrapper():
 6         init_time = time.time()
 7         func()
 8         print(time.time() - init_time)
 9     return wrapper
10 
11 
12 @timer
13 def test():
14     print('lol')
15 
16 
17 test()

Что такое генератор? Напишите свой генератор.

Чтобы объяснить, что такое генератор, нужно сначала рассказать об итераторах. Итератор - это такой объект, у которого есть два метода - __iter__ и next (__next__ в python 3). Итератор можно перебрать только один раз, после этого он будет "исчерпан". Пример итератора:

 1 class Counter(object):
 2     def __init__(self, size):
 3         self.size = size
 4         self.start = 0
 5 
 6     def __iter__(self):
 7         print("called __iter__", self.size)
 8         return self
 9 
10     def next(self):
11         if self.start < self.size:
12             self.start += 1
13             return self.start
14         raise StopIteration
15 
16 
17 c = Counter(3)
18 for num in c:
19     print(num)
20 
21 for num in c:
22     print(num)
23 
24 ... python test.py 
25 ('called __iter__', 3)
26 1
27 2
28 3
29 ('called __iter__', 3)

Итератор можно использовать один раз, после чего он будет исчерпан, при этом будет выброшено исключение StopIteration:

 1 >>> c = Counter(2)
 2 >>> c_iter = iter(c)
 3 ('called __iter__', 2)
 4 >>> c_iter.next()
 5 1
 6 >>> c_iter.next()
 7 2
 8 >>> c_iter.next()
 9 Traceback (most recent call last):
10   File "<stdin>", line 1, in <module>
11   File "test.py", line 219, in next
12     raise StopIteration
13 StopIteration

Использование итераторов при создании матрицы может не сработать:

 1 >>> c2 = Counter(2)
 2 >>> c3 = Counter(3)
 3 >>> for x in c2:
 4 ...     for y in c3:
 5 ...         print x, y
 6 ... 
 7 ('called __iter__', 2)
 8 ('called __iter__', 3)
 9 1 1
10 1 2
11 1 3
12 ('called __iter__', 3)

Также можно убедиться в том, что наш объект Counter - это self-iteraror, потому что его метод __iter__ возвращает сам объект (instance), в отличие от iterable - объектов типа list, у которых метод iter возвращает отдельный объект - итератор:

 1 >>> print c
 2 <test.Counter object at 0x7fa30a4a6950>
 3 >>> print iter(c)
 4 ('called __iter__', 2)
 5 <test.Counter object at 0x7fa30a4a6950>
 6 >>> id(c) == id(iter(c))
 7 ('called __iter__', 2)
 8 True
 9 
10 >>> items=[1,2,3]
11 >>> print items
12 [1, 2, 3]
13 >>> 
14 >>> print iter(items)
15 <listiterator object at 0x7fa30a4a6a10>
16 >>> id(items) == id(iter(items))
17 False

Пример iterable - объекта, у него задается только __iter__ метод, по нему можно итерироваться сколько угодно раз:

 1 CounterIterator = Counter
 2 
 3 
 4 class Counter2(object):
 5     def __init__(self, size):
 6         self.size = size
 7 
 8     def __iter__(self):
 9         return CounterIterator(self.size)
10 
11 c = Counter2(2)
12 
13 for num in c:
14     print num
15 
16 for num in c:
17     print num
18 
19 
20 1
21 2
22 1
23 2

Генератор - это функция, которая возвращает итератор, каждый раз "запоминает" свое состояние и генерирует по одному элементу за раз. Создается генератор, используя ключевое слово yield. Пример простого генератора:

 1 def simple_generator():
 2     print("generate")
 3     yield 1
 4     yield 2
 5 
 6 
 7 >>> print(simple_generator())
 8 >>> <generator object simple_generator at 0x7f68b6937cd0>
 9 >>> print(dir(simple_generator))
10 >>> ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__',
11  '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__',
12  '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
13  '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running',
14  'next', 'send', 'throw']
15 >>> id(iter(simple_generator())) == id(simple_generator())
16 >>> True

Как вы можете заметить, при вызове генератора он не был исполнен, потому что генераторы, в отличие от функций, не исполняются при вызове, а только при итерации по ним. Также видно, что генератор возвращает объект - генератор, который является итератором (есть next и __iter__). Кроме того, как вы можете заметить, генератор - это также self-iteraror.

1 for x in simple_generator():
2     print x
3 
4 generate
5 1
6 2

Генераторы, в отличие от функций, позволяют итерироваться по ним. Также генераторы "замораживают" свое состояние после yield. Генераторы используются при работе с большими массивами данных, чтобы не загружать их в память целиком:

1 # Python 2
2 N = 100000000000000000
3 for x in range(N):
4     print(x)
5 
6 Traceback (most recent call last):
7   File "test.py", line 252, in <module>
8     for x in range(N):
9 MemoryErrors

Но если использовать генератор (функция xrange в python 2 работает как генератор), то все будет ок:

1 # Python 2
2 N = 100000000000000000
3 for x in xrange(N):
4     print(x)

Чем python 2 отличается от 3?

Конечно же, во втором питоне можно принтить без скобок, самое новое (асинхронность например) есть только в 3-ем питоне, также в нем есть поддержка юникода. В python 3 был улучшен GIL. В 3 ем python функция range стала такой как xrange во втором. Также в 3 питоне была добавлена библиотека mock.

Что такое GIL?

GIL - это Global Interpreter Lock. Это механизм, который обеспечивает потокобезопасность в python, защищая память от неосмотрительных действий программиста. GIL обеспечивает то, что в каждый момент времени активен только один поток. Переключение между потоками такое быстрое, что может показаться, что ваша программа выполняет несколько потоков одновременно, хотя на деле активен только один поток.

Чем итератор отличается от iterable - объекта?

По итератору можно пройти циклом for только один раз, а по iterable - объекту сколько угодно. Iterable - объект не имеет функции next, в отличие от iterator.

Хэширование. Что такое хэш-функция? Как определить, что можно хэшировать, а что нет?

Словарь в питоне реализован на основе хэш - таблицы, поэтому ключами словаря могут быть только хэшируемые типы данных, соответственно, ключами словаря могут быть только неизменяемые типы данных.

Общие вопросы


Какие алгоритмы сортировки знаете?

Ну, тут все просто, я знаю только сортировку пузырьком, список будет отсортирован по возрастанию:

1 import random
2 alist = [random.randrange(10) for _ in range(10)]
3 
4 for i in range(len(alist)-1, 0, -1):
5     for j in range(i):
6         if alist[j] > alist[j+1]:
7             alist[j], alist[j+1] = alist[j+1], alist[j]
8 print(alist)

Чем поток отличается от процесса?

Потоки могут иметь доступ к области видимости друг друга, а процессы изолированы друг от друга.

Расскажите про SQL join'ы?

Создадим две таблицы :

 1 CREATE TABLE orders (
 2   order_id INT,
 3   customer_id INT);
 4 INSERT INTO orders (order_id, customer_id) VALUES (1, 1);
 5 INSERT INTO orders (order_id, customer_id) VALUES (2, 2);
 6 INSERT INTO orders (order_id, customer_id) VALUES (3, 3);
 7 
 8 CREATE TABLE customers (
 9   customer_name char(200),
10   customer_id INT);
11 INSERT INTO customers (customer_name, customer_id) VALUES ('foo', 2);
12 INSERT INTO customers (customer_name, customer_id) VALUES ('bar', 5);
13 INSERT INTO customers (customer_name, customer_id) VALUES ('test', 6);

orders:

order_id customer_id
1 1
2 2
3 3

customers:

customer_name customer_id
'foo' 2
'bar' 5
'test' 6

Тогда данный запрос:

1 SELECT orders.order_id, customers.customer_name
2 FROM orders
3 INNER JOIN customers ON orders.customer_id=customers.customer_id;

Вернет следующую таблицу:


order_id csutomer_name
2 'foo'

То есть INNER JOIN возвращает только "пересечение" из двух таблиц.

Если сделать LEFT JOIN:

1 SELECT orders.order_id, customers.customer_name
2 FROM orders
3 LEFT JOIN customers ON orders.customer_id=customers.customer_id;

То вернутся все результаты из левой таблицы и "пересечение" из правой:

order_id csutomer_name
2 'foo'
1 null
3 null

Если сделать RIGHT JOIN:

1 SELECT orders.order_id, customers.customer_name
2 FROM orders
3 RIGHT JOIN customers ON orders.customer_id=customers.customer_id;

То вернутся все результаты из правой таблицы и "пересечение" из левой:

order_id csutomer_name
2 'foo'
null 'bar'
null 'test'

Если сделать FULL OUTER JOIN:

1 SELECT orders.order_id, customers.customer_name
2 FROM orders
3 FULL OUTER JOIN customers ON orders.customer_id=customers.customer_id;

То вернется вообще все из обоих таблиц:

order_id cutomer_name
1 null
2 'foo'
3 null
null 'bar'
null 'test'

Эти SQL JOIN'ы могут быть представлены в виде диаграмм Венна:

SQL JOINS

Что такое MVC?

Паттерн проектирования Model View Controller, в котором Model отвечает за связь с БД, View - за представления данных (html шаблоны), а Сontroller - за обработку данных, полученных от модели и бизнес - логику.

Какие основные принципы ООП?

  • Инкапсуляция - это когда есть приватные методы, не доступные за пределами класса.
  • Полиморфизм - возможность переопределить методы и переменные в дочернем классе.
  • Наследование - дочерний класс получает все методы и параметры родительского.

Что такое RESTful API?

Это API, которое отвечает JSON'ом или XML'ом, принимает GET, POST, PUT, DELETE запросы, не сохраняет данные о клиенте, GET - запросы не должны приводить к изменению состояния сервера. Один запрос приводит к какому-либо одному действию.

Вопросы про другие языки


Чем отличаются переменные с var и без var в js?

Если в глобальной области - то ничем, если же внутри функции - то без var - это глобальная перменная, а с var - локальная.

Выводы


Если вы изучите эту статью, то вы сможете ответить на наиболее часто встречающиеся вопросы на собеседованиях, конечно это не сделает вас экспертом в python. Возможно, я где-то не слишком точно отвечал на вопросы, но на собеседованиях и не требутся точных академических определений, важно, чтобы вы могли объяснить какие-то понятия своими словами. И, конечно, этого материала явно не достаточно, чтобы претендовать на позицию senior разработчика.

Метки

python
Если вам понравился пост, можете поделиться им в соцсетях: