15 вопросов по Python: как джуниору пройти собеседование

Готовитесь к собеседованию на позицию Python-джуниора? Подборка важных вопросов по Python с объяснением и полезными ссылками вам поможет.

15 вопросов по Python

Python надежно занял место в пятерке самых популярных языков программирования. Кажется, в ближайшее время он не собирается сдавать позиции. Число разработчиков растет быстрее, чем количество рабочих мест для них. Чтобы получить должность джуниора в хорошей компании, важно отлично показать себя на собеседовании. Итак, что же должен знать начинающий Python-программист по мнению работодателя.

О Питоне в двух словах

Разумеется, отправляясь на интервью по Python, соискатель должен иметь общее представление об этом языке программирования.

Мировые IT-лидеры, такие как Google и Dropbox, активно используют Python в своих проектах. Причина популярности кроется в его простоте и мощности.

Прежде всего, язык эффективен в активно развивающихся сферах веб-разработкимашинного обучения и big data. На нем создаются игры и научные модели. Также он с успехом применяется в системном администрировании и автоматизации задач.

Топ-5 самых популярных языков программирования

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

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

10 базовых вопросов по Python

Прежде всего, программист должен хорошо знать свой рабочий инструмент и его возможности. В противном случае придется изобретать велосипеды и тратить время на поиски очевидных решений. Чтобы показать себя в лучшем свете на собеседовании, просмотрите еще раз справочники по Python.

Базовые вопросы по Python

Изменяемые и неизменяемые типы данных

Все данные относятся к одному из двух типов – неизменяемые или изменяемые. Очевидно, что первые в конце программы выглядят точно так же, как и в начале. К ним относятся числа, строки и кортежи. С другой стороны, списки и словари могут измениться в процессе работы. Например, добавятся или удалятся элементы.

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

def foo(a=[]):
    a.append(1)
    print(a)

foo()
foo()
foo()

Первый вызов функции foo предсказуемо выведет список, состоящий из одного элемента 1. Однако если вы ожидаете такого же результата от второго и третьего вызовов, то будете удивлены. На самом деле, вывод будет следующим:

# [1]
# [1, 1]
# [1, 1, 1]

Так происходит, потому что при первом вызове в памяти создается пустой список a. Именно к нему функция будет обращаться и дальше, если не получит собственный аргумент. Так как список не копируется, а передается по ссылке, он будет изменяться.

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

Хеширование

Хеш-таблицы – это особые структуры данных, подобные ассоциативным массивам. Ключами в них могут выступать не только числа, но и другие объекты. Однако есть одно важное условие. Для каждого ключа требуется вычислить особый уникальный код. Этим занимаются специальные функции.

Хеш-функция в Python

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

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

Часто говорят, что изменяемые объекты Python в принципе нельзя хешировать, а неизменяемые – всегда можно. На самом деле, возможность хешировать объект и его неизменяемость – понятия разные.

Лучше разобраться в концепции поможет видео:

Виды строк

Оперировать строками в Python – одно удовольствие, так как язык предоставляет для них множество удобных методов. Также имеется поддержка "сырых" строк и строковых литералов.

Чтобы строка стала "сырой", перед ней необходимо поставить символ r в любом регистре:

common_string = 'C:\file.txt'
raw_string = r'C:\file.txt'
print(common_string) # C: ile.text
print(raw_string) # C:\file.txt

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

Строковые литералы заключаются в тройные кавычки или апострофы. С их помощью удобно обрабатывать большие блоки текста, например, HTML-код, так как их можно разбить на несколько строк. Экранирование кавычек внутри литерала не требуется.

s = '''<div>
  <a href="#">content</a>
</div>'''
print(s)

Этот код выведет все, что находится между тройными апострофами. При этом кавычки в значении атрибута и переносы строк сохранятся.

Лямбда-выражения

Лямбды пришли в Python из языка Lisp. Это простые анонимные функции, записанные в одну строку. Их можно объявить даже там, где нельзя воспользоваться инструкцией def. Например, эти выражения часто используются в методах filter и map.

foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]

print(list(filter(lambda x: x % 3 == 0, foo)))
# [18, 9, 24, 12, 27]

print(list(map(lambda x: x * 2 + 10, foo)))
# [14, 46, 28, 54, 44, 58, 26, 34, 64] 

Списки

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

Вот небольшая задачка по python-спискам для тренировки мозга:

A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
A1 = range(10)
A2 = sorted([i for i in A1 if i in A0])
A3 = sorted([A0[s] for s in A0])
A4 = [i for i in A1 if i in A3]
A5 = {i:i*i for i in A1}
A6 = [[i,i*i] for i in A1]

Определите, что находится в каждой переменной, и сравните свои предположения с ответом.

