5 Хитростей Python, которые отличают Senior-разработчика от Junior

Каждый год, начиная с 2015 года, первого декабря стартует Advent of Code. Как описано на их веб-сайте, Advent of Code (далее AoC) – это

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

В этой статье мы рассмотрим пять подходов к решению распространённых задач кодинга Senior-способами, а не Junior. Каждая задача является производной от головоломки AoC, причём многие из них многократно повторяются на протяжении AoC и других задач кодинга и задач, с которыми вы можете столкнуться, например, на собеседованиях при приёме на работу.

Чтобы проиллюстрировать концепции, я не буду вдаваться вполные решение головоломок AoC, а, скорее, сосредоточусь только на небольшой части конкретной головоломки, в которой Сениоров легко отличить от Джуниоров.

https://t.me/python_job_interview – разбор практических задач на Python.

1. Эффективное считывание в файле со списковым включением и split

На первый день AoC требуется прочитать некоторое количество блоков чисел. Каждый блок разделён пустой строкой (таким образом, фактически ‘\n’).

Входные данные и желаемый результат

# INPUT
10
20
30

50
60
70

# DESIRED OUTPUT
[[10, 20, 30], [50, 60 70]]

Подход Junior-разработчика: цикл с операторами if-else

numbers = []
with open("file.txt") as f:
  group = []
  for line in f:
    if line == "\n":
      numbers.append(group)
      group = []
    else:
      group.append(int(line.rstrip()))
  # append the last group because if line == "\n" will not be True for
  # the last group
  numbers.append(group)

Подход Senior-разработчика: использование спискового включения и .split()

with open("file.txt") as f:
  nums = [list(map(int, (line.split()))) for line in f.read().rstrip().split("\n\n")]

Используя списковое включение, мы можем объединить девять предыдущих строк в одну, не теряя при этом понятности или удобочитаемости и при этом повышая производительность (списковое включение происходит быстрее, чем обычные циклы). Для тех, кто раньше не видел map, map сопоставляет функцию (первый аргумент) с итерацией во втором аргументе. В этой конкретной ситуации он применяет int() к каждому значению в списке, превращая каждый элемент в целое число. Для получения дополнительной информации о карте нажмите здесь.

2. Использование Enum вместо if-elif-else

На второй день задача вращается вокруг игры в камень-ножницы-бумага. Любая выбранная форма (камень, бумага или ножницы) приводит к различному количеству очков: 1 (X), 2 (Y) и 3 (Z) соответственно. Ниже приведены два подхода к решению этой проблемы.

Входные данные и желаемый результат

# Входные данные
X
Y
Z

# желаемый результат
1
2
3

Подход Junior-разработчика: if-elif-else

def points_per_shape(shape: str) -> int:
  if shape == 'X':
    return 1
  elif shape == 'Y':
    return 2
  elif shape == 'Z':
    return 3
  else:
    raise ValueError('Invalid shape')

Подход Senior-разработчика: Enum

from enum import Enum

class ShapePoints(Enum):
  X = 1
  Y = 2
  Z = 3

def points_per_shape(shape: str) -> int:
  return ShapePoints[shape].value

Конечно, в этом примере if else не так уж и ужасен, но использование Enum приводит к более короткому и удобочитаемому коду. Особенно, когда есть много вариантов, алгоритм if-elif-else будет становиться всё хуже и хуже, в то время как с Enum остаётся относительно легко. Для получения дополнительной информации о Enum читайте здесь.

3. Использование таблиц поиска вместо словарей

На третий день задача с буквами, которые имеют разные значения. Строчные буквы a-z имели значения от 1 до 26, а прописные буквы a-z от 27 до 52. Из-за множества различных возможных значений использование Enum, подобного приведённому выше, привело бы к большому количеству строк кода. Более практичным подходом здесь является использование метода .index():

# INPUT
c
Z
a
...

# DESIRED OUPUT
3
52
1
...

Подход Junior-разработчика: создание глобального словаря

letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
letter_dict = dict()
for value, letter in enumerate(letters, start=1):
  letter_dict[letter] = value

def letter_value(ltr: str) -> int:
  return letter_dict[ltr]

Подход Senior-разработчика: использование строки в качестве таблицы поиска

