Содержание
- 1 Введение
- 2 Правила написания Python-кода
- 2.1 Линтер
- 2.2 Импорты
- 2.3 Пакеты
- 2.4 Исключения
- 2.5 Глобальные переменные
- 2.6 Вложенные / локальные / внутренние классы и функции
- 2.7 Comprehensions и генераторные выражения
- 2.8 Итераторы и операторы по умолчанию
- 2.9 Генераторы
- 2.10 Лямбда-функции
- 2.11 Условные выражения
- 2.12 Значения аргументов по умолчанию
- 2.13 Свойства
- 2.14 True/False Evaluations
- 2.16 Лексическая область видимости
- 2.17 Декораторы функций и методов
- 2.18 Потоки
- 2.19 "Мощные" функции
- 2.20 Современный Python: Python 3 и from __future__ imports
- 2.21 Аннотация типов в коде
- 3 Правила оформления Python-кода
- 3.1 Точка с запятой
- 3.2 Длина строки
- 3.3 Круглые скобки
- 3.4 Отступы
- 3.4.1 Замыкающие запятые в последовательности элементов?
- 3.5 Пустые строки
- 3.6 Пробелы
- 3.7 Шебанг-строка
- 3.8 Комментарии и Документирование
- 3.10 Строки
- 3.11 Файлы и сокеты
- 3.12 Комментарии TODO
- 3.13 Форматирование импортов
- 3.14 Выражения
- 3.15 Способы доступа
- 3.16 Именование
- 3.17 Main-функция
- 3.18 Длина функций
- 3.19 Аннотация типов
- 3.19.1 Общие правила
- 3.19.2 Перенос строк
- 3.19.3 Предварительные декларации
- 3.19.4 Значения по умолчанию
- 3.19.5 NoneType
- 3.19.6 Псевдонимы типов
- 3.19.7 Игнорирование типов
- 3.19.8 Типизация переменных
- 3.19.9 Кортежи или строки?
- 3.19.10 TypeVars
- 3.19.11 Строковые типы
- 3.19.12 Импорты для типов
- 3.19.13 Условные импорты
- 3.19.14 Циклически зависимости
- 3.19.15 Generic-типы
- 3.19.16 Создание зависимостей
- 4 Заключение
- 5 Словарь терминов
Python - основной динамический язык, используемый в Google. Это руководство по стилю кода представляет собой перечень из разрешений и запретов для программ на Python.
Чтобы помочь вам правильно отформатировать код, мы создали файл настроек для Vim. Для Emacs должны подойти настройки по умолчанию.
Многие команды используют yapf для автоматического форматирования кода, чтобы избежать лишних споров.
Запустите pylint
в своем проекте, используя это pylintrc.
pylint
это инструмент для поиска ошибок и стилистических проблем в исходном коде Python.
Он находит проблемы, которые обычно обнаруживаются компилятором в менее
динамических языках, таких как C и C ++. Из-за динамической природы Python
некоторые предупреждения могут быть неверными; однако, такие ситуации обычно
возникают достаточно редко.
Выявляет ошибки, которые легко пропустить. Например, опечатки, использование переменных до их объявления и другие.
pylint
не идеален. Чтобы воспользоваться им, иногда приходится писать код специально под него,
отключать его предупреждения или вносить в него правки.
Убедитесь, что запускаете
pylint
в вашем проекте.
Отключите предупреждения, если они неактуальны, чтобы не пропустить другие значимые проблемы. Чтобы отключить предупреждения, вы можете установить комментарий в соответствующей строке:
dict = 'something awful' # Плохая идея... pylint: disable=redefined-builtin
Каждое предупреждение
pylint
имеет свой собственный строковый идентификатор (например, empty-docstring
).
Предупреждения по спецификации Google начинаются с g-
.
If the reason for the suppression is not clear from the symbolic name, add an explanation.
Если причина отключения не ясна из строкового названия, добавьте к нему пояснение.
Вы можете получить список предупреждений
pylint
выполнив команду:
pylint --list-msgs
Чтобы получить дополнительную информацию о конкретном сообщении, используйте:
pylint --help-msg=C6409
Предпочтительно использовать pylint: disable
взамен устаревшей формы pylint: disable-msg
.
Предупреждения о неиспользуемых аргументах можно отключить, удалив переменные в начале функции. Всегда оставляйте комментарий, объясняющий, почему вы удаляете аргумент. "Не используется" является достаточной формулировкой. Например:
def viking_cafe_order(spam, beans, eggs=None):
del beans, eggs # Не используется викингами.
return spam + spam + spam
Другим распространенным способом отключения этого типа предупреждения является
использование '_
' в качестве идентификатора для неиспользуемого аргумента или
постфикса аргумента 'unused_
', или его присвоение '_
'. Все эти способы
допустимы, но не являются предпочтительными, поскольку могут некорректно
работать для вызовов, которые передают аргументы по имени и не гарантируют,
что они фактически не используются.
Механизм повторного использования кода из одного модуля в другом.
Соглашение об управлении пространством имен - простое. Все члены модуля должны быть явно прописаны в импорте.
Имена модулей могут пересекаться, вызывая конфликты. Некоторые имена модулей могут быть чрезмерно длинными.
- Используйте
from x import y
, гдеx
- это префикс модуля, аy
- имя члена без префикса. - Используйте
from x import y as z
, если импортируются два члена модулей с именемy
или еслиy
- это слишком длинное имя. - Используйте
import y as z
только тогда, когдаz
- это стандартное сокращение (например,np
дляnumpy
).
Например, класс sound.effects.echo.EchoFilter
может быть импортирован так:
from sound.effects.echo import EchoFilter
...
EchoFilter(input, output, delay=0.7, atten=4)
Не используйте относительные имена при импорте. Даже если модуль находится в этом же пакете, используйте полное имя пакета. Это позволяет избежать случайного повторного импорта пакета.
При импорте модуля всегда используйте полный путь к нему.
Позволяет избежать конфликтов в именах модулей или неправильного импорта из-за того, что путь для поиска модуля не соответствует ожиданиям автора. Кроме того, подход облегчает поиск модулей.
Усложняет развертывание кода, поскольку вам приходится повторять иерархию пакетов. Однако, это не является большой проблемой с современными механизмами развертывания.
Весь новый код должен импортировать каждый модуль по его полному имени пакета.
Импорт должен выглядеть следующим образом:
# Ссылка на absl.flags в коде через указание полного пути (более подробно).
import absl.flags
from doctor.who import jodie
FLAGS = absl.flags.FLAGS
# Ссылка на flags в коде через указание только имени модуля (более общее).
from absl import flags
from doctor.who import jodie
FLAGS = flags.FLAGS
Пример неправильного использования (предполагается, что этот файл находится по
пути doctor/who/
, где также находится jodie.py
):
# Непонятно, какой модуль подразумевал автор и что будет импортировано.
# Фактическое поведение импорта зависит от настроек sys.path.
# Какой из возможных модулей jodie намеревался импортировать автор?
import jodie
Не следует рассчитывать на то, что каталог, в котором находится основной
двоичный файл, находится в sys.path, несмотря на то, что это по умолчанию
происходит в некоторых средах. В этом случае код должен предполагать, что
import jodie
относится к стороннему пакету или пакету верхнего уровня с
именем jodie
, а не к локальному jodie.py
.
Исключения допустимы, но их следует использовать осторожно.
Исключение - это способ выхода из обычного потока управления для обработки ошибок или других исключительных условий.
Стандартный поток управления кодом не загроможден кодом обработки ошибок. Это также позволяет потоку управления пропускать несколько шагов выполнения в определенных случаях, например, при возврате из N вложенных функций за один шаг вместо необходимости переносить коды ошибок по каждой из них.
Может сделать поток управления менее предсказуемым. Легко пропустить ошибку при использовании сторонних функций или классов.
Исключения должны соответствовать определенным условиям:
-
Используйте встроенные классы исключений, когда это действительно имеет смысл. Например, сделайте вызов исключения
ValueError
, чтобы указать на ошибку программирования, такую как нарушенное предусловие (например, если вам передали отрицательное число, а требовалось положительное). Не используйте выраженияassert
для валидации значений аргументов публичного API.assert
используется для поддержания внутренней корректности кода, а не для обеспечения правильности его использования или для указания того, что произошло какое-то непредвиденное событие. Если в указанных случаях требуется исключение, используйте операторraise
. Например:Правильно: def connect_to_next_port(self, minimum): """Подключение к следующему доступному порту. Args: minimum: Значение порта, большее или равное 1024. Returns: Новый минимальный порт. Raises: ConnectionError: Если не найден доступный порт. """ if minimum < 1024: # Обратите внимание, что вызов исключения ValueError не указан в # описании секции "Raises:", поскольку нет необходимости обрабатывать # его как случай неверного использования API. raise ValueError(f'Минимальный порт должен быть не меньше 1024, а не {minimum}.') port = self._find_next_open_port(minimum) if not port: raise ConnectionError( f'Не удалось подключиться к сервису по порту {minimum} или выше.') assert port >= minimum, ( f'Некорректный порт {port}, поскольку минимальный порт равен {minimum}.') return port
Неправильно: def connect_to_next_port(self, minimum): """Подключение к следующему доступному порту. Args: minimum: Значение порта, большее или равное 1024. Returns: Новый минимальный порт. """ assert minimum >= 1024, 'Минимальный порт должен быть не меньше 1024.' port = self._find_next_open_port(minimum) assert port is not None return port
-
Библиотеки или пакеты могут определять свои собственные исключения. При этом они должны наследовать их от существующего класса исключения. Имена исключений должны заканчиваться на
Error
и не должны дублировать название модуля (foo.FooError
). -
Никогда не используйте выражение
except:
, которое перехватывает все исключения, или перехватException
илиStandardError
, если вы не- повторно вызываете исключение, или
- создаете точку изоляции в программе, где исключения не распространяются, а вместо этого записываются и подавляются, например, в случае защиты потока от сбоя путем поддержания стабильности его внешнего блока
Python - очень толерантный язык в этом отношении и выражение
except:
действительно будет перехватывать все, включая ошибки именования, вызовы sys.exit(), прерывания Ctrl+C, ошибки юниттестов и иные виды исключений которые вы на самом деле не планировали перехватывать. -
Минимизируйте количество кода в блоке
try
/except
. Чем больше телоtry
, тем больше вероятность того, что исключение будет вызвано строкой кода, от которой вы этого не ожидали. В этом случае, блокtry
/except
скроет реальную ошибку. -
Используйте выражение
finally
для выполнения кода вне зависимости от того, возникнет ли исключение в блокеtry
. Это часто бывает полезно для процедуры высвобождения ресурсов, например, закрытия файла.
Избегайте глобальных переменных.
Глобальными считаются переменные, которые объявлены на уровне модуля или как атрибуты класса.
Подобные переменные иногда бывают полезны.
Могут изменить поведение модуля во время импорта, поскольку присвоение глобальным переменным выполняется при первом импорте модуля.
Избегайте глобальных переменных.
При этом константы уровня модуля разрешены и приветствуются несмотря на то, что они
технически являются переменными. Например: MAX_HOLY_HANDGRENADE_COUNT = 3
.
Константы должны именоваться заглавными буквами с подчеркиванием. Подробнее см. Именование.
В случае, когда глобальная переменная действительно необходима, она должна
быть объявлена защищенной на уровне модуля при помощи добавления к имени префикса _
.
Доступ извне должен осуществляться через публичные функции уровня модуля. Подробнее см. Именование.
Вложенные локальные функции или классы подходят, когда используются для скрытия локальной переменной. Внутренние классы приветствуются.
Класс может быть определен внутри метода, функции или класса. Функция может быть определена внутри метода или функции. Вложенные функции имеют доступ только для чтения к переменным, определенным в той же области видимости, что и они сами.
Позволяет определять служебные классы и функции, которые используются только в очень ограниченной области видимости. Позволяет реализовать АТД. Обычно используется для реализации декораторов.
Экземпляры вложенных или локальных классов нельзя сериализовать. Вложенные функции и классы не могут быть протестированы напрямую. Вложенность может сделать вашу внешнюю функцию более длинной и менее читаемой.
Применимы с некоторыми оговорками. Избегайте вложенных функций или классов, кроме случаев скрытия локальных значений. Не делайте функцию вложенной только для того, чтобы скрыть возможность её использования вне вашего модуля. Вместо этого добавьте к ее имени префикс _ на уровне модуля, чтобы тесты могли получить к ней доступ.
Можно использовать в простых случаях.
List, Dict, и Set comprehensions, а также генераторные выражения обеспечивают
выразительный и эффективный способ создания контейнерных типов и итераторов,
без необходимости использования традиционных циклов, функций map()
,
filter()
, или lambda
.
Простые comprehensions могут быть более логичными и понятными, чем другие методы создания словаря, списка или множества. Генераторные выражения могут быть очень эффективными, поскольку они полностью исключают создание списка.
Сложные comprehensions или выражения генератора могут быть трудночитаемыми..
Можно использовать в простых случаях. Каждая часть выражения должна умещаться
в одной строке: сопоставление, выражение for, фильтр. Использование нескольких
выражений for
или фильтров не допускается. Когда выражения становятся более
сложными - используйте обычные циклы. [Локальное дополнение] Важное замечание:
несмотря на то, что блок фильтрации может быть реализован как тернарная
операция - запрещается разбивать его на несколько строк, поскольку в данном
случае это атомарная конструкция.
Примеры правильного использования:
result = [mapping_expr for value in iterable if filter_expr]
result = [
{'key': value} for value in iterable
if a_long_filter_expression(value)
]
result = [
complicated_transform(x)
for x in iterable if predicate(x)
]
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
}
squares_generator = (x**2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
eat(
jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black'
)
Примеры неправильного использования:
result = [complicated_transform(
x, some_argument=x+1)
for x in iterable if predicate(x)]
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)
Используйте итераторы и операторы по умолчанию для типов, которые их поддерживают, например для списков, словарей и файлов.
Контейнерные типы, такие как словари или списки, определяют итераторы по умолчанию и операторы проверки вхождения ("in" и "not in").
Итераторы и операторы по умолчанию просты и эффективны. Они выполняют операцию напрямую, без дополнительных вызовов методов. Функция, использующая операторы по умолчанию, является универсальной. Ее можно использовать с любым типом, поддерживающим операцию.
Вы не можете определить тип объекта, прочитав имя метода (например, has_key()
относится к словарю). Одновременно, это можно считать преимуществом.
Используйте итераторы и операторы по умолчанию для типов, которые их поддерживают, например для списков, словарей и файлов. Встроенные типы также определяют методы итератора. Используйте эти методы вместо методов, возвращающих списки, за исключением тех случаев, когда вам нужно изменять контейнерный объект при итерации по нему.
Примеры правильного использования:
for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
for k, v in six.iteritems(adict): ...
Примеры неправильного использования:
for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
for k, v in dict.iteritems(): ...
При необходимости используйте генераторы.
Функция-генератор возвращает итератор, который возвращает значение каждый раз,
когда выполняется оператор yield
. После получения значения состояние
выполнения функции-генератора приостанавливается до тех пор, пока не
потребуется следующее значение.
Использование генераторов обеспечивает более простой код, поскольку состояние локальных переменных и потока управления сохраняется для каждого вызова. Генератор потребляет меньше памяти, чем функция, которая создает сразу весь список значений.
Не обнаружены.
Используйте. Указывайте "Yields:" вместо "Returns:" в docstring для функций-генераторов.
Применимы для однострочных выражений. Используйте генераторные выражения вместо
map()
или filter()
с lambda
.
Лямбды определяют анонимные функции в выражении без отдельного объявления.
Они удобны.
Сложнее читать и отлаживать, чем локальные функции. Отсутствие имен означает, что трассировку стека будет труднее понять. Выразительность ограничена, потому что функция может содержать только выражение.
Можно использовать их для однострочных выражений. Если код внутри лямбда-функции не умещается в одну строку, то нужно определить ее как обычную вложенную функцию.
Для обычных операций, таких как умножение, вместо лямбда-функций используйте
функции из модуля operator
. Например, нужно использовать operator.mul
вместо
lambda x, y: x * y
.
Применимы для простых случаев.
Условные выражения (иногда называемые «тернарными операторами») - это
механизмы, которые обеспечивают более короткий синтаксис для операторов if.
Например: x = 1 if cond else 2
.
Короче и удобнее, чем оператор if.
В определенных случаях труднее читать, чем оператор if. Иногда условие сложно выделить, если выражение длинное.
Применимы для простых случаев. Каждая часть должна умещаться на одной линии: true-выражение, if-выражение, else-выражение. Используйте стандартную форму оператора 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')
Применимы в большинстве случаев.
Вы можете указать значения для переменных в конце списка параметров функции,
например, def foo(a, b=0):
. Если foo
вызывается только с одним аргументом,
то b
принимает значение 0. Если функция вызывается с двумя аргументами,
b
принимает значение второго аргумента.
Часто у вас есть функция, которая использует множество значений по умолчанию, но в редких случаях вы хотели бы изменить их. Значения аргументов по умолчанию обеспечивают простой способ сделать это без необходимости определять множество функций для специфических случаев. Поскольку Python не поддерживает перегрузку методов / функций, аргументы по умолчанию - это простой способ "имитировать" поведение при перегрузке.
Аргументы по умолчанию определяются один раз во время загрузки модуля. Это может вызвать проблемы, если аргумент является изменяемым объектом, таким как список или словарь. Если функция изменяет объект (например, добавляет элемент в список), значение по умолчанию изменится.
Можно использовать, соблюдая следующее условие: не используйте изменяемые объекты в качестве значений по умолчанию при определении функции или метода.
Примеры правильного использования:
def foo(a, b=None):
if b is None:
b = []
def foo(a, b: Optional[Sequence] = None):
if b is None:
b = []
# Пустой кортеж можно использовать, поскольку он неизменяемый
def foo(a, b: Sequence = ()):
...
Примеры неправильного использования:
def foo(a, b=[]):
...
def foo(a, b=time.time()): # Был ли загружен модуль time???
...
def foo(a, b=FLAGS.my_thing): # sys.argv еще не прочитан...
...
def foo(a, b: Mapping = {}): # Используется "небезопасный" изменяемый тип
...
Используйте свойства для доступа или заполнения данных там, где вы хотели использовать простые геттеры или сеттеры.
Способ декорации вызовов методов для получения и установки атрибута через стандартный доступ к атрибуту, когда логика его вычисления является простой.
Читаемость повышается за счет исключения явных вызовов методов get и set для простого доступа к атрибутам. Позволяет реализовать ленивые вычисления. Считается Pythonic-подходом для объявления интерфейса класса. С точки зрения производительности, разрешение свойств убирает необходимость в тривиальных методах доступа, когда прямой доступ к переменной является обоснованным. Также позволяют добавлять методы доступа в будущем без нарушения интерфейса.
Может скрывать побочные эффекты, такие как перегрузка оператора. Может сбивать с толку при создании дочерних классов.
Используйте свойства для доступа или заполнения данных там, где вы хотели
использовать простые геттеры или сеттеры. Свойства должны объявляться при
помощи декоратора @property
.
Поведение свойств при наследовании может быть неочевидным, если само свойство не переопределено. Необходимо убедиться, что методы доступа вызываются косвенно, чтобы гарантировать, что методы, переопределенные в подклассах, будут вызваны свойством (используйте паттерн проектирования "Шаблонный метод").
Пример:
import math
class Square:
"""Квадрат с двумя свойствами: площадь - изменяемое, периметр - только для чтения.
Пример использования:
>>> sq = Square(3)
>>> sq.area
9
>>> sq.perimeter
12
>>> sq.area = 16
>>> sq.side
4
>>> sq.perimeter
16
"""
def __init__(self, side):
self.side = side
@property
def area(self):
"""Площадь квадрата."""
return self._get_area()
@area.setter
def area(self, area):
return self._set_area(area)
def _get_area(self):
"""Косвенный метод доступа для вычисления свойства 'area'."""
return self.side ** 2
def _set_area(self, area):
"""Косвенный метод установки значения свойства 'area'."""
self.side = math.sqrt(area)
@property
def perimeter(self):
return self.side * 4
Если возможно, используйте «неявное» логическое значение "ложь".
Python определяет ряд значений как False
. Практическое правило: все «пустые»
значения определяются как False
. Например, 0, None, [], {}, ''
.
Условия, использующие логические значения Python, легче читать и они меньше подвержены ошибкам. В большинстве случаев они также быстрее.
Могут показаться странным для разработчиков C/C++.
По возможности используйте "неявное" логическое значение "ложь", например,
if foo:
вместо if foo != []:
. Однако, следует иметь в виду несколько
условий:
-
Всегда используйте
if foo is None:
(илиis not None
), чтобы проверить значениеNone
. Например, при проверке того, было ли для переменной или аргумента со значением по умолчаниюNone
установлено другое значение. Другое значение может быть ложным при приведении к типу bool! -
Никогда не сравнивайте логическую переменную с
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
. -
Обратите внимание, что
'0'
(т.е.0
в виде строки) приводится к логическому значению "истина".
Примеры правильного использования:
if not users:
print('no users')
if foo == 0:
self.handle_zero()
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 foo is not None and not foo:
self.handle_zero()
if not i % 10:
self.handle_multiple_of_ten()
def f(x=None):
x = x or []
Допустимо к использованию.
Вложенные функции Python могут ссылаться на переменные, определенные в родительских функциях, но не могут присваивать им значения. Привязки переменных разрешаются с использованием лексической области видимости, то есть на основе статического текста программы. Любое присвоение значения к определенному имени переменной в блоке приведет к тому, что Python будет рассматривать все ссылки на это имя как локальную переменную, даже если присвоение было выполнено после использования. Если происходит глобальное объявление, то имя рассматривается как глобальная переменная.
Пример использования:
def get_adder(summand1):
"""Возвращает функцию, которая прибавляет число к переданному числу."""
def adder(summand2):
return summand1 + summand2
return adder
В большинстве случаев позволяет реализовать более четкий и элегантный код. Особенно удобен для опытных программистов на Lisp и Scheme (а также Haskell, ML и др.).
Может привести к неочевидным ошибкам. Например, таким как пример из PEP-0227:
i = 4
def foo(x):
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
.
Допустимо к использованию.
Используйте декораторы разумно: только тогда, когда от этого есть явная польза.
Избегайте staticmethod
и ограничьте использование classmethod
.
Декораторы для функций и методов
(т.е. "объявление через @
"). Один из распространенных декораторов - это
декоратор @property
, используется для преобразования обычных методов в
динамически вычисляемые атрибуты. Синтаксис декоратора также позволяет
использовать декораторы, определяемые пользователем. В частности, для
некоторой функции my_decorator
, пример использования в качестве декоратора
будет выглядеть так:
class C:
@my_decorator
def method(self):
# тело метода ...
что аналогично:
class C:
def method(self):
# тело метода ...
method = my_decorator(method)
Удобным образом определяет некоторые преобразования над методом; преобразование может помочь устранить повторение кода, принудительно применить инварианты и т.д.
Декораторы могут выполнять произвольные операции с аргументами функции или возвращаемыми значениями, что приводит к неявному поведению. Кроме того, декораторы выполняются во время импорта. Failures in decorator code are pretty much impossible to recover from.
Используйте декораторы разумно: только тогда, когда от этого есть явная польза.
Декораторы должны следовать тем же правилам импорта и именования, что и
функции. Декоратор pydoc
должен четко указывать, что функция является
декоратором. Пишите unit тесты для декораторов.
Избегайте внешних зависимостей в самом декораторе (например, не полагайтесь на
файлы, сокеты, соединения с базой данных и т. д.), поскольку они могут быть
недоступны при запуске декоратора (во время импорта, возможно, из pydoc
или
других инструментов). Декоратор, который вызывается с валидными параметрами,
должен (насколько это возможно) успешно выполняться во всех случаях.
Декораторы - это частный случай "кода верхнего уровня" - подробнее смотрите в разделе Основное.
Никогда не используйте staticmethod
, если это не требуется для интеграции с
API, определенным в существующей библиотеке. Напишите вместо этого функцию
уровня модуля.
Используйте classmethod
только тогда, когда реализуете именованный
конструктор или специфичную для класса подпрограмму, которая изменяет
некоторое глобальное состояние, такое как кэш всего процесса.
Не полагайтесь на атомарность встроенных типов.
Несмотря на то, что встроенные типы данных Python, такие как словари, на первый
взгляд, содержат атомарные операции, есть ряд случаев, когда они не атомарны (
например если __hash__
или __eq__
реализованы как методы Python), и на их
атомарность не следует полагаться. Вы также не должны полагаться на атомарность
присвоения переменной (поскольку это, в свою очередь, зависит от реализации
словарей).
Используйте тип данных Queue
модуля Queue в качестве предпочтительного
способа передачи данных между потоками. В противном случае, используйте модуль
потоковой передачи и его блокирующие примитивы. Предпочитайте условные
переменные и threading.Condition
вместо использования низкоуровневых
блокировок.
Избегайте этих функций.
Python - чрезвычайно гибкий язык, который предоставляет множество фантастических
функций, таких как кастомные метаклассы, доступ к байт-коду, компиляция на лету,
динамическое наследование, переподчинение объектов, хаки импортов, рефлексия (
например, некоторые варианты использования getattr ()
), модификация
внутреннего устройства системы и т.д.
Это мощные языковые функции. Они могут сделать ваш код более компактным.
Очень заманчиво использовать эти "крутые" функции, когда они не являются действительно необходимыми. Сложнее читать, понимать и отлаживать код, в котором используются нестандартные функции. Поначалу так не кажется ( особенно тому, кто это написал), но при последующем пересмотре кода, он выглядит сложнее, в большинстве случаев, чем более длинный, но понятный код.
Избегайте этих функций в своем коде.
Стандартные библиотечные модули и классы, которые внутренне используют эти
функции, можно использовать (например, abc.ABCMeta
, dataclasses
и enum
).
Python 3 здесь! Проект БАРС.Бюджет-онлайн полностью перешел с Python 2.7 на Python 3.x, и в связи с этим в проекте могут попадаться старые варианты кода.
Python 3 - значительное изменение в языке Python. Существующий код не полностью адаптирован под версии 3.x, но нужды в хранении универсальных подходов и старых вариантов кода уже нет.
Код, написанный для Python 3, более понятен и в нем нет зависимости от необходимости проставлять кодировку utf-8, т. к. она теперь является стандартной.
Не обнаружены.
В прошлом был добавлен код from __future__ import
в некоторые модули.
На текущий момент в нем нет нужды, поэтому его нужно удалять из файлов, с
которыми производится работа в рамках задачи
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
Также в версии Python 3 нет необходимости прописывать в начале файла
# coding: utf-8
и любые его варианты, поэтому эту строку также нужно удалять.
Unicode-строки теперь являются стандартными в Python 3, поэтому нет необходимости
дополнительно указывать это в начале строки с помощью u''
. Все указания u
перед
началом строки нужно удалять:
# Python 2.7:
# Unicode-строка:
a = u'unicode string'
# Байтовая строка (стандартная):
a = 'byte string'
# Python 3.x:
# Unicode-строка (стандартная):
a = 'unicode string'
# Байтовая строка:
a = b'byte string'
Ранее в проекте была нужда в активной поддержке использования Python 2 и 3 одновременно, и для этого использовалась библиотека six, однако сейчас в ее наличии нет необходимости. Нужно преобразовывать ее использования в обычный Python 3-код. Примеры:
# Удалять импорты builtin-функций:
from six.moves import (
filter,
map,
zip,
range,
# и т. д.
)
# Заменять использования универсальных операторов на методы, присущие объектам:
# Старый вариант:
import six
for x, y in six.iteritems(some_dict):
...
for value in six.itervalues(some_dict):
...
# Должно стать:
for x, y in some_dict.items():
...
for value in some_dict.values():
...
# Заменять использования универсальных определений типов на типы Python 3:
# Старый вариант:
import six
int_as_str = six.text_type(1)
# Должно стать:
int_as_str = str(1)
Вы можете дополнять аннотациями код Python 3 с помощью "подсказок типов" в соответствии с PEP-484, и проверить код во время сборки с помощью инструмента проверки типов, например pytype.
Аннотации типов могут быть указаны в исходном коде или в файле stub pyi. По возможности, аннотации типов должны быть указаны в исходном коде. Используйте файлы pyi для сторонних модулей или модулей расширения.
Аннотации типов (или "подсказки типов") предназначены для аргументов функции или методов и возвращаемых значений:
def func(a: int) -> List[int]:
Вы также можете объявить тип переменной, используя синтаксис, аналогичный PEP-526:
a: SomeType = some_func()
Или используя комментарии типов в коде, который должен поддерживать устаревшие версии Python:
a = some_func() # type: SomeType
Аннотации типов улучшают читаемость и поддерживаемость вашего кода. Средство проверки типов преобразует многие ошибки времени выполнения в ошибки времени сборки и уменьшает свободу использования "мощных" функций.
Вам нужно будет поддерживать объявления типов в актуальном состоянии. Вы можете увидеть ошибки типов, которые, по вашему мнению, являются допустимыми. Использование проверки типов может затруднить использование "мощных" функций.
Настоятельно рекомендуем включить анализ типов Python при обновлении кода.
При добавлении или изменении общедоступных API включите аннотации типов, а
также проверку через pytype
в системе сборки. Поскольку статический анализ
является относительно новым для Python, мы признаем, что возможны нежелательные
побочные эффекты (например, ошибочно выведенные типы), которые могут помешать
некоторым проектам. В таких ситуациях авторам рекомендуется добавить
комментарий с TODO или ссылку на ошибку, описывающую проблему, которая в
настоящее время препятствуют принятию аннотации типов в файле BUILD или в самом
коде, если это необходимо.
Не завершайте строки точкой с запятой, и не ставьте точки с запятой для того, чтобы разместить два выражения в одной строке.
Максимальная длина строки - 120 символов.
Исключениями из ограничения в 120 символов являются:
- Длинные операторы импорта.
- URL-адреса, пути или длинные флаги в комментариях.
- Константы уровня модуля с длинным именем без пробелов, которые нежелательно
разбивать на строки, такие как URL-адрес или пути.
- Комментарии отключения Pylint. (например,
# pylint: disable=invalid-name
)
- Комментарии отключения Pylint. (например,
Не используйте продолжение строки с обратной косой чертой, за исключением
операторов with
, использующих три и более менеджеров контекста.
Используйте предоставляемое Python неявное соединение строк внутри круглых, квадратных и фигурных скобок. При необходимости вы можете добавить дополнительную пару скобок вокруг выражения:
foo_bar(
self, width, height, color='black', design=None, x='foo',
emphasis=None, highlight=0,
)
if (
width == 0
and height == 0
and color == 'red'
and emphasis == 'strong'
):
Если строка не помещается в одну линию, используйте круглые скобки для неявного соединения строк:
x = (
'This will build a very long long long long long long long long long long long long long long '
'long long long long long long string'
)
В комментариях, при необходимости, помещайте длинные URL-адреса в отдельную строку. Пример правильного использования:
# See details at
# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
Пример неправильного использования:
# See details at
# http://www.example.com/us/developer/documentation/api/content/\
# v2.0/csv_file_name_extension_full_specification.html
Допустимо использовать продолжение строки через обратную косую черту при
определении оператора with
, которое размещается на трех и более строках. Для
двустрочных выражений используйте вложенный оператор with
.
Примеры правильного использования:
with very_long_first_expression_function() as spam, \
very_long_second_expression_function() as beans, \
third_thing() as eggs:
place_order(eggs, beans, spam, beans)
with very_long_first_expression_function() as spam:
with very_long_second_expression_function() as beans:
place_order(beans, spam)
Пример неправильного использования:
with VeryLongFirstExpressionFunction() as spam, \
VeryLongSecondExpressionFunction() as beans:
PlaceOrder(eggs, beans, spam, beans)
Обратите внимание на отступ элементов в приведенных выше примерах продолжения строки; подробную информацию смотрите в разделе отступы.
Во всех остальных случаях, когда длина строки превышает 120 символов, и yapf не помогает привести линию к размеру ниже ограничения, - допустимо превышать указанный максимум.
Экономно используйте круглые скобки.
Можно, хотя и не обязательно, заключать кортежи в круглые скобки. Не используйте их в операторах возврата или условных операторах, только если это не перенос на новую строку или явное объявление кортежа.
Примеры правильного использования:
if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
# Для кортежа из 1 элемента круглые скобки визуально более очевидны, чем запятая.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...
Примеры неправильного использования:
if (x):
bar()
if not(x):
bar()
return (foo)
Формируйте отступы в кодовых блоках 4 пробелами.
Никогда не используйте табуляции и не смешивайте табуляции и пробелы. В случаях неявного продолжения строки следует выровнять используя отступ из 4 пробелов, и в этом случае после открытой круглой скобки или скобки в первой строке не должно быть ничего. Рекомендуется оставлять максимально возможное количество символов в строке, если все выражение не занимает более одной линии кода. Это правило не распространяется на объявления функций - для упрощения чтения при наличии аннотаций типов каждый аргумент должен быть на своей строке; также оно не распространяется на импорты.
# Выравнивание с отступом в случае одной линии кода
foo = long_function_name(
var_one, var_two, var_three, var_four, var_five, var_six, var_seven, var_eight, var_nine
)
# Если заполненные подряд линии кода занимают более 1й строки, нужен
# перенос каждого элемента
meal = (
var_one,
var_two,
var_three,
var_four,
var_five,
var_six,
var_seven,
var_eight,
var_nine,
var_ten,
var_eleven,
)
# Отступ в 4 пробела в словаре
foo = {
long_dictionary_key: (
long_dictionary_value
),
...
}
Примеры неправильного использования:
# Объекты в первой строке запрещены, если они полностью не входят в строку
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# Отступ в 2 пробела запрещен
foo = long_function_name(
var_one, var_two, var_three, var_four
)
# Отсутствует отступ в словаре
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
Замыкающие запятые в последовательностях элементов рекомендуется использовать
только в том случае, когда закрывающий символ контейнера ]
, )
или }
не
находится в той же строке, что и последний элемент. Наличие замыкающей запятой
также используется в качестве подсказки инструменту автоматического
форматирования кода Python YAPF, чтобы
направить его на автоматическое форматирование контейнера элементов до одного
элемента в строке, когда присутствует ,
после последнего элемента.
golomb3 = [0, 1, 3]
golomb4 = [
0,
1,
4,
6,
]
Пример неправильного использования:
golomb4 = [
0,
1,
4,
6
]
Две пустые строки между определениями верхнего уровня, будь то определения
функций или классов. Одна пустая строка между определениями методов, а также
между объявлением class
и первым методом. Не ставить пустую строку после
объявления def
. Используйте одиночные пустые строки, если сочтете
целесообразным, в функциях или методах.
Соблюдайте стандартные типографские правила использования пробелов вокруг знаков препинания.
Не ствьте пробелы внутри круглых скобок, квадратных скобок или фигурных скобок.
Пример правильного использования:
spam(ham[1], {eggs: 2}, [])
Пример неправильного использования:
spam( ham[ 1 ], { eggs: 2 }, [ ] )
Перед запятой, точкой с запятой или двоеточием не должно быть пробелов. Используйте пробелы после запятой, точки с запятой или двоеточия, за исключением конца строки.
Пример правильного использования:
if x == 4:
print(x, y)
x, y = y, x
Пример неправильного использования:
if x == 4 :
print(x , y)
x , y = y , x
Не используйте пробелы перед открытой круглой или фигурной скобкой, с которой начинается список аргументов, индексация или срез.
Пример правильного использования:
spam(1)
Пример неправильного использования:
spam (1)
Пример правильного использования:
dict['key'] = list[index]
Пример неправильного использования:
dict ['key'] = list [index]
Не используйте пробелы в конце строк.
Обозначьте бинарные операторы одним пробелом с обеих сторон для присваивания
(=
), сравнения (==, <, >, !=, <>, <=, >=, in, not in, is, is not
) и
Boolean-операторов (and, or, not
). Используйте свой здравый смысл при вставке
пробелов вокруг арифметических операторов (+
, -
, *
, /
, //
, %
, **
,
@
).
Пример правильного использования:
x == 1
Пример неправильного использования:
x<1
Никогда не используйте пробелы вокруг =
при передаче именованных аргументов
или определении значения параметра по умолчанию, за одним исключением:
когда используется аннотация типов, используйте
пробелы вокруг =
для значения параметра по умолчанию.
Примеры правильного использования:
def complex(real, imag=0.0): return Magic(r=real, i=imag)
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
Примеры неправильного использования:
def complex(real, imag = 0.0): return Magic(r = real, i = imag)
def complex(real, imag: float=0.0): return Magic(r = real, i = imag)
Не используйте пробелы для вертикального выравнивания символов в
последовательных строках, так как это очень тяжело поддерживать (
приминительно к :
, #
, =
, etc.):
Пример правильного использования:
foo = 1000 # комментарий
long_name = 2 # комментарий, который не следует выравнивать
dictionary = {
'foo': 1,
'long_name': 2,
}
Пример неправильного использования:
foo = 1000 # комментарий
long_name = 2 # комментарий, который не следует выравнивать
dictionary = {
'foo' : 1,
'long_name': 2,
}
Большинство файлов .py
не нужно начинать со строки#!
. Начните основной
файл программы с #!/usr/bin/env python3
(для поддержки виртуальных окружений)
или #!/usr/bin/python3
согласно
PEP-394.
Эта строка используется ядром для поиска интерпретатора Python, но игнорируется Python при импорте модулей. Она необходима только для файла, который будет выполняться напрямую.
Убедитесь, что вы используете правильный стиль для docstring уровня модуля, функции, метода и встроенных комментариев.
Python использует docstrings для документирования кода. Docstring - это строка,
которая является первым оператором в пакете, модуле, классе или функции. Эти
строки могут быть извлечены автоматически через атрибут __doc__
объекта и
используются pydoc
(попробуйте запустить pydoc
в своем модуле, чтобы
посмотреть, как он выглядит.) Всегда используйте формат трех двойных кавычек
"""
для строк документации (подробнее в
PEP 257).
Docstring должна быть организована в виде итоговой строки (одна физическая
строка, не превышающая 120 символов), оканчивающаяся точкой, вопросительным
знаком или восклицательным знаком. При написании дополнительных строк - они
должны быть отделены пустой строкой, за которой следует остальная часть
docstring, начиная с той же позиции курсора, что и первый символ
первой строки. Ниже приведены дополнительные рекомендации по форматированию
docstring.
Каждый файл должен содержать шаблон лицензии. Выберите соответствующий шаблон для лицензии, используемой в проекте (например, Apache 2.0, BSD, LGPL, GPL).
Файлы должны начинаться с docstring, описывающей содержимое и использование модуля. Например:
"""Однострочное описание модуля или программы, оканчивающееся точкой.
Оставьте одну пустую строку. Остальная часть этой строки документации должна
содержать полное описание модуля или программы. Опционально, она также может
содержать краткое описание экспортируемых классов и функций и/или примеры
использования.
Пример использования:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
В этом разделе «функция» означает метод, функцию или генератор.
Функция должна иметь docstring, если она не соответствует всем следующим критериям:
- не видна снаружи
- очень короткая
- очевидная
Docstring должен содержать достаточно информации для записи вызова функции без
чтения кода функции. Docstring должен быть описательным,
("""Выбирает строки из Большой таблицы."""
) а не императивным (
"""Получить строки из Большой таблицы."""
), за исключением дескрипторов
данных @property
, которые должны использовать тот же
стиль, что и атрибуты. Docstring должен описывать синтаксис вызова функции
и ее семантику, а не ее реализацию. Для сложного кода комментарии рядом с кодом
более уместны, чем использование docstring.
Метод, который переопределяет метод из базового класса, может иметь простую
строку документации, отправляющую читателя в строку документации своего
переопределенного метода, такую как """См. базовый класс."""
. Причина в том,
что нет необходимости во многих местах повторять документацию, которая уже
присутствует в docstring базового метода. Однако, если поведение
переопределяющего метода существенно отличается от переопределенного, или
необходимо указать подробности (например, задокументировать дополнительные
side-эффекты), для переопределенного метода требуется указать хотя бы эти
различия в docstring.
Определенные аспекты функции следует задокументировать в специальных разделах, перечисленных ниже. Каждый раздел начинается со строки заголовка, которая заканчивается двоеточием. Во всех разделах, кроме заголовка, должен быть отступ из двух или четырех пробелов (выбранный формат должен быть единым для файла). Эти разделы можно опустить в тех случаях, когда имя и сигнатура функции достаточно информативны, чтобы ее можно было точно описать с помощью однострочного docstring.
Args:
: Расположите все параметры в списке по имени. Описание должно следовать за
именем и разделяться двоеточием, за которым следует либо пробел, либо новая
строка. Если описание слишком длинное и не помещается в одну строку из 120
символов, используйте отступ на 2 или 4 пробела больше, чем имя параметра
(должно быть согласовано с остальными строками документации в файле).
Описание должно включать требуемый тип(ы), если код не содержит аннотации
соответствующего типа. Если функция принимает *foo
(списки аргументов
переменной длины) и/или **bar
(произвольные именованные аргументы), они
должны быть указаны как *foo
и **bar
.
Returns: (или Yields: для генераторов)
: Описывает тип и семантику возвращаемого значения. Если функция возвращает
только None
, этот раздел не является обязательным. Его также можно
пропустить, если docstring начинается с результата работы (например,
"""Возвращает строку из Bigtable как кортеж строк."""`) и этого достаточно
для описания возвращаемого значения.
Raises: : Выведите в список все исключения с их описаниями, относящиеся к интерфейсу. Используйте аналогичное имя исключения + двоеточие + пробел или новую строку и стиль дополнительного отступа, как описано в Args:. Вы не должны документировать исключения, которые возникают, если API, указанный в docstring, нарушается (потому что это парадоксальным образом приведет к нарушению API).
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Возвращает строки из Smalltable.
Извлекает строки, относящиеся к указанным ключам, из экземпляра
таблицы, представленного table_handle. Строковые ключи будут
закодированы в UTF-8.
Args:
table_handle: Открытый экземпляр smalltable.Table.
keys: Последовательность строк, представляющая собой ключ
каждой строки таблицы для выборки. Строковые ключи
будут закодированы в UTF-8.
require_all_keys: Необязательный; Если require_all_keys
установлен в True - будут возвращены только строки,
содержащие все ключи.
Returns:
Словарь соответствия ключей к соответствующим найденным
строкам таблицы. Каждая запист представлена как кортеж
строк. Например:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Возвращаемые ключи - всегда байты. Если ключ из аргумента
keys отсутствует в словаре, тогда эта строка не была найдена
в таблице (и require_all_keys должен был иметь значение False).
Raises:
IOError: Ошибка доступа к smalltable.
"""
Точно так же эта вариация Args:
с разрывом строки также допускается:
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Возвращает строки из Smalltable.
Извлекает строки, относящиеся к указанным ключам, из экземпляра
таблицы, представленного table_handle. Строковые ключи будут
закодированы в UTF-8.
Args:
table_handle:
Открытый экземпляр smalltable.Table.
keys:
Последовательность строк, представляющая собой ключ каждой
строки таблицы для выборки. Строковые ключи будут
закодированы в UTF-8.
require_all_keys:
Необязательный; Если require_all_keys установлен в True -
будут возвращены только строки, содержащие все ключи.
Returns:
Словарь соответствия ключей к соответствующим найденным
строкам таблицы. Каждая запист представлена как кортеж
строк. Например:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Возвращаемые ключи - всегда байты. Если ключ из аргумента
keys отсутствует в словаре, тогда эта строка не была найдена
в таблице (и require_all_keys должен был иметь значение False).
Raises:
IOError: Ошибка доступа к smalltable.
"""
Классы должны иметь docstring под определением класса.
Если у вашего класса есть общедоступные атрибуты, они
должны быть задокументированы в секции Attributes
и
следовать тому же формату, что и секции
Args
у функций.
class SampleClass:
"""Краткое описание класса.
Подробная информация о классе....
Подробная информация о классе....
Attributes:
likes_spam: Булевый флаг, указывающий на то,
нравится нам СПАМ или нет.
eggs: Целое число отложенных нами яиц.
"""
def __init__(self, likes_spam=False):
"""Инициализация SampleClass."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""Выполнение операции."""
Последнее место для комментариев - сложные части кода. Если вам придется объяснять это на следующем ревью кода, вы должны прокомментировать это прямо сейчас. Сложные операции получают несколько строк комментариев до начала операций. Неочевидные получают комментарии в конце строки.
# Мы используем взвешенный поиск по словарю, чтобы узнать, где
# i находится в массиве. Мы экстраполируем позицию на основе
# наибольшего числа в массиве и размера массива, а затем
# выполняем двоичный поиск, чтобы получить точное число.
if i & (i-1) == 0: # True, если i равна 0 или степени 2.
Чтобы улучшить читаемость, эти комментарии должны начинаться
через 2 пробела дальше от кода с символа комментария #
, за
которым следует как минимум один пробел перед текстом самого
комментария.
С другой стороны, никогда не описывайте код. Предполагайте, что человек, читающий код, знает Python (а не то, что вы пытаетесь сделать) лучше, чем вы.
# ПЛОХОЙ КОММЕНТАРИЙ: Теперь просмотрим массив b и убедимся,
# что всякий раз, когда встречается i, следующим элементом
# является i + 1
Обратите внимание на пунктуацию, орфографию и грамматику; хорошо написанные комментарии читать легче, чем плохо написанные.
Комментарии должны быть такими же удобочитаемыми, как и повествовательный текст, с правильными заглавными буквами и пунктуацией. Во многих случаях полные предложения читаются лучше, чем их фрагменты. Более короткие комментарии, такие как комментарии в конце строки кода, иногда могут быть менее формальными, но вы должны соответствовать своему стилю.
Хотя может быть неприятно, когда ревьюер кода указывает, что вы используете запятую, когда вам следует использовать точку с запятой, очень важно, чтобы исходный код сохранял высокий уровень ясности и читабельности. Правильная пунктуация, орфография и грамматика помогут в этом.
Используйте f-строки или метод format
для форматирования строк, даже если
все параметры являются строками. Метод format
в основном используется,
когда нельзя использовать f-строки (пример ниже), либо когда необходимо
отформатировать строку, сохраненную заранее в переменной.
Примеры правильного использования:
x = f'{a}{b}'
# Нежелательно, но допустимо
x = '{}, {}'.format(first, second)
x = f'name: {name}; score: {n}'
Примеры неправильного использования:
x = '%s %s' % (a, b) # ленивое форматирование не рекомендуется
x = first + ', ' + second
x = 'name: ' + name + '; score: ' + str(n)
# Экранированные символы нельзя использовать внутри выражения,
# подставляемого с помощью f-строки
x = f'log: {"\n".join([error1, error2])}'
Избегайте использования операторов +
и + =
для накопления строки в цикле.
В некоторых условиях соединение строк может привести к квадратичному, а не линейному времени работы. Несмотря на то,
что такие соединения строк могут быть оптимизированы на CPython, не стоит на это рассчитывать, так как это является
деталями реализации. Условия, при которых применяется эта оптимизация, предсказать непросто, и они могут измениться.
Вместо этого добавьте каждую подстроку в список и выполните ''.join
для него после завершения цикла (
или запишите каждую подстроку в буфер io.BytesIO
). У этих подходов всегда амортизированно-линейная сложность времени
выполнения.
Пример правильного использования:
items = ['<table>']
for last_name, first_name in employee_list:
items.append(f'<tr><td>{last_name}, {first_name}</td></tr>')
items.append('</table>')
employee_table = ''.join(items)
Пример неправильного использования:
employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
Будьте последовательны в выборе символа строковой кавычки в файле. Выберите '
или "
и придерживайтесь выбранного подхода. Можно использовать другой символ
кавычки в строке, чтобы избежать необходимости экранирования \\
внутри строки.
Пример правильного использования:
Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')
Пример неправильного использования:
Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")
Для многострочных строк предпочтительнее """
, чем '''
. Проекты могут
использовать '''
для всех многострочных строк без документации тогда и только
тогда, когда они также используют '
для обычных строк. Docstring должны
использовать """
всегда.
Многострочные строки не очень хорошо сочетаются с отступами в остальном коде.
Если вам нужно избежать встраивания лишних пробелов в строку,
используйте либо конкатенированные однострочные строки, либо многострочные
строки с textwrap.dedent()
для удаления начального пробела в каждой строке:
Пример неверного использования
long_string = """This is pretty ugly.
Don't do this.
"""
Пример правильного использования
long_string = """This is fine if your use case can accept
extraneous leading spaces."""
Пример правильного использования
long_string = ("And this is fine if you cannot accept\n" +
"extraneous leading spaces.")
Пример правильного использования
long_string = ("And this too is fine if you cannot accept\n"
"extraneous leading spaces.")
Пример правильного использования
import textwrap
long_string = textwrap.dedent("""\
This is also fine, because textwrap.dedent()
will collapse common leading spaces in each line.""")
Для логирования функций, которые ожидают шаблон-строку (с %-заполнителем) как их первый аргумент: Всегда в качестве первого аргумента передавайте строковый литерал (не f-строку!), а в качестве последующих аргументов - параметры шаблона. Некоторые реализации логирования сохраняют неразвернутую шаблон-строку именно как шаблон, не подставляя значения параметров сразу. Это также предотвращает трату времени на рендеринг сообщения для вывода которого не сконфигурирован логгер.
Пример правильного использования
import tensorflow as tf
logger = tf.get_logger()
logger.info('TensorFlow Version is: %s', tf.__version__)
Пример правильного использования
import os
from absl import logging
logging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))
homedir = os.getenv('HOME')
if homedir is None or not os.access(homedir, os.W_OK):
logging.error('Cannot write to home directory, $HOME=%r', homedir)
Пример неправильного использования
import os
from absl import logging
logging.info('Current $PAGER is:')
logging.info(os.getenv('PAGER', default=''))
homedir = os.getenv('HOME')
if homedir is None or not os.access(homedir, os.W_OK):
logging.error(f'Cannot write to home directory, $HOME={homedir!r}')
Сообщения об ошибках (такие как: строки сообщений об исключениях, например,
ValueError
, или сообщения показываемые пользователю) должны следовать
следующим трем правилам:
-
Сообщение должно точно отражать фактическое состояние ошибки.
-
Фрагменты сообщения об ошибке, вставленные из переменных должны быть легко определяемы.
-
Они должны позволять простую автоматическу обработку (например, парсинг с использованием grep).
Пример правильного использования
if not 0 <= p <= 1:
raise ValueError(f'Not a probability: {p!r}')
try:
os.rmdir(workdir)
except OSError as error:
logging.warning('Could not remove directory (reason: %r): %r',
error, workdir)
Пример неправильного использования
if p < 0 or p > 1: # PROBLEM: also false for float('nan')!
raise ValueError(f'Not a probability: {p!r}')
try:
os.rmdir(workdir)
except OSError:
# PROBLEM: Message makes an assumption that might not be true:
# Deletion might have failed for some other reason, misleading
# whoever has to debug this.
logging.warning('Directory already was deleted: %s', workdir)
try:
os.rmdir(workdir)
except OSError:
# PROBLEM: The message is harder to grep for than necessary, and
# not universally non-confusing for all possible values of `workdir`.
# Imagine someone calling a library function with such code
# using a name such as workdir = 'deleted'. The warning would read:
# "The deleted directory could not be deleted."
logging.warning('The %s directory could not be deleted.', workdir)
Явно закрывайте файлы и сокеты, когда завершаете работу с ними.
Если оставить файлы, сокеты или другие файловые объекты открытыми без необходимости, это может привести к следующим проблемам:
- Они могут потреблять ограниченные системные ресурсы, такие как файловые дескрипторы. Код, который работает со многими подобными объектами, может исчерпать эти ресурсы без необходимости, если они не возвращаются в систему сразу после использования.
- Открытие файлов может помешать другим действиям с ними, таким как перемещение или удаление.
- Файлы и сокеты, которые используются в программе, могут быть случайно прочитаны или записаны после того, как по логике, должны быть закрыты. Если они действительно будут закрыты, попытки чтения или записи из них вызовут исключения, что позволит быстрее узнать о проблеме.
Кроме того, хотя файлы и сокеты автоматически закрываются при удалении файлового объекта, привязка времени жизни файлового объекта к состоянию файла является плохой практикой:
- Нет никаких гарантий относительно того, когда среда выполнения действительно запустит деструктор файла. В разных реализациях Python используются разные методы управления памятью, такие как отложенная сборка мусора, которая может произвольно и бесконечно увеличивать время жизни объекта.
- Неожиданные ссылки на файл, например в глобальных объектах или при трассировке исключений, могут хранить его дольше, чем предполагалось.
Предпочтительный способ управления файлами - использование менеджера контекста
через with
:
with open("hello.txt") as hello_file:
for line in hello_file:
print(line)
Для файловых объектов, которые не поддерживают оператор with
, используйте
contextlib.closing()
:
import contextlib
with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
for line in front_page:
print(line)
Используйте комментарии TODO
для кода, который является временным,
краткосрочным решением или достаточно хорошим, но не идеальным.
Комментарий TODO
должен начинаться со строки TODO
заглавными буквами c
последующим двоеточием и номером задачи. Далее следует объяснение того,
что нужно делать.
Цель состоит в том, чтобы иметь согласованный формат TODO
, в котором можно
было бы выполнять поиск, чтобы узнать, как получить более подробную информацию.
TODO
здесь скорее является пометкой, в каком месте кода следует вносить
изменения. Следует иметь ввиду, что это место может быть результатом первого
приближения, а не досконального анализа проблемы, поэтому исправление не должно
быть обязательно в том же месте.
В ходе выполнения указанной в TODO
задачи комментарий должен быть удален.
# TODO: BOBUH-12345 Используйте "*" для повторения строк.
# TODO: BOZIK-54321 Измените это, чтобы использовать отношения.
Импорты должны размещаться в отдельных строках.
Примеры правильного использования:
import os
import sys
from typing import (
Mapping,
Sequence,
)
Примеры неправильного использования:
import os, sys
Импорт всегда помещается в начало файла, сразу после любых комментариев и docstring модуля и перед глобальными объектами и константами модуля. Импорты следует сгруппировать от наиболее общего к наименее общему:
-
Импорты стандартной библиотеки Python. Например:
import sys
-
Импорты внешних модулей или пакетов. Например:
import tensorflow as tf
-
Импорты суб-пакетов основного кода проекта. Например:
from otherproject.ai import mind
-
Устарело: специфичный для приложения импорт, который является частью того же суб-пакета верхнего уровня, что и этот файл. Например:
from myproject.backend.hgwells import time_machine
Вы можете найти старый стиль кода Google Python, который делает это, но это больше не требуется. Новому коду рекомендуется не обращать на это внимание. Просто относитесь к импорту суб-пакетов для конкретного приложения так же, как и к импорту других суб-пакетов.
Внутри каждой группы импорты должны быть отсортирован лексикографически, без
учета регистра, в соответствии с полным путем к пакету каждого модуля (
path
в from path import ...
). Между разделами импортов нужно вставить
пустую строку.
import collections
import queue
import sys
from absl import (
app,
)
from absl import (
flags,
)
import bs4
import cryptography
import tensorflow as tf
from book.genres import (
scifi,
)
from myproject.backend import (
huxley,
)
from myproject.backend.hgwells import (
time_machine,
)
from myproject.backend.state_machine import (
main_loop,
)
from otherproject.ai import (
body,
)
from otherproject.ai import (
mind,
)
from otherproject.ai import soul
# Older style code may have these imports down here instead:
#from myproject.backend.hgwells import time_machine
#from myproject.backend.state_machine import main_loop
Для автоматического форматирования импортов необходимо использовать пакет isort.
Обычно должен использоваться только один оператор в строке.
Однако, вы можете поместить результат теста в ту же строку, что и тест, только
если весь оператор умещается в одной строке. В частности, вы никогда не сможете
сделать это с try
/ except
, поскольку try
и except
не могут
одновременно уместиться в одной строке, но у вас может получиться это сделать
с выражением if
, если у него нет else
.
Пример правильного использования:
if foo: bar(foo)
Примеры неправильного использования:
if foo: bar(foo)
else: baz(foo)
try: bar(foo)
except ValueError: baz(foo)
try:
bar(foo)
except ValueError: baz(foo)
Если функция доступа выглядит тривиальной, вы должны использовать
общедоступные переменные вместо функций доступа, чтобы избежать дополнительных
затрат на вызовы функций в Python. Когда объем функциональности увеличится, вы
сможете использовать property
, чтобы синтаксис оставался согласованным.
С другой стороны, если доступ сложен или стоимость доступа к переменной
значительна, вы должны использовать вызовы функций, (следуя правилам
Именования) такие как get_foo()
или set_foo()
. Если
предыдущее поведение разрешало доступ через свойство, не привязывайте новые
функции доступа к свойству. Любой код, которые по прежнему пытается получить
доступ к переменной старым методом, должен явным образом сломаться, чтобы
стало известно об изменении уровня сложности.
имя_модуля
, имя_пакета
, ИмяКласса
, имя_метода
, ИмяИсключения
,
имя_функции
, ИМЯ_КОНСТАНТЫ
, имя_глобальной_переменной
,
имя_переменной_объекта
, имя_параметра_функций
, имя_локальной_переменной
.
Имена функций, имена переменных и имена файлов должны быть описательными; избегайте сокращений. В частности, не используйте сокращения, которые являются двусмысленными или незнакомыми читателям за пределами вашего проекта, и не сокращайте слова, удаляя буквы в слове.
Всегда используйте расширение имени файла .py
. Никогда не используйте дефис.
-
односимвольные имена, за исключением специально разрешенных случаев:
- счетчики или итераторы (например
i
,j
,k
,v
и др.) e
как идентификатор исключения в операторахtry/except
f
как дескриптор файла в операторахwith
Помните, что нельзя злоупотреблять односимвольными именами. Фактически, информативность должна быть пропорциональна области видимости имени. Например,
i
может быть хорошим именем для 5-строчного блока кода, но в пределах нескольких вложенных областей видимости оно будет слишком расплывчатым.Никогда не используйте символы l (маленькая латинская буква «эль»), O (заглавная латинская буква «о») или I (заглавная латинская буква «ай») как однобуквенные идентификаторы.
- счетчики или итераторы (например
-
дефис (
-
) в любом имени пакета/модуля -
имена
__с_двойным_подчеркиванием_в_начале_и_в_конце__
(зарезервированы Python) -
оскорбительные термины
-
имена, содержащие типы переменных, если на то нет причин (для примера:
id_to_name_dict
)
-
«Внутренний» означает внутренний по отношению к модулю, защищенный или приватный класс.
-
Добавление одиночного подчеркивания (
_
) в некоторой степени поддерживает защиту переменных и функций модуля (линтеры будут отмечать защищенный доступ к членам модуля). При добавлении двойного подчеркивания (__
) к переменной или методу экземпляра - он фактически делает переменную или метод закрытым для своего класса (с использованием изменения имени); мы не рекомендуем их использование, поскольку это влияет на читаемость и тестируемость и при этом атрибут класса не является по-настоящему частным. -
Поместите связанные классы и функции верхнего уровня вместе в модуль. В отличие от Java, нет необходимости ограничивать себя одним классом на модуль.
-
Используйте CapWords для имен классов, но lower_with_under.py для имен модулей. Хотя есть несколько старых модулей с именем CapWords.py, теперь это такое именование не рекомендуется, потому что это сбивает с толку, когда модуль назвали в честь класса. ("Подождите -- должен ли я написать
import StringIO
илиfrom StringIO import StringIO
?") -
Подчеркивание может появляться в именах методов unittest, начинающихся с
test
, для разделения логических компонентов имени, даже если эти компоненты используют CapWords. Один из возможных шаблонов - этоtest<MethodUnderTest>_<state>
; например, testPop_EmptyStack - это нормально. Не существует единственного правильного способа назвать методы тестирования.
Имена файлов Python должны иметь расширение .py
и не должны содержать
дефисов (-
). Это позволяет импортировать и тестировать их. Если вы хотите,
чтобы исполняемый файл был доступен без расширения, используйте символическую
ссылку или простой bash-скрипт, содержащий exec "$0.py" "$@"
.
3.16.4 Принципы, основанные на Рекомендациях Гвидо(https://en.wikipedia.org/wiki/Guido_van_Rossum)
Тип | Общедоступный | Внутренний |
---|---|---|
Пакеты | lower_with_under |
|
Модули | lower_with_under |
_lower_with_under |
Классы | CapWords |
_CapWords |
Исключения | CapWords |
|
Функции | lower_with_under() |
_lower_with_under() |
Глобальные/Классовые константы | CAPS_WITH_UNDER |
_CAPS_WITH_UNDER |
Глобальные/Классовые переменные | lower_with_under |
_lower_with_under |
Переменные экземпляра | lower_with_under |
_lower_with_under (protected) |
Имена методов | lower_with_under() |
_lower_with_under() (protected) |
Параметры функции/метода | lower_with_under |
|
Локальные переменные | lower_with_under |
В Python pydoc
, а также модульные тесты требуют, чтобы модули были
импортируемыми. Если файл предназначен для использования в качестве исполняемого
файла, его основная функциональность должна быть в функции main()
, и ваш код
должен всегда проверять if __name__ == '__main__'
перед выполнением вашей
основной программы, чтобы он не выполнялся при импорте модуля.
При использовании absl, используйте
app.run
:
from absl import app
...
def main(argv):
# process non-flag arguments
...
if __name__ == '__main__':
app.run(main)
В противном случае, используйте:
def main():
...
if __name__ == '__main__':
main()
Весь код верхнего уровня будет выполняться при импорте модуля. Будьте
осторожны, не вызывайте функции, не создавайте объекты и не выполняйте другие
операции, которые не должны выполняться, когда файл обрабатывается pydoc
.
Отдавайте предпочтение небольшим и точечным функциям.
Мы понимаем, что иногда уместны длинные функции, поэтому жестких ограничений на длину функции нет. Если функция превышает 40 строк, подумайте, можно ли ее разбить, не повредив структуру программы.
Даже если ваша длинная функция сейчас работает отлично, кто-то, изменив ее через несколько месяцев, может добавить новое поведение. Это может привести к ошибкам, которые потом будет трудно найти. Если ваши функции будут короткими и простыми, то другим людям будет легче читать и изменять ваш код.
Не пугайтесь изменять существующий код: подумайте о разбиении функции на более мелкие и управляемые части, если работа с такой функцией оказывается затруднительной - вы обнаруживаете, что ошибки трудно отлаживать - или если вы хотите использовать ее часть в нескольких разных контекстаx.
- Ознакомьтесь с PEP-484.
- В методах аннотируйте self или cls только если это необходимо для
правильной информации о типе, например:
@classmethod def create(cls: Type[T]) -> T: return cls()
- Если какая-либо другая переменная или возвращаемый тип не должны быть явно
объявлены, используйте
Any
. - От вас не требуется аннотировать все функции в модуле:
- Аннотируйте свои общедоступные API.
- Используйте здравый смысл, чтобы найти хороший баланс между безопасностью и ясностью с одной стороны, и гибкостью с другой.
- Аннотируйте код, который подвержен ошибкам, связанным с типом (с учетом предыдущих ошибок или сложности).
- Аннотируйте код, который трудно понять.
- Аннотируйте код, когда он становится стабильным с точки зрения типов. Во многих случаях вы можете аннотировать все функции в давно написанном коде, не теряя при этом в гибкости.
Постарайтесь следовать существующим правилам отступов.
После аннотирования многие сигнатуры функций примут вид «по одному параметру на строку».
def my_method(self,
first_var: int,
second_var: Foo,
third_var: Optional[Bar]) -> int:
...
Всегда предпочитайте разрыв между переменными, а не между именами переменных и аннотациями типов. Однако, если все умещается в одну строку, используйте этот вариант.
def my_method(self, first_var: int) -> int:
...
Если комбинация имени функции, последнего параметра и возвращаемого типа слишком длинная, сделайте отступ на 4 в новой строке.
def my_method(
self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
...
Если тип возвращаемого значения не помещается в той же строке, что и последний
параметр, предпочтительным способом является отступ параметров на 4 в новой
строке и выравнивание закрывающей круглой скобки с def
.
Yes:
def my_method(
self, other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
...
pylint
позволяет переместить закрывающую скобку на новую строку и выровнять
ее с открывающей, но это менее читаемо:
def my_method(self,
other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
...
Как и в примерах выше, постарайтесь не разбивать типы. Однако, иногда они слишком длинные, чтобы находиться в одной строке (старайтесь, чтобы подтипы не прерывались).
def my_method(
self,
first_var: Tuple[List[MyLongType1],
List[MyLongType2]],
second_var: List[Dict[
MyLongType3, MyLongType4]]) -> None:
...
Если одно имя и тип слишком длинные, рассмотрите возможность использования алиаса для типа. В крайнем случае следует разбить двоеточие и сделать отступ на 4:
def my_function(
long_variable_name:
long_module_name.LongTypeName,
) -> None:
...
Ни в коем случае не делаете следующим образом:
def my_function(
long_variable_name: long_module_name.
LongTypeName,
) -> None:
...
Если вам требуется использовать имя класса из того же модуля, который еще не определен - например, если вам нужен класс внутри объявления класса, или если вы используете класс, определенный ниже - используйте строку для имени класса.
class MyClass:
def __init__(self,
stack: List["MyClass"]) -> None:
Согласно
PEP-008,
используйте пробелы вокруг =
только для аргументов, которые имеют как
аннотацию типа, так и значение по умолчанию.
Пример правильного оформления:
def func(a: int = 0) -> int:
...
Пример неправильного оформления:
def func(a:int=0) -> int:
...
В системе типов Python NoneType
является типом "первого класса", и для целей
типизирования None
является псевдонимом для NoneType
. Если аргумент может
иметь значение None
, его нужно объявить! Вы можете использовать Union
, но
если есть только один другой тип, используйте Optional
.
Используйте явный Optional
вместо неявного Optional
. В более ранних версиях
PEP 484 a: Text = None
можно было интерпретировать как
a: Optional[Text] = None
, но это больше не является предпочтительным
поведением.
Примеры правильного форматирования:
def func(a: Optional[Text], b: Optional[Text] = None) -> Text:
...
def multiple_nullable_union(a: Union[None, Text, int]) -> Text
...
Примеры неправильного форматирования:
def nullable_union(a: Union[None, Text]) -> Text:
...
def implicit_optional(a: Text = None) -> Text:
...
Вы можете объявлять псевдонимы сложных типов. Имя псевдонима должно быть CapWorded. Если псевдоним используется только в этом модуле, он должен быть _Приватным.
Например, если имя модуля вместе с именем типа слишком длинное:
_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[Text, List[Tuple[int, int]]]
Другими примерами являются сложные вложенные типы и несколько переменных, возвращаемых функцией (в виде кортежа).
Вы можете отключить проверку типов в строке специальным комментарием # type: ignore
.
В pytype
есть опция отключения для определенных ошибок (аналогично lint):
# pytype: disable=attribute-error
Если внутренняя переменная имеет тип, который трудно или невозможно вывести, вы можете указать ее тип двумя способами.
Комментарий с типом - используйте комментарий # type:
в
конце строки:
a = SomeUndecoratedFunction() # type: Foo
Анотация присвоения - используйте двоеточие и укажите тип между именем переменной и значением, как с аргументами функции:
a: Foo = SomeUndecoratedFunction()
Типизированные списки могут содержать объекты только одного типа. Типизированные кортежи могут иметь один повторяющийся тип или заданное количество элементов с разными типами. Последний обычно используется как тип, возвращаемый функцией.
a = [1, 2, 3] # type: List[int]
b = (1, 2, 3) # type: Tuple[int, ...]
c = (1, "2", 3.5) # type: Tuple[int, Text, float]
Система типов Python имеет
generic-типы. Фабричная
функция TypeVar
- это стандартный способ их использования. Например:
from typing import List, TypeVar
T = TypeVar("T")
...
def next(l: List[T]) -> T:
return l.pop()
TypeVar
может быть ограничен:
AddableType = TypeVar("AddableType", int, float, Text)
def add(a: AddableType, b: AddableType) -> AddableType:
return a + b
Стандартным предопределенным типом переменной в модуле typing
является
AnyStr
. Используйте его для нескольких аннотаций, которые могут быть bytes
или unicode
, и все они должны быть одного типа.
from typing import AnyStr
def check_length(x: AnyStr) -> AnyStr:
if len(x) <= 42:
return x
raise ValueError()
Правильный способ аннотирования строк зависит от того, для каких версий Python предназначен код.
Для кода Python 3 предпочтительно использовать str
. Text
также применим.
Будьте последовательны в использовании того или иного способа.
Для кода, совместимого с Python 2, используйте Text
. В некоторых редких
случаях, str
может иметь смысл; обычно для обеспечения совместимости, когда
типы возвращаемых значений не совпадают между двумя версиями Python. Избегайте
использования unicode
: его нет в Python 3.
Причина этого несоответствия заключается в том, что str
имеет различное
значение в зависимости от версии Python.
Пример неправильного использования:
def py2_code(x: str) -> unicode:
...
Для кода, который имеет дело с бинарными данными, используйте bytes
:
def deals_with_binary_data(x: bytes) -> bytes:
...
Для кода, совместимого с Python 2, который обрабатывает текстовые данные (str
или unicode
для Python 2, str
для Python 3), используйте Text
. Для
Python 3 кода, который обрабатывает текстовые данные, используйте str
.
from typing import Text
...
def py2_compatible(x: Text) -> Text:
...
def py3_only(x: str) -> str:
...
Если тип может быть байтовым или текстовым, используйте Union
, с
соответствующим типом текста.
from typing import Text, Union
...
def py2_compatible(x: Union[bytes, Text]) -> Union[bytes, Text]:
...
def py3_only(x: Union[bytes, str]) -> Union[bytes, str]:
...
Если все строковые типы определенной функции всегда одинаковы, например, если тип возвращаемого значения совпадает с типом аргумента (как в приведенном выше коде), используйте AnyStr.
Соблюдение этих правил упростит процесс переноса кода на Python 3.
Для классов из модуля typing
всегда импортируйте сам класс. Явно разрешено
импортировать несколько определенных классов в одной строке из модуля typing
.
Например:
from typing import (
Any,
Dict,
Optional,
)
Учитывая, что этот способ импорта из typing
добавляет элементы в локальное
пространство имен, любые имена в typing
должны обрабатываться так же, как
ключевые слова, и не переопределяться в вашем типизированном или не
типизированном коде Python. Если есть конфликт между типом и существующим
именем в модуле, импортируйте его, используя import x as y
.
from typing import (
Any as AnyType,
)
Используйте условные импорты только в исключительных случаях, когда во время выполнения необходимо избегать дополнительного импорта, необходимого для проверки типов. Этот шаблон не рекомендуется к использованию; альтернативы, такие как рефакторинг кода, чтобы разрешить импорт верхнего уровня, должны быть предпочтительны в данном случае.
Импорты, которые необходимы только для аннотаций типов, могут быть помещены в
блок if TYPE_CHECKING:
.
- На условно импортированные типы нужно ссылаться как на строки, чтобы они были совместимы с Python 3.6, где аннотации фактически выполняются.
- Следует определять только те объекты, которые используются исключительно для типизации; это правило распространяется на псевдонимы. В противном случае это будет ошибка времени выполнения, поскольку модуль не был импортирован во время выполнения.
- Блок должен быть размещен сразу после обычных импортов.
- В списке импорта для типизации не должно быть пустых строк.
- Отсортируйте этот список, как если бы это был обычный список импортов.
import typing
if typing.TYPE_CHECKING:
import sketch
def f(x: "sketch.Sketch"): ...
Циклические зависимости, вызванные типизацией - это "запах кода". Такой код - хороший кандидат на рефакторинг. Хотя технически возможно сохранить циклические зависимости, различные системы сборки не позволят вам сделать это, потому что каждый модуль должен зависеть от другого.
Замените модули, которые создают циклические зависимости, на Any
. Установите
псевдоним с понятным именем, и используйте настоящее имя
типа из этого модуля (любой атрибут у Any
считается за Any
). Определения
псевдонимов следует отделять от последнего импорта одной строкой.
from typing import Any
some_mod = Any # some_mod.py импортирует этот модуль.
...
def my_method(self, var: "some_mod.SomeType") -> None:
...
При аннотации предпочитайте указывать параметры типа для универсальных типов; в
противном случае, предполагается, что параметры generic-типов будут равны
Any
.
def get_names(employee_ids: List[int]) -> Dict[int, Any]:
...
# Обе функции интерпретируются как get_names(employee_ids: List[Any]) -> Dict[Any, Any]
def get_names(employee_ids: list) -> Dict:
...
def get_names(employee_ids: List) -> Dict:
...
Если лучшим параметром типа для generic-типа является Any
, сделайте его
явным, но помните, что во многих случаях TypeVar
может
быть более подходящим:
def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
"""Возвращает соответствие ID и имен сотрудников для соответствующих ID сотрудников."""
T = TypeVar('T')
def get_names(employee_ids: List[T]) -> Dict[T, Text]:
"""Возвращает соответствие ID и имен сотрудников для соответствующих ID сотрудников."""
БУДЬТЕ ПОСЛЕДОВАТЕЛЬНЫМИ.
Если вы редактируете код, уделите несколько минут, чтобы взглянуть на код вокруг вас и определите его стиль. Если они используют пробелы вокруг всех своих арифметических операторов, вам тоже следует. Если вокруг комментариев есть маленькие прямоугольники с хеш-метками, сделайте так, чтобы ваши комментарии тоже содержали маленькие прямоугольники с хеш-метками.
Смысл руководства по стилю состоит в том, чтобы иметь общий словарь кодирования, чтобы люди могли сосредоточиться на том, что вы говорите, а не на том, как вы это говорите. Мы представляем здесь глобальные правила стиля, чтобы люди знали словарный запас, но локальный стиль также важен. Если код, который вы добавляете в файл, сильно отличается от существующего кода вокруг него, это сбивает читателей с ритма, когда они будут читать его. Избегайте этого.
-
Docstring - это строковый литерал, являющийся первой инструкцией в определении модуля, функции, класса или метода. Такая строка становится доступна при обращении с специальному атрибуту
__doc__
этого объекта. -
Pythonic (Pythonic-way) - это код или подход к кодированию, который не только позволяет правильно выстроить синтаксис, но и следует соглашениям сообщества Python, а также использует язык именно так, как и задумывалось изначально.