A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4} 
A1 = range(0, 10)
A2 = []
A3 = [1, 2, 3, 4, 5]
A4 = [1, 2, 3, 4, 5]
A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

Если эти преобразования вам непонятны, потратьте на них немного времени.

Итераторы и генераторы

Итератор – это интерфейс, который позволяет перебирать элементы последовательности. Он используется, например, в цикле for ... in ..., но этот механизм скрыт от глаз разработчика. При желании итератор можно получить "в сыром виде", воспользовавшись функцией iter().

Чтобы получить следующий элемент коллекции или строки, нужно передать итератор функции next().

Под капотом функциональность реализуется в методах __iter__ и __next__.

Итераторы в Python

Пример простого итератора:

class SimpleIterator:
    def __iter__(self):
        return self

    def __init__(self, limit):
        self.limit = limit
        self.counter = 0

    def __next__(self):
        if self.counter < self.limit:
            self.counter += 1
            return 1
        else:
            raise StopIteration

simple_iter = SimpleIterator(5)

for i in simple_iter:
    print(i)
# 1
# 1
# 1
# 1
# 1

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

numbers = range(10)
squared = [n ** 2 for n in numbers if n % 2 == 0]
print(squared)   # [0, 4, 16, 36, 64]

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

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

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

*args и **kwargs

Иногда нельзя предсказать, сколько аргументов получит функция. Чтобы обработать их, используются специальные конструкции *args и **kwargs.

На самом деле, названия переменных – args, kwargs – это просто соглашение. Важны здесь только звездочки. Они обозначают сборку аргументов в коллекцию (список или словарь). Одна звездочка предназначена для обычных аргументов, две – для именованных.

Можно заменить *args на *vars, а **kwargs на **options или другое слово. Программа будет работать так, как ожидается. Однако, другие разработчики могут вас не понять.

def test_args(farg, *args):
    print("Первый известный аргумент: ", farg)
    for arg in args:
        print("Один из оставшихся аргументов: ", arg)

test_args(1, "two", 3)
# Первый известный аргумент: 1
# Один из оставшихся аргументов: two
# Один из оставшихся аргументов: 3

def test_kwargs(farg, **kwargs):
    print("Первый известный аргумент: ", farg)
    for key in kwargs:
        print("Один из оставшихся аргументов: %s: %s" % (key, kwargs[key]))

test_kwargs(farg=1, myarg2="two", myarg3=3)
# formal arg: 1
# Один из оставшихся аргументов: myarg2: two
# Один из оставшихся аргументов: myarg3: 3

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

Декораторы

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

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

Декораторы в Python – это, по сути, синтаксический сахар. Для их обозначения используется символ @.

Декораторы в Python

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

def bread(func):
  def wrapper():
      print "</''''''\>"
      func()
      print "<\______/>"
  return wrapper

def vegetables(func):
  def wrapper():
      print "#помидорка#"
      func()
      print "~лист салата~"
  return wrapper

def sandwich(food="--ветчина--"):
  print food

sandwich = bread(vegetables(sandwich))
sandwich()

#</''''''\>
# #помидорка#
# --ветчина--
# ~лист салата~
#<\______/>

Изначально функция sandwich только печатает начинку, а затем она становится полноценным бутербродом. То же самое можно сделать чуть проще:

@bread
@vegetables
def sandwich(food="--ветчина--"):
  print food

sandwich()

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

В Python есть несколько встроенных декораторов, например, @classmethod, @staticmethod, @property.

Исключения

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

В Питоне определен главный класс BaseException, от которого наследуются все остальные классы ошибок. У него есть четыре прямых наследника:

  • SystemExit – произошел выход из программы.
  • KeyboardInterrupt – пользователь прервал выполнение программы (комбинация Ctrl+C).
  • GeneratorExit – завершена работа объекта generator.
  • Exception – родительский класс для пользовательских исключений.

python exceptions

От класса Exception наследуется больше десятка различных ошибок, которые может обработать программист. Вот лишь некоторые из них:

  • IOError – ошибка ввода-вывода, например, "файл не найден".
  • ImportError – ошибка импорта модуля.
  • IndexError – обращение к несуществующему индексу последовательности.
  • OSError – ошибка системы.
  • SyntaxError – синтаксическая ошибка.
  • TypeError – ошибка типа данных, например, функция вызывается с неподходящим по типу аргументом.
  • ZeroDivisionError – деление на ноль.

Чтобы поймать и обработать исключения, нужно использовать конструкцию try – except – finally.

try:
    k = 1 / 0
except ZeroDivisionError:
    k = 0

print(k)

# 0

В блоке try размещается код, который должен быть выполнен. Блок except дает возможность поймать и обработать нужные ошибки. При необходимости можно добавить еще инструкцию finally, которая выполнится в любом случае.

ООП

В Python изначально заложена поддержка ООП, метаклассов, наследования, включая множественное, и инкапсуляции.

Пример наследования в Python:

class Animal(object):
    def __init__(self, name):
        self.name = name

    def say(self):
        print(self.name + " хочет что-то сказать")

    def swim(self):
        print(self.name + " подходит к воде")


class Cat(Animal):
    def say(self):
        super(Cat, self).say()
        print(self.name + " говорит Мяу")

    def swim(self):
        super(Cat, self).swim()
        print(self.name + " боится воды")


class Dog(Animal):
    def say(self):
        super(Dog, self).say()
        print(self.name + " говорит Гав")

    def swim(self):
        super(Dog, self).swim()
        print(self.name + " плывет по-собачьи")


class CatDog(Cat,Dog):
    swim = Dog.swim

    def say(self):
        super(CatDog, self).say()

cat = Cat("Кот")
dog = Dog("Пес")
catDog = CatDog("КотоПес")

cat.say()
# Кот хочет что-то сказать
# Кот говорит Мяу

dog.say()
# Пес хочет что-то сказать
# Пес говорит Гав

catDog.say()
# КотоПес хочет что-то сказать
# КотоПес говорит Гав
# КотоПес говорит Мяу

cat.swim()
# Кот подходит к воде
# Кот боится воды

dog.swim()
# Пес подходит к воде
# Пес плывет по-собачьи

catDog.swim()
# КотоПес подходит к воде
# КотоПес плывет по-собачьи

Наследование в Python

В коде определяется родительский класс Animal с базовой реализацией методов say() и swim(). У Animal есть два наследника: Cat и Dog.

Дочерние классы дополняют методы родителя собственным поведением. Чтобы сделать это, им приходится вызывать метод суперкласса с помощью функции super().

Класс CatDog демонстрирует пример множественного наследования в Python. Он берет лучшее от обоих родителей: плавает как Dog и говорит на двух языках.

5 вопросов о Python-технологиях

Потоки

Вопрос о потоках – один из самых животрепещущих в сообществе. Поэтому вполне можно ожидать, что он прозвучит на собеседовании по Python.

Работа нескольких потоков иногда заканчивается конфликтом. Чтобы защититься от этого, CPython использует технологию Global Interpreter Lock.

Глобальный блокировщик следит за тем, чтобы активен был всегда только один поток. По сути, он просто запрещает параллельность. Хотя такой подход очень упрощает работу, он фактически убирает все преимущества многопоточной модели. Например, нельзя ускорить программу, разделив один поток на несколько. Python-сообщество неоднократно просило убрать GIL, однако, создатель языка решил оставить все как есть.

Многопоточность в Python

Впрочем, иногда потоки нужны. Например, на них можно переложить загрузку файлов с сервера. Для этого в Python используется модуль threading.

Код ниже демонстрирует добавление функции clock в поток.

import threading
import time
def clock(interval):
    while True:
        print("The time is %s" % time.ctime())
        time.sleep(interval)
t = threading.Thread(target=clock, args=(15,))
t.daemon = True
t.start()

Асинхронность

Асинхронность – еще один способ выполнения нескольких задач сразу. Она предлагает решать проблему с помощью функций обратного вызова (callback).

Встретив в коде блокирующий запрос, интерпретатор вешает на него сигнальный маячок и идет дальше. Когда запрос завершится, маячок подаст сигнал. В этот момент обработчик вернется, получит результат и вызовет коллбэк.

В Python есть несколько асинхронных библиотек. Самые популярные из них – стандартная AsyncIO и Tornado.

В последних версиях языка появились новые синтаксические конструкции async и await.

Тестирование

В Python есть стандартный модуль unittest. Он позволяет объединять тесты в группы, настраивать и автоматизировать их. Дополнение Mock дает возможность использовать mock-объекты, что облегчает тестирование.

Отладка

Python-код можно и нужно отлаживать. Для этого в языке есть специальный интерактивный дебаггер pdb.

Расширения на C/C++

Интерпретатор CPython позволяет внедрять в программы расширения, которые написаны на C и C++. Разработчик может оптимизировать код и пользоваться библиотеками языка C. Кроме того, можно управлять ресурсами на низком уровне.

Другие вопросы

Список из 15 вопросов по Python не является исчерпывающим.

Многое зависит от специфики компании, которая проводит интервью. Где-то требуется знание популярных фреймворков, например, Django. В другом месте важно понимать основы взаимодействия с базами данных.

Тем не менее вопросы охватывают большую часть знаний, которые нужны разработчику. Разобравшись в них, вы улучшите свои шансы на успех.

Вероятно, вам предложат задачи по Python. Чтобы решить их, нужна не только хорошая теоретическая подготовка, но и практика. Начните с простых упражнений и постепенно поднимайте свой уровень.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Go-разработчик
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