Привет, Хабр! Предлагаю Вашему вниманию перевод статьи Good and Bad Practices of Coding in Python автора Duomly.

Python – высокоуровневый язык программирования, акцентирующий внимание на удобочитаемости. Он разрабатывается, поддерживается и часто используется в соответствии с The Zen of Python или PEP 20.

В этой статье показано несколько примеров хороших и плохих методов кодинга в Python, с которыми вы, вероятно, столкнетесь.

Использование распаковки (Unpacking) для написания компактного кода


Упаковка и распаковка — мощные инструменты Python. Вы можете использовать распаковку для присваивания значений переменным:

>>> a, b = 2, 'my-string'
>>> a
2
>>> b
'my-string'

Вы можете использовать это для написания, возможно, самого компактного кода, меняющего значения переменных местами

>>> a, b = b, a
>>> a
'my-string'
>>> b
2

Это потрясающе!

Распаковка также может быть использована для присвоения значения нескольким переменным в более сложных случаях. Например, Вы можете написать так:

>>> x = (1, 2, 4, 8, 16)
>>> a = x[0]
>>> b = x[1]
>>> c = x[2]
>>> d = x[3]
>>> e = x[4]
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

Но вместо этого Вы также можете воспользоваться более лаконичным и, возможно, более читабельным способом:

>>> a, b, c, d, e = x
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

Это круто, правда? Но можно написать еще круче:

>>> a, *y, e = x
>>> a, e, y
(1, 16, [2, 4, 8])

Фишка в том, что переменная с * собирает значения, не назначенные другим переменным.

Использование ��епочки (Chaining) для написания компактного кода


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

>>> x = 4
>>> x >= 2 and x <= 8
True

Вместо этого Вы можете использовать более компактную форму написания:

>>> 2 <= x <= 8
True
>>> 2 <= x <= 3
False

Python также поддерживает присваивание значений переменным в виде цепочки. Итак, если Вы хотите присвоить одно и то же значение нескольким переменным одновременно, Вы можете сделать это простым способом:

>>> x = 2
>>> y = 2
>>> z = 2

Более компактный способ — использовать распаковку:

>>> x, y, z = 2, 2, 2

Тем не менее, все выглядит ещё круче, если использовать присвоение значения цепочкой:

>>> x = y = z = 2
>>> x, y, z
(2, 2, 2)

Будьте осторожны, когда значения разные! Все переменные ссылаются на одно и то же значение.

Проверка на None


None не является уникальным объектом в Python. Он имеет аналоги, например, null в C-подобных языках.

Можно проверить, ссылается ли переменная на None с помощью операторов сравнения == и !=:

>>> x, y = 2, None
>>> x == None
False
>>> y == None
True
>>> x != None
True
>>> y != None
False

Однако, предпочтительнее использование is и is not:

>>> x is None
False
>>> y is None
True
>>> x is not None
True
>>> y is not None
False

Кроме того, лучше использовать конструкцию x is not None, а не менее читабельную альтернативу (x is None).

Перебор последовательностей (Sequences) и отображений (Mappings)


Вы можете реализовать циклы в Python несколькими способами. Python предлагает несколько встроенных классов для упрощения их реализации.

Почти всегда Вы можете использовать диапазон, чтобы получить цикл, который выдает целые числа:

>>> x = [1, 2, 4, 8, 16]
>>> for i in range(len(x)):
...     print(x[i])
... 
1
2
4
8
16

Однако для этого есть лучший способ:

>>> for item in x:
...     print(item)
... 
1
2
4
8
16

Но что, если Вы хотите запустить цикл в обратном порядке? Конечно, снова можно использовать диапазон:

>>> for i in range(len(x)-1, -1, -1):
...     print(x[i])
... 
16
8
4
2
1

Но «перевернуть» последовательность – более компактный способ:

>>> for item in x[::-1]:
...     print(item)
... 
16
8
4
2
1

Pythonic способ заключается в использовании обратной последовательности, чтобы получить цикл, который будет возвращать элементы последовательности в обратном порядке:

>>> for item in reversed(x):
...     print(item)
... 
16
8
4
2
1

Иногда необходимы как элементы последовательности, так и соответствующие им индексы:

>>> for i in range(len(x)):
...     print(i, x[i])
... 
0 1
1 2
2 4
3 8
4 16

Лучше использовать перечисление, чтобы получить другой цикл, который возвращает значения индексов и элементов:

>>> for i, item in enumerate(x):
...     print(i, item)
... 
0 1
1 2
2 4
3 8
4 16

Это круто. Но что, если необходимо перебрать две или более последовательности? Конечно, можно снова использовать диапазон:

>>> y = 'abcde'
>>> for i in range(len(x)):
...     print(x[i], y[i])
... 
1 a
2 b
4 c
8 d
16 e

В этом случае Python также предлагает лучший способ решения. Вы можете применить zip:

>>> for item in zip(x, y):
...     print(item)
... 
(1, 'a')
(2, 'b')
(4, 'c')
(8, 'd')
(16, 'e')

Можно также скомбинировать этот метод с распаковкой:

>>> for x_item, y_item in zip(x, y):
...     print(x_item, y_item)
... 
1 a
2 b
4 c
8 d
16 e

Пожалуйста, имейте в виду, что диапазон может быть весьма полезным. Тем не менее, есть случаи (как, например, выше), где оптимальнее использовать альтернативы.
Перебор словаря дает его ключи:

>>> z = {'a': 0, 'b': 1}
>>> for k in z:
... print(k, z[k])
... 
a 0
b 1

Однако вы можете применить метод .items () и получить ключи и соответствующие им значения:

>>> for k, v in z.items():
...     print(k, v)
... 
a 0
b 1

Можно также использовать методы .keys () и .values ​​() для перебора ключей и значений соответственно.

Сравнение с нулем


Когда у Вас есть числовые данные, и нужно проверить, равны ли числа нулю, можно (но не всегда нужно) использовать операторы сравнения == и !=:

>>> x = (1, 2, 0, 3, 0, 4)
>>> for item in x:
...     if item != 0:
...         print(item)
... 
1
2
3
4

Способ, предлагаемый Python, состоит в том, чтобы интерпретировать нуль как False, а все другие числа как True:

>>> bool(0)
False
>>> bool(-1), bool(1), bool(20), bool(28.4)
(True, True, True, True)

Имея это в виду, Вы можете использовать if item вместо if item! = 0:

>>> for item in x:
...     if item:
...         print(item)
... 
1
2
3
4

Вы можете следовать той же логике и использовать if not item вместо if item == 0.

Избегание изменяемых необязательных аргументов


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

>>> def f(value, seq=[]):
...     seq.append(value)
...     return seq

На первый взгляд это выглядит так, если вы не укажете seq, f () добавляет value в пустой список и возвращает что-то вроде [value]:

>>> f(value=2)
[2]

Выгля��ит здорово, правда? Нет! Рассмотрим следующие примеры:

>>> f(value=4)
[2, 4]
>>> f(value=8)
[2, 4, 8]
>>> f(value=16)
[2, 4, 8, 16]

Удивлены? Если да, то не Вы одни.

Кажется, что каждый раз, когда вызывается функция, предоставляется один и тот же экземпляр необязательного аргумента (в данном случае, списка). Может быть, иногда это и будет то, что Вам нужно. Тем не менее, гораздо более вероятно, что Вам необходимо будет избежать это. Один из способов заключается в следующем:

>>> def f(value, seq=None):
...     if seq is None:
...         seq = []
...     seq.append(value)
...     return seq

Версия покомпактней:

>>> def f(value, seq=None):
...     if not seq:
...         seq = []
...     seq.append(value)
...     return seq

Теперь мы получаем другой вывод:

>>> f(value=2)
[2]
>>> f(value=4)
[4]
>>> f(value=8)
[8]
>>> f(value=16)
[16]

В большинстве случаев это то, что нужно.

Избегание классических Getter и Setter


Python позволяет определять методы getter и setter так же, как C ++ и Java:

>>> class C:
...     def get_x(self):
...         return self.__x
...     def set_x(self, value):
...         self.__x = value

Вот как Вы можете использовать их:

>>> c = C()
>>> c.set_x(2)
>>> c.get_x()
2

В некоторых случаях это лучший способ. Однако часто лучше использовать свойства, особенно в простых случаях:

>>> class C:
...     @property
...     def x(self):
...         return self.__x
...     @x.setter
...     def x(self, value):
...         self.__x = value

Свойства считаются более Pythonic, чем классические геттеры и сеттеры. Вы можете использовать их так же, как в C#, то есть так же, как обычные атрибуты данных:

>>> c = C()
>>> c.x = 2
>>> c.x
2

Так что, в общем, лучше использовать свойства, когда это возможно.

Избегание доступа к защищенным членам класса


В Python отсутствуют как таковые private члены класса. Однако, если в начале названия элемента написать (_), то доступ к его изменению за пределами класса будет запрещен.

Например, рассмотрим код:

>>> class C:
...     def __init__(self, *args):
...         self.x, self._y, self.__z = args
... 
>>> c = C(1, 2, 4)

Экземпляры класса C имеют три элемента данных: .x, .y и ._Cz. Если имя участника начинается с двойного подчеркивания (dunder), оно становится измененным. Вот почему вместо ._z элемент ._Cz.

Теперь можно доступ или изменить напрямую .x:

>>> c.x  # OK
1

Вы также можете получить доступ или изменить ._y вне его класса, но это считается моветоном:

>>> c._y  # Possible, but a bad practice!
2

Вы не можете получить доступ к .z, потому что переменная изменена, но вы можете получить доступ или изменить ._Cz:

>>> c.__z # Error!
Traceback (most recent call last):
File "", line 1, in 
AttributeError: 'C' object has no attribute '__z'
>>> c._C__z # Possible, but even worse!
4
>>>

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

Использование контекстных менеджеров (Context Managers) для освобождения ресурсов


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

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file`

Чтобы правильно управлять памятью, Вам необходимо закрыть этот файл после завершения работы:

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file and`
>>> my_file.close()

Делать это таким способом лучше, чем не делать вовсе. Но что, если во время обработки Вашего файла возникает исключение? Тогда my_file.close () никогда не выполняется. Можно справиться с этим с помощью обработки исключений или с помощью контекстных менеджеров. Второй способ означает, что вы помещаете свой код в блок with:

>>> with open('filename.csv', 'w') as my_file:
...     # do something with `my_file`

Использование блока with означает, что специальные методы .enter () и .exit () вызываются даже в случаях исключений. Эти методы должны заботиться о ресурсах.
Вы можете добиться особенно надежных конструкций, комбинируя контекстные менеджеры и обработку исключений.

Стилистические советы


Код Python должен быть элегантным, лаконичным и читабельным.

Основной ресурс на тему того, как написать красивый код Python, — это Style Guide for Python Code или PEP 8. Вам стоит прочитать его, если хотите писать код на Python.

Выводы


Эта статья дает несколько советов о том, как написать более компактный, читабельный код. Короче говоря, она показывает, как сделать код более Pythonic. Кроме того, PEP 8 предоставляет Style Guide for Python Code, а PEP 20 представляет принципы языка Python.

Наслаждайтесь написанием полезного и красивого кода!

Спасибо за чтение.