Как стать автором
Обновить

Эффективные и не эффективные методы кодинга на Python

Время на прочтение7 мин
Количество просмотров28K
Привет, Хабр! Предлагаю Вашему вниманию перевод статьи 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.

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

Спасибо за чтение.
Теги:
Хабы:
Всего голосов 31: ↑25 и ↓6+28
Комментарии18

Публикации

Истории

Работа

Data Scientist
71 вакансия
Python разработчик
112 вакансий

Ближайшие события

22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань