Оригинал
1. Lint
Запустите pylint в директории с вашим кодом.
Скачать конфиг можно здесь: pylintrc
1.1. Зачем нужны линтеры
pylint - это инструмент для поиска ошибок и проблем со стилем в исходном коде Python. Он находит проблемы, которые в других языках обнаруживаются компилятором. Из-за того, что Python интерпретируемый, а не компилируемый язык некоторые предупреждения могут быть неверными; однако ложные предупреждения встречаются довольно редко.
1.2. Плюсы использования линтеров
Линтеры выявляют ошибки, которые легко пропустить, например: опечатки, использование переменных до объявления и т.д.
Важно!
pylintне идеален. Для эффективного и правильного использования этого инструмента, иногда нужно что-то изменить, например: отключить предупреждения.
1.3. Отключение предупреждений
Убедитесь, что вы запустили pylint в своем проекте.
Отключайте предупреждения, только если они неуместны, чтобы не скрыть другие проблемы. Для подавления предупреждений вы можете задать комментарий на уровне строки:
def do_PUT(self): # Имя WSGI, поэтому pylint: disable=invalid-name
...каждое предупреждение pylint идентифицируется символическим именем (“empty-docstring”)
Предупреждения, относящиеся к Google, начинаются с g-.
Если из символического названия не ясна причина игнорирования предупреждения, добавьте пояснение. Преимущество игнорирования этим методом в том, что мы можем легко искать и повторно просматривать их.
Вы можете получить список предупреждений pylint, выполнив следующие действия:
pylint --list-msgsЧтобы получить дополнительную информацию о конкретном сообщении, используйте:
pylint --help-msg=invalid-nameПредупреждение
Предпочтите “pylint: disable” устаревшей форме “pylint: disable-msg”.
Предупреждения о неиспользуемых аргументах функции можно устранить, удалив переменные в начале функции. Всегда добавляйте комментарий, объясняющий, почему вы их удаляете. ”Неиспользованный” - этого достаточно. Например:
def viking_cafe_order(spam: str, beans: str, eggs: str | None = Нет) -> str:
del beans, eggs # Причина удаления переменных(почему они есть, но не используются)
return spam * 3Другие распространенные способы подавления этого предупреждения включают использование _ в качестве идентификатора для неиспользуемого аргумента или добавление к имени аргумента префикса unused_ или присвоение им значения _.
Эти способы разрешены, но являются устаревшими, т.к. это прерывающие вызовы, передающие аргументы по имени и не обеспечивающие не использование аргументов.
2. Импорты
Важно
Используйте операторы
importтолько для пакетов и модулей, а не для отдельных типов, классов или функций.
2.1. Пространства имен
Соглашение об управлении пространством имен простое. Источник каждого идентификатора указывается последовательным образом; x.Obj означает, что объект Obj определен в модуле x.
2.2. Возникающие проблемы
Имена модулей могут конфликтовать. Некоторые имена модулей неудобно длинные.
2.3. Рекомендации
- Используйте
import xдля импорта пакетов и модулей. - Используйте
from x import y, гдеx— префикс пакета, аy— имя модуля без префикса. - Используйте
from x import y as zв любом из следующих случаев:- Необходимо импортировать два модуля с именем
y. yконфликтует с именем верхнего уровня, определенным в текущем модуле.yконфликтует с общим именем параметра, которое является частью публичного API (например,features).y— неудобное длинное имя.yслишком общее в контексте вашего кода (например,from storage.file_system import options as fs_options).
- Необходимо импортировать два модуля с именем
- Используйте
import y as zтолько в том случае, еслиzявляется стандартной аббревиатурой (например,import numpy as np).
Например, модуль sound.effects.echo можно импортировать следующим образом:
from sound.effects import echo...
echo.EchoFilter(input, output, delay=0.7, atten=4)Важно!
Не используйте относительные имена в импортах. Даже если модуль находится в том же пакете, используйте полное имя пакета. Это поможет избежать непреднамеренного импорта пакета дважды.
2.3.1. Исключения
Исключения из этого правила:
- Символы из следующих модулей используются для поддержки статического анализа и проверки типов:
- [[2. Рекомендации по форматированию#1812-импорты-для-типизации|модуль
typing]] - [[2. Рекомендации по форматированию#1812-импорты-для-типизации|модуль
collections.abc]] - модуль
typing_extensions
- [[2. Рекомендации по форматированию#1812-импорты-для-типизации|модуль
- Редиректы из модуля six.moves
3. Пакеты
Важно!
Импортируйте каждый модуль, используя полный путь к его расположению.
Это позволяет избежать конфликтов в именах модулей или некорректного импорта из-за того, что путь поиска модулей не соответствует ожиданиям автора, а также упрощает поиск модулей.
Но также это усложняет развертывание кода, поскольку требует воспроизведения иерархии пакетов. В современных механизмах развертывания это не является серьезной проблемой.
Весь новый код должен импортировать каждый модуль по его полному имени пакета. Пример:
# Так мы делаем:
# Ссылка на absl.flags в коде с полным именем (подробно).
import absl.flags
from doctor.who import jodie
_FOO = absl.flags.DEFINE_string(...)
# Ссылайтесь на флаги в коде, используя только название модуля (обычно).
from absl import flags
from doctor.who import jodie
_FOO = flags.DEFINE_string(...)(теперь предположим, что этот файл находится в папке doctor/who/, где также находится файл jodie.py)
# Так мы не делаем:
# Неясно, какой модуль хотел автор и что будет импортировано. Фактическое
# поведение импорта зависит от внешних факторов, контролирующих sys.path.
# Какой из возможных модулей jodie автор намеревался импортировать?
import jodieНе следует предполагать, что каталог, в котором находится основной бинарный файл, находится в sys.path, несмотря на то, что в некоторых средах это так и происходит. В этом случае код должен предполагать, что import jodie относится к стороннему или верхнему уровню пакета с именем jodie, а не к локальному jodie.py.
4. Исключения
Info
Исключения допускаются, но должны использоваться с осторожностью.
Исключения - средство выхода из нормального потока управления для обработки ошибок или других исключительных условий.
Поток управления нормальным кодом операций не загроможден кодом обработки ошибок. Он также позволяет потоку управления пропускать несколько фреймов при возникновении определенного условия, например, возвращаться из N вложенных функций за один шаг, вместо того чтобы проходить все коды ошибок.
Но, это может привести к запутанности потока управления. Легко пропустить ошибки при вызове библиотек.
Поэтому исключения должны соответствовать определенным условиям:
- Используйте встроенные классы исключений, когда это целесообразно. Например, вызывайте исключение
ValueError, чтобы указать на ошибку программирования, такую как нарушение предварительного условия, которое может произойти при проверке аргументов функции. - Не используйте операторы
assertвместо условий или проверки предварительных условий. Они не должны быть критически важными для логики приложения. Решающим критерием будет то, чтоassertможно удалить, не нарушая работу кода. Оценка условийassertне гарантируется. Для тестов на основе pytestassertдопустим и ожидаем для проверки ожиданий.
Например:
# Так мы делаем
def connect_to_next_port(self, minimum: int) -> int:
"""Подключается к следующему доступному порту.
Args:
minimum: значение порта, большее или равное 1024.
Returns:
Новый минимальный порт.
Raises:
ConnectionError: если не найден доступный порт.
"""
if minimum < 1024:
# Обратите внимание, что это возникновение ValueError не упоминается в разделе «Raises:» документации,
# поскольку нецелесообразно гарантировать
# такую конкретную реакцию на неправомерное использование API..
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if port is None:
raise ConnectionError(f'Could not connect to service on port {minimum} or higher.')
# Код не зависит от результата этого утверждения.
assert port >= minimum, (f'Unexpected port {port} when minimum was {minimum}.')
return port# Так мы не делаем
def connect_to_next_port(self, minimum: int) -> int:
"""Подключается к следующему доступному порту.
Args:
minimum: значение порта, большее или равное 1024.
Returns:
Новый минимальный порт.
"""
assert minimum >= 1024, 'Minimum port must be at least 1024.'
# Следующий код зависит от предыдущего утверждения.
port = self._find_next_open_port(minimum)
assert port is not None
# Проверка типа оператора return зависит от утверждения assert.
return port- Библиотеки или пакеты могут определять свои собственные исключения. При этом они должны наследовать базовый класс исключений. Имена исключений должны заканчиваться на
Errorи не должны содержать повторений (foo.FooError). - Никогда не используйте операторы catch-all
except:или catchExceptionилиStandardError, если только вы не:- повторно не вызываете исключение
- не создаете точку изоляции в программе, где исключения не распространяются, а записываются и подавляются, например, для защиты потока от сбоя путем охраны его самого внешнего блока.
Python очень толерантен в этом отношении, и except: действительно перехватывает все, включая неправильно написанные имена, вызовы sys.exit(), прерывания Ctrl+C, сбои unittest и все другие исключения, которые вы просто не хотите перехватывать.
- Минимизируйте количество кода в блоке
try/except. Чем больше телоtry, тем больше вероятность, что исключение будет вызвано строкой кода, от которой вы не ожидали возникновения исключения. В таких случаях блокtry/exceptскрывает реальную ошибку. - Используйте блок
finallyдля выполнения кода независимо от того, вызвано ли исключение в блокеtry. Это часто полезно для очистки, например, для закрытия файла.
5. Глобальные состояния
Важно!
Избегайте изменяемого глобального состояния.
Глобальные состояния - значения на уровне модуля или атрибуты класса, которые могут изменяться во время выполнения программы.
Возникающие проблемы:
- Нарушает инкапсуляцию: такой дизайн кода может затруднить достижение поставленных целей. Например, если глобальное состояние используется для управления подключением к базе данных, то подключение к двум разным базам данных одновременно (например, для вычисления различий во время миграции) становится затруднительным. Подобные проблемы легко возникают с глобальными реестрами.
- Может изменить поведение модуля во время импорта, поскольку присвоение глобальным переменным выполняется при первом импорте модуля.
5.1. Решение
Избегайте изменяемого глобального состояния.
В тех редких случаях, когда использование глобального состояния оправдано, изменяемые глобальные сущности должны быть объявлены на уровне модуля или в качестве атрибута класса и сделаны внутренними путем добавления символа _ к имени. При необходимости внешний доступ к изменяемому глобальному состоянию должен осуществляться через публичные функции или методы класса. См. Именование ниже. Пожалуйста, объясните в комментарии или в документе, на который ссылается комментарий, причины использования изменяемого глобального состояния.
Константы на уровне модуля разрешены и приветствуются. Например: _MAX_HOLY_HANDGRENADE_COUNT = 3 для константы внутреннего использования или SIR_LANCELOTS_FAVORITE_COLOR = «blue» для константы общедоступного API. Константы должны иметь имена, написанные заглавными буквами с подчеркиванием. См. раздел Именование ниже.
6. Вложенные/локальные/внутренние классы и функции
Класс может быть определен внутри метода, функции или класса. Функция может быть определена внутри метода или функции. Вложенные функции имеют доступ только для чтения к переменным, определенным в окружающих областях.
Это позволяет определять классы и функции, которые используются только в очень ограниченной области. Очень похоже на ADT. Обычно используется для реализации декораторов.
Но вложенные функции и классы не могут быть протестированы напрямую. Вложение может сделать внешнюю функцию более длинной и менее читаемой.
6.1. Решение
Они подходят с некоторыми оговорками. Избегайте вложенных функций или классов, за исключением случаев, когда закрываете локальное значение, отличное от self или cls. Не вкладывайте функцию только для того, чтобы скрыть ее от пользователей модуля. Вместо этого добавьте префикс _ к ее имени на уровне модуля, чтобы к ней по-прежнему был доступ для тестов.
7. Выражения-генераторы
Списковые включения, словарей и наборов, а также выражений генераторов предоставляют лаконичный и эффективный способ создания типов контейнеров и итераторов без использования традиционных циклов, map(), filter() или lambda.
Простые генераторы могут быть более понятными и простыми, чем другие методы создания словарей, списков или наборов. Выражения-генераторы могут быть очень эффективными, поскольку они полностью исключают создание списка.
Но в тоже время сложные выражения-генераторы могут быть трудны для чтения.
Генераторы допускаются, однако несколько предложений for или выражений фильтрации не допускаются. Оптимизируйте для читаемости, а не для краткости.
# Так мы делаем
result = [mapping_expr for value in iterable if filter_expr]
result = [
is_valid(metric={'key': value})
for value in interesting_iterable
if a_longer_filter_expression(value)
]
descriptive_name = [
transform({'key': key, 'value': value}, color='black')
for key, value in generate_iterable(some_input)
if complicated_condition_is_met(key, value)
]
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
return {
x: complicated_transform(x)
for x in long_generator_function(parameter)
if x is not None
}
return (x**2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
# Так мы не делаем
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
return (
(x, y, z)
for x in range(5)
for y in range(5)
if x != y
for z in range(5)
if y != z
)8. Итераторы и операторы по умолчанию
Используйте итераторы и операторы по умолчанию для типов, которые их поддерживают, таких как списки, словари и файлы. Типы контейнеров, такие как словари и списки, определяют итераторы по умолчанию и операторы проверки членства («in» и «not in»).
Итераторы и операторы по умолчанию просты и эффективны. Они выражают операцию напрямую, без дополнительных вызовов методов. Функция, использующая операторы по умолчанию, является универсальной. Ее можно использовать с любым типом, который поддерживает эту операцию.
Но невозможно определить тип объектов по именам методов (если переменная не имеет аннотаций типов). Это также является преимуществом.
Используйте стандартные итераторы и операторы для типов, которые их поддерживают, таких как списки, словари и файлы. Встроенные типы также определяют методы итераторов. Предпочитайте эти методы методам, которые возвращают списки, за исключением того, что вы не должны изменять контейнер во время его итерации.
# Так мы делаем
for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...# Так мы не делаем
for key in adict.keys(): ...
for line in afile.readlines(): ...9. Функции генераторы
Функция-генератор возвращает итератор, который возвращает значение каждый раз при выполнении оператора yield. После возврата значения состояние выполнения функции-генератора приостанавливается до тех пор, пока не понадобится следующее значение.
Таким образом мы получаем более простой код, поскольку состояние локальных переменных и поток управления сохраняются для каждого вызова. Генератор использует меньше памяти, чем функция, которая создает весь список значений сразу.
9.1. Недостатки
Локальные переменные в генераторе не будут подвергаться сборке мусора, пока генератор не будет полностью израсходован или сам не подвергнется сборке мусора.
9.2. Решение
- Правильно используйте «Yields:» вместо «Returns:» в документации для генераторных функций.
- Если генератор управляет дорогостоящим ресурсом, убедитесь, что очистка выполняется принудительно.
Хороший способ очистки — обернуть генератор контекстным менеджером PEP-0533.
10. Лямбда функции
Лямбды определяют анонимные функции в выражении.
Подходят для однострочных выражений. Предпочтительнее использовать выражения-генераторы, чем map() или filter() с lambda.
Но при использовании лямбда функций труднее читать и отлаживать код. Отсутствие имен означает, что трассировки стека сложнее понять. Выразительность ограничена, поскольку функция может содержать только выражение.
Допускается использование лямбда-функций. Если код внутри лямбда-функции занимает несколько строк или превышает 60–80 символов, лучше определить его как обычную вложенную функцию.
Для общих операций, таких как умножение, используйте функции из модуля operator вместо лямбда-функций. Например, предпочтите operator.mul вместо lambda x, y: x * y.
11. Условные выражения(тернарные операторы)
Условные выражения (иногда называемые «тернарным оператором») — это механизмы, которые обеспечивают более краткий синтаксис для операторов if. Например: x = 1 if cond else 2.
Подходят для простых случаев. т.к. могут быть сложнее для чтения, чем оператор if. Условие может быть трудно найти, если выражение длинное.
# Так мы делаем
one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value)
else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
'yes, true, affirmative, confirmed, correct'
if predicate(value)
else 'no, false, negative, nay')# Так мы не делаем
bad_line_breaking = ('yes' if predicate(value) else
'no')
portion_too_long = ('yes'
if some_long_module.some_long_predicate_function(
really_long_variable_name)
else 'no, false, negative, nay')12. Значение аргументов по умолчанию
Вы можете указать значения для переменных в конце списка параметров функции, например, def foo(a, b=0):. Если foo вызывается только с одним аргументом, b устанавливается равным 0. Если она вызывается с двумя аргументами, b принимает значение второго аргумента.
Часто бывает, что функция использует много значений по умолчанию, но в редких случаях вы хотите переопределить значения по умолчанию. Значения аргументов по умолчанию предоставляют простой способ сделать это, без необходимости определять множество функций для редких исключений. Поскольку Python не поддерживает перегруженные методы/функции, аргументы по умолчанию являются простым способом «имитации» поведения перегрузки.
Но аргументы по умолчанию оцениваются один раз при загрузке модуля. Это может вызвать проблемы, если аргумент является изменяемым объектом, таким как список или словарь. Если функция изменяет объект (например, добавляя элемент в список), значение по умолчанию изменяется.
Можно использовать со следующим предостережением:
- Не используйте изменяемые объекты в качестве значений по умолчанию в определении функции или метода.
# Так мы делаем
def foo(a, b=None):
if b is None:
b = []
def foo(a, b: Sequence | None = None):
if b is None:
b = []
def foo(a, b: Sequence = ()):
# Пустой кортеж в порядке, так как кортежи неизменяемы.
...
from absl import flags
_FOO = flags.DEFINE_string(...)# Так мы не делаем
def foo(a, b=[]):
...
def foo(a, b=time.time()):
# Предполагается, что `b` означает, когда этот модуль был загружен?
...
def foo(a, b=_FOO.value):
# sys.argv еще не был проанализирован...
...
def foo(a, b: Mapping = {}):
# Все еще может быть передан в непроверенный код.
...13. Свойства
Свойства - способ обертывания вызовов методов для получения и установки атрибута в качестве стандартного доступа к атрибуту.
Свойства могут использоваться для управления получением или установкой атрибутов, которые требуют тривиальных вычислений или логики. Реализация свойств должна соответствовать общим ожиданиям от обычного доступа к атрибутам: они должны быть дешевыми, простыми и не вызывать неожиданностей.
13.1. Преимущества
- Позволяет использовать API для доступа к атрибутам и их присвоения вместо вызовов методов getter и setter
- Может использоваться для создания атрибута, доступного только для чтения.
- Позволяет выполнять вычисления в режиме отложенного выполнения.
- Предоставляет способ поддерживать публичный интерфейс класса, когда внутренние компоненты развиваются независимо от пользователей класса.
13.2. Недостатки
- Может скрывать побочные эффекты, подобно перегрузке операторов.
- Может сбивать с толку подклассы.
13.3. Решение
Свойства разрешены, но, как и перегрузка операторов, должны использоваться только при необходимости и соответствовать ожиданиям типичного доступа к атрибутам; в противном случае следуйте правилам геттеров и сеттеров
Например, использование свойства для простого получения и установки внутреннего атрибута не допускается: вычисления не производятся, поэтому свойство не нужно (вместо этого сделайте атрибут общедоступным. В то же время, использование свойства для управления доступом к атрибуту или для вычисления тривиально производного значения допускается: логика проста и не вызывает удивления.
Свойства должны создаваться с помощью декоратора @property. Ручная реализация дескриптора свойства считается расширенной функцией.
Наследование свойств может быть неочевидным. Не используйте свойства для реализации вычислений, которые подкласс может захотеть переопределить и расширить.
14. True/False
Python оценивает определенные значения как False в булевом контексте. Правило гласит, что все «пустые» значения считаются ложными, поэтому 0, None, [], {}, „“ все оцениваются как ложные в булевом контексте.
Условия, использующие булевы значения Python, легче читать и они менее подвержены ошибкам. В большинстве случаев они также быстрее.
По возможности используйте «неявное» ложное значение, например, if foo: вместо if foo != []:. Однако следует помнить о нескольких предостережениях:
- Всегда используйте
if foo is None:(илиis not None) для проверки значенияNone. Например, при проверке, была ли переменная или аргумент, по умолчанию равныйNone, установлен на какое-либо другое значение. Другое значение может быть ложным в булевом контексте! - Никогда не сравнивайте булеву переменную с
False, используя==. Вместо этого используйтеif not x:. Если вам нужно отличитьFalseотNone, то соедините выражения, например,if not x and x is not None:. - Для последовательностей (строки, списки, кортежи) используйте тот факт, что пустые последовательности являются ложными, поэтому
if seq:иif not seq:предпочтительнее, чемif len(seq):иif not len(seq):соответственно. - При работе с целыми числами неявная ложность может принести больше вреда, чем пользы (например, случайная обработка
Noneкак 0). Вы можете сравнить значение, которое, как известно, является целым числом (и не является результатомlen()), с целым числом 0.
# Так мы делаем
if not users:
print('no users')
if i % 10 == 0:
self.handle_multiple_of_ten()
def f(x=None):
if x is None:
x = []# Так мы не делаем
if len(users) == 0:
print('no users')
if not i % 10:
self.handle_multiple_of_ten()
def f(x=None):
x = x or []- Обратите внимание, что
„0“(т. е.0в виде строки) оценивается как true. - Обратите внимание, что массивы Numpy могут вызывать исключение в неявном булевом контексте. При проверке пустоты
np.arrayпредпочтительно использовать атрибут.size(например,if not users.size).
15. Лексическая область действия
Вложенная функция Python может ссылаться на переменные, определенные в окружающих функциях, но не может присваивать им значения. Связывание переменных разрешается с помощью лексической области действия, то есть на основе статического текста программы. Любое присвоение имени в блоке приводит к тому, что Python будет рассматривать все ссылки на это имя как локальную переменную, даже если использование предшествует присвоению. Если происходит глобальное объявление, имя рассматривается как глобальная переменная.
Пример использования этой функции:
def get_adder(summand1: float) -> Callable[[float], float]:
"""Возвращает функцию, которая складывает числа с заданным числом."""
def adder(summand2: float) -> float:
return summand1 + summand2
return adderЧасто приводит к более понятному и элегантному коду. Особенно удобно для опытных программистов, работающих с Lisp и Scheme (а также Haskell, ML и др.).
Но может привести к запутанным ошибкам, как в этом примере, основанном на PEP-0227.
i = 4
def foo(x: Iterable[int]):
def bar():
print(i, end='')
# ...
# Здесь куча кода
# ...
for i in x: # i является локальным для foo, так что это то, что видит bar
print(i, end='')
bar()Таким образом, foo([1, 2, 3]) выведет 1 2 3 3, а не 1 2 3 4.
16. Декораторы функций и методов
Декораторы для функций и методов (также известные как «нотация @»).
Одним из распространенных декораторов является @property, использующийся для преобразования обычных методов в динамически вычисляемые атрибуты.
Однако синтаксис декораторов также допускает использование пользовательских декораторов. В частности, для некоторой функции my_decorator следующий код:
class C:
@my_decorator
def method(self):
# тело метода ...эквивалентно следующему:
class C:
def method(self):
# тело метода ...
method = my_decorator(method)Важно!
Используйте декораторы с умом, когда в этом есть явное преимущество. Избегайте
staticmethodи ограничьте использованиеclassmethod
16.1. Минусы
Декораторы могут выполнять произвольные операции над аргументами функции или возвращаемыми значениями, что приводит к неожиданному неявном поведению. Кроме того, декораторы выполняются во время определения объекта. Для объектов на уровне модуля (классы, функции модуля и т. д.) это происходит во время импорта. Сбои в коде декоратора практически невозможно исправить.
16.2. Решение
Используйте декораторы с умом, когда в этом есть явное преимущество. Декораторы должны следовать тем же правилам импорта и именования, что и функции. В документации декоратора должно быть четко указано, что функция является декоратором. Пишите юнит-тесты для декораторов.
Избегайте внешних зависимостей в самом декораторе (например, не полагайтесь на файлы, сокеты, подключения к базам данных и т. д.), поскольку они могут быть недоступны при запуске декоратора (во время импорта, возможно, из pydoc или других инструментов). Декоратор, вызываемый с действительными параметрами, должен (насколько это возможно) гарантированно работать во всех случаях.
Декораторы являются частным случаем «кода верхнего уровня» — см. main для более подробного обсуждения.
Никогда не используйте staticmethod, если это не является обязательным для интеграции с API, определенным в существующей библиотеке. Вместо этого напишите функцию на уровне модуля.
Используйте classmethod только при написании именованного конструктора или специфичной для класса процедуры, которая изменяет необходимое глобальное состояние, такое как кэш всего процесса.
17. Потоки
Не полагайтесь на атомарность встроенных типов.
Хотя встроенные типы данных Python, такие как словари, кажутся атомарными, существуют крайние случаи, когда они не являются атомарными (например, если __hash__ или __eq__ реализованы как методы Python), и на их атомарность полагаться не следует. Не следует также полагаться на атомарное присваивание переменных (поскольку это, в свою очередь, зависит от словарей).
Используйте тип данных Queue модуля queue в качестве предпочтительного способа обмена данными между потоками. В противном случае используйте модуль threading и его примитивы блокировки. Предпочитайте переменные условий и threading.Condition вместо использования блокировок более низкого уровня.
18. Расширенные возможности
ПРЕДУПРЕЖДЕНИЕ!
Избегайте этих возможностей.
Python — чрезвычайно гибкий язык, который предоставляет множество интересных функций, таких как пользовательские метаклассы, доступ к байт-коду, компиляция на лету, динамическое наследование, переопределение родительских объектов, хаки импорта, рефлексия (например, некоторые использования getattr()), модификация внутренних компонентов системы, методы __del__, реализующие настраиваемую очистку, и т. д.
18.1. Плюсы
Это мощные возможности языка. Они могут сделать ваш код более компактным.
18.2. Минусы
Очень соблазнительно использовать эти «классные» функции, когда в них нет абсолютной необходимости. Код, в котором используются необычные функции, сложнее читать, понимать и отлаживать. Сначала (автору) это не кажется таким, но при повторном просмотре код оказывается сложнее, чем код, который длиннее, но проще.
18.3. Решение
Избегайте использования этих функций в своем коде.
Модули и классы стандартной библиотеки, которые используют эти функции внутренне, можно использовать (например, abc.ABCMeta, dataclasses и enum).
19. Современный Python: из __future__ imports
Возможность включить некоторые из более современных функций с помощью операторов from __future__ import позволяет рано использовать функции из ожидаемых будущих версий Python.
Семантические изменения в новой версии языка могут быть ограничены специальным импортом future, чтобы включить их на основе отдельных файлов в более ранних версиях среды выполнения.
19.1. Плюсы
Это доказало, что обновления версий среды выполнения проходят более гладко, поскольку изменения могут вноситься на основе отдельных файлов, при этом объявляется совместимость и предотвращаются регрессии в этих файлах. Современный код более удобен в обслуживании, поскольку в нем меньше вероятность накопления технического долга, который станет проблемой при будущих обновлениях среды выполнения.
19.2. Минусы
Такой код может не работать на очень старых версиях интерпретатора, выпущенных до введения необходимого оператора future. Необходимость в этом чаще возникает в проектах, поддерживающих чрезвычайно широкий спектр сред.
19.3. Решение
Рекомендуется использовать операторы from __future__ import. Это позволяет данному исходному файлу начать использовать более современные синтаксические возможности Python уже сегодня. Как только вам больше не нужно будет запускать программу на версии, в которой эти возможности скрыты за импортом __future__, можете смело удалять эти строки.
В коде, который может выполняться на версиях 3.5, а не >= 3.7, импортируйте:
from __future__ import generator_stop
Для получения дополнительной информации прочитайте документацию Определения операторов future в Python.
Пожалуйста, не удаляйте эти импорты, пока не будете уверены, что код используется только в достаточно современной среде. Даже если вы в настоящее время не используете функцию, которую обеспечивает конкретный импорт future в вашем коде, ее сохранение в файле предотвращает непреднамеренную зависимость более поздних модификаций кода от старого поведения.
Используйте другие операторы импорта from __future__ по своему усмотрению.
20. Код с аннотациями типов
Аннотации типов (или «подсказки типов») используются для аргументов функций или методов и возвращаемых значений:
def func(a: int) -> list[int]:Вы также можете объявить тип переменной, используя аналогичный синтаксис:
a: SomeType = some_func()Вы можете аннотировать код Python с помощью подсказок типов. Проверяйте тип кода во время сборки с помощью инструмента проверки типов, такого как pytype. В большинстве случаев, когда это возможно, аннотации типов находятся в исходных файлах. Для сторонних модулей или модулей расширений аннотации могут находиться в [заглушках .pyi файлах](https://peps.python.org/pep-0484/# заглушки).
20.1. Преимущества
Аннотации типов улучшают читаемость и поддерживаемость вашего кода. Проверка типов преобразует многие ошибки выполнения в ошибки компиляции и ограничивает возможности использования расширенных функций.
20.2. Недостатки
Вам придется постоянно обновлять объявления типов. Вы можете увидеть ошибки типов, которые, по вашему мнению, являются действительным кодом. Использование проверки типов может ограничить вашу возможность использовать расширенные функции.
20.3. Решение
Настоятельно рекомендуется включить анализ типов Python при обновлении кода. При добавлении или изменении публичных API включите аннотации типов и включите проверку через pytype в системе сборки. Поскольку статический анализ является относительно новым для Python, мы признаем, что нежелательные побочные эффекты (такие как неправильно выведенные типы) могут помешать его внедрению в некоторых проектах. В таких ситуациях авторам рекомендуется добавить комментарий с TODO или ссылку на ошибку, описывающую проблему (проблемы), которая в настоящее время препятствует внедрению аннотаций типов в файле BUILD или в самом коде, в зависимости от ситуации.