Избегайте этих нелепых ошибок при работе с Python

Чем больше вы практикуетесь, тем легче программирование становится для вас, но суть не меняется:

программирование — это сложно.

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

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

NotImplemented

Хотя ошибка NotImplemented является одной из самых распространённых, я считаю важным напомнить о ней в очередной раз. Выброс NotImplemented в Python не приводит к выбросу ошибки NotImplemented, вместо этого выбрасывается TypeError. Вот пример:

def implementtest(num):
    if num == 5:
        raise(NotImplemented)

При любой попытке запустить функцию, где “num” равен 5, произойдёт следующее:

Для выброса корректного исключения стоить выбрасывать NotImplementedError вместо NotImplemented. Отредактируем функцию:

def implementtest(num):
    if num == 5:
        raise(NotImplemented)
    if num == 10:
        raise(NotImplementedError('This is the right way!'))

Выполнение этой функции даст нужный результат:

Изменяемые значения по умолчанию 

(Каюсь, и я допускал эту ошибку).

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

def add(item, items=[]):
    items.append(item)

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

def add(item, items=None):
    if items is None:
        items = []
    items.append(item)

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

Глобальные переменные

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

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

Копирование!

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

d = 5
h = d

Существуют два основных типа копирования, осуществляемых с помощью модуля копирования Python:

поверхностное копирование и глубокое копирование.

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

Поверхностное копирование создаёт новый составной объект и затем (насколько это возможно) вставляет в него ссылки на объекты, найденные в исходном. Глубокое копирование создаёт новый составной объект и затем рекурсивно вставляет внутрь него копии объектов из исходного. Учитывая эти определения легко понять, почему к тому или иному типу данных больше подходит тот или иной вариант. 

import copy
l = [10,15,20]
h = 5
hcopy = copy.copy(h)
z = copy.deepcopy(l)

Чтобы протестировать наши результаты, просто проверим, является ли id переменных тем же самым, что и в условном выражении:

print id(h) == id(hcopy) 
False

Заключение

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

Читайте также:


Перевод статьи Emmett Boudreau: Avoid These Rookie Python Mistakes