def letter_value(ltr: str) -> int
  return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.index(ltr) + 1

Используя метод .index() для строки, мы получаем индекс, следовательно, letters.index('c')+1 приведёт к ожидаемому значению 3. Нет необходимости хранить значения в словаре, потому что индекс – это значение. Чтобы избежать проблемы с +1, вы могли бы просто добавить пробел в начале строки, чтобы индекс a начинался с 1. Однако это зависит от того, хотите ли вы вернуть значение 0 для пробела или нет.

Как вы, возможно, уже сами догадались, да, мы также могли бы решить задачу “камень, ножницы, бумага”, используя таблицу поиска:

def points_per_shape(shape: str) -> int:
  return 'XYZ'.index(shape) + 1

4. Усовершенствованные срезы

На 5-й день вас ждет задача , в которой нужно прочитать буквы из строк (см. Ввод ниже). Каждая буква находится в индексе, начиная с индекса 1. Практически каждый программист на Python знаком с разделением строк и списков, используя, например, list_[10:20]. Но чего многие не знают, так это того, что вы можете определить размер шага, используя, например, list_[10:20: 2], чтобы определить размер шага 2.

# INPUT
    [D]    
[N] [C]    
[Z] [M] [P]

# DESIRED OUTPUT
[' D ', 'NC', 'ZMP']

Подход Junior-разработчика: двойной цикл for с диапазоном range и индексами

letters = []
with open('input.txt') as f:
  for line in f:
    row = ''
    for index in range(1, len(line), 4):
      row += line[index]
    letters.append(row)

Подход Senior-разработчика: использование продвинутых методов среза

with open('input.txt') as f:
  letters = [line[1::4] for line in f]

5. Использование атрибутов класса для хранения экземпляров класса

На одиннадцатый день ваш ждет задача, в которой обезьяны передают предметы друг другу. Для упрощения мы представим, что они просто передают бананы друг другу. Каждая обезьяна может быть представлена как экземпляр Python с id и количеством бананов в качестве атрибутов экземпляра. Однако обезьян много, и они должны уметь взаимодействовать друг с другом. Хитрость для хранения всех обезьян и для того, чтобы они могли взаимодействовать друг с другом, заключается в определении словаря со всеми экземплярами Monkey в качестве атрибута класса Monkey. Используя Monkey.monkeys[id], вы можете получить доступ ко всем существующим обезьянам без использования класса Monkeys или внешнего словаря:

class Monkey:
  monkeys: dict = dict()

  def __init__(self, id: int):
      self.id = id
      self.bananas = 3
      Monkey.monkeys[id] = self
  
  def pass_banana(self, to_id: int):
      Monkey.monkeys[to_id].bananas += 1
      self.bananas -= 1

Monkey(1)
Monkey(2)
Monkey.monkeys[1].pass_banana(to_id=2)

print(Monkey.monkeys[1].bananas)
2

print(Monkey.monkeys[2].bananas)
4

Самодокументируемые выражения (БОНУС)

Этот трюк применим практически каждый раз, когда вы пишете программу на Python. Вместо определения в f-строке того, что вы печатаете (например
, print(f"x = {x}"), вы можете использовать print(f"{x = }”) для печати значения со спецификацией того, что вы печатаете.

# INPUT
x = 10 * 2
y = 3 * 7

max(x,y)

# DESIRED OUTPUT
x = 20
y = 21

max(x,y) = 21

Подход Junior-разработчика:

print(f"x = {x}")
print(f"y = {y}")

print(f"max(x,y) = {max(x,y)}")

Подход Senior-разработчика:

print(f"{x = }")
print(f"{y = }")

print(f"{max(x,y) = }")

Заключение

Мы рассмотрели 5 приёмов Python, которые отличают Сениоров от Джуниоров. Конечно, только применение этих приёмов не сделает вас Сениором . Однако, проанализировав разницу в стиле и шаблонах между ними, вы можете узнать разницу в том, как оптытный разработчик подходит к проблемам кодинга по сравнению с Джуниором, и вы можете начать изучать эти подходы, чтобы в конечном итоге самому стать Senior-разработчиком!

+1
2
+1
5
+1
3
+1
1
+1
3

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *