Эта статья изначально была опубликована по адресу: https://www.blog.duomly.com/good-and-bad-practices-of-coding-in-python/

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

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

Использование распаковки для написания сжатого кода

Упаковка и распаковка - мощные возможности 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])

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

Использование цепочки для написания краткого кода

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 не является особым и уникальным объектом в Python. Он имеет аналогичную цель, как null в C-подобных языках.

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

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

Тем не менее, более питонический и желательный способ использования:

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

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

Итерации по последовательностям и сопоставлениям

Вы можете реализовать итерации и циклы for в 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

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

>>> 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

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

>>> 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

Метод Pythonic заключается в использовании того факта, что ноль интерпретируется как 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 () добавляет значение в пустой список и возвращает что-то вроде [значение]:

>>> 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]

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

Избегайте классических геттеров и сеттеров

Python позволяет определять методы получения и установки аналогично 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

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

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

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

Предотвращение доступа к членам защищенного класса

У Python нет настоящих частных членов класса. Однако существует соглашение, согласно которому вы не должны получать доступ или изменять элементы, начинающиеся с символа подчеркивания (_), за пределами их экземпляров. Им не гарантируется сохранение существующего поведения.

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

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

Экземпляры класса C имеют три члена данных: .x, ._y и ._C__z. Если имя участника начинается с двойного подчеркивания (dunder), оно искажается, то есть изменяется. Вот почему у вас ._C__z вместо .__ z.
Теперь можно получить доступ или изменить .x напрямую:

>>> c.x  # OK
1

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

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

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

>>> 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
>>>

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

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

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

>>> 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 open('filename.csv', 'w') as my_file:
...     # do something with `my_file`

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

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

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

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

Выводы

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

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

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

Статью подготовил наш одноклубник Мирко.