Как правильно ничего не делать в Python: инструкция pass
Говорят, что в Python нет ничего проще ключевого слова pass – всего лишь инструкция «ничего не делать», чтобы соблюсти синтаксис языка. Однако не всегда pass служит заглушкой – есть и более интересные применения. Обсудить Публикация представляет собой сокращенный перевод статьи Моше Задка The pass Statement: How to Do Nothing in Python. *** В Python ключевое слово Иногда В этом туториале мы изучим: Синтаксис Python предполагает, что в некоторых случаях после двоеточия новые блоки кода идут с отступом. Например, после объявления цикла Тело условия или цикла не может быть пустым: Чтобы структура кода осталась корректной, нужно использовать инструкцию В первом случае из-за невалидного синтаксиса вызывается исключение, во втором – Есть много ситуаций, в которых инструкция При продумывании макроструктур программы не нужно отвлекаться на низкоуровневые решения. Инструкция Представьте: нужна функция, которая находит среднюю часть строки, записывает результат в файл и возвращает его: Но вам пока не нужна вызываемая функция Можно закомментировать вызов Теперь функцию Другой вариант использования Такие структурные скелеты выстраивают логику и порядок ветвления. В приведенном примере первый оператор После того как вы прониклись логикой задачи, можно решить, будет ли использоваться Функция напрямую печатает строки, однако из-за этого ее будет неудобно тестировать. Разумная альтернатива – написать функцию, возвращающую строковое значение: Выявление основных условий и структуры с помощью Подход полезен и при написании классов. Если вы пока не до конца понимаете предметную область, используйте Вообразим, что мы реализуем класс Такой код даже позволит создавать экземпляры класса. Если у вас есть условие Например, вы хотите запустить этот код с некоторыми проблемными данными и посмотреть, почему так много значений не являются Другая ситуация, в которой мы ходим закомментировать код во время устранения неполадок, – когда скрываемый код имеет нежелательный побочный эффект, например, отправку электронной почты или обновление счетчика. Запуская код в отладчике, можно установить маркер на позиции, где отладчик остановится и позволит проверить состояние программы. Многие отладчики допускают выставить точку останова, которая срабатывает только при выполнении условия. Например, можно установить точку останова в цикле for так, чтобы отладчик срабатывал, только если переменная имеет значение Хотя инструкция Распространенная ситуация – код определяет класс, наследуемый от класса, требуется переопределение метода. Вполне вероятно, что новый метод не должен делать или ему даже нужно запретить что-то делать: Необходимые функции и методы в этом случае по-прежнему поддерживаются и не вызывают исключений при вызове. Python поддерживает концепцию наследования исключений. Например, встроенное исключение При поиске несуществующего ключа в словаре возникает исключение Иногда возникает задача вызова исключений, наследуемых от определенного класса и соответствующих некоторому набору инструкций по обработке исключений. Сами исключения не выполняют никаких действий, а служат простейшими сигнализаторами: произошла такая-то ошибка. Простой пример: задача проверки паролей. Прежде чем пользователь сможет изменить пароль на веб-сайте, программа на сервере тестирует пароль на соответствие правилам: Примечание Этот пример предназначен исключительно для иллюстрации семантики и методов Python. Для получения дополнительной информации изучите рекомендации Национального института стандартов и технологий (NIST) и исследования, на которых они основаны. Каждая из соответствующих ошибок должна вызывает собственное исключение. Следующий код реализует указанные правила: password_checker.py Эта функция вызовет исключение, если пароль не соответствует какому-либо из описанных правил. Более реалистичный пример отметил бы все несоблюденные правила, но такая задача выходит за рамки данного руководства. В этом примере Некоторые методы в классах существуют не для того, чтобы их вызывать. Иногда они просто определенным образом помечают сам класс. Представим, что вы пишете код для анализа шаблонов использования веб-сервера. Требуется различать запросы, поступающие от пользователей, вошедших в систему, и запросы от неаутентифицированных подключений. Ситуацию можно смоделировать, имея суперкласс Примечание Название модуля стандартной библиотеки Python Хотя реалистичный класс Классы с декораторами методов Есть и другие примеры использования таких маркеров вне стандартной библиотеки Python. Например, они используются в пакете Инструкция Основная причина, почему стоит избегать использования таких инструкций вместо Есть одно важное исключение из идиомы использования Даже если строка документации не является обязательной, часто она является хорошей заменой инструкции Примечание Строки документации кода обычно описывают код более тщательно, чем приведенные примеры. Во всех приведенных случаях строка документации делает код понятнее, а работу с ним – более удобной. В pyi-файлах рекомендуется использовать в качестве выражения многоточие ( Первоначально объект Эта функция не только ничего не делает, но и находится в файле, который интерпретатор Python обычно не запускает. В тех случаях, когда функции или методы пусты, потому что они никогда не выполняются, иногда лучшим телом будет вызов исключения Порой использование оператора При использовании Например, вы хотите быть уверены, что файл не существует и используете При вызове исключения Примечание Игнорируя исключения, важно соблюдать осторожность. Исключение обычно подразумевает, что произошло что-то непредвиденное и требуется некоторая последовательность действий для решения проблемы. Игнорирование исключений всегда должно быть чем-то оправдано. Стоит заметить, что в схожих ситуациях оператор Вы также можете использовать диспетчер контекста Например, если вы хотите, чтобы приведенная выше функция Здесь происходит игнорирование исключения В этом примере порядок инструкций При обработке длинных цепочек Представьте, что рекрутер попросил вас написать fizz-buzz с такимиусловиями: Как и во всех вопросах по кодингу, есть много способов решить эту проблему. Один из них – использовать цикл Цепочка Теперь вы знаете, что собой представляет инструкция Если вам понравился материал этой статьи, обратите внимание на следующие публикации:pass
– самостоятельная инструкция, которая буквально ничего не делает. Она даже отбрасывается на этапе компиляции байт-кода. В чем же толк от такого оператора-бездельника?pass
можно встретить в финальном коде на продакшене, но чаще инструкцию используют в процессе разработки. Заметим, что в некоторых случаях сделать что-то – лучше, чем ничего, и pass
является не лучшим решением.pass
и чем полезна эта инструкция;pass
в продакшене;pass
в разработке;Python и синтаксис pass
for
или условия if
:
>>> for x in [1, 2, 3]: ... y = x + 1 ... print(x, y) ... 1 2 2 3 3 4
>>> if 1 + 1 == 3: ... File "<stdin>", line 2 ^ IndentationError: expected an indented block
pass
:
>>> if 1 + 1 == 3: ... pass ...
pass
позволяет соблюсти требования Python.Временное использование pass
pass
может быть полезна в процессе разработки, даже если она не появится в окончательной версии кода. Подобно строительным лесам pass
может поддерживать структуру программы, прежде чем ее заменят на что-то дельное.Будущий код
pass
помогает оформить ключевые конструкции, а потом вернуться к деталям.
def get_and_save_middle(data, fname): middle = data[len(data)//3:2*len(data)//3] save_to_file(middle, fname) return middle
save_to_file()
– в первую очередь вы хотите проверить, нет ли ошибки неучтенной единицы. Однако функции save_to_file()
еще не существует – при ее вызове будет вызвано исключение.save_to_file()
, но тогда придется держать в уме: это не просто комментарий – соответствующую функцию когда-то придется реализовать. Лучше сделать заготовку сразу же:
def save_to_file(data, fname): pass # TODO: заполнить позже
get_and_save_middle()
можно тестировать.pass
– когда мы пишем сложную структуру управления потоком и нужен заполнитель для будущего кода. Например, для реализации fizz-buzz полезно сначала набросать структуру кода:
if idx % 15 == 0: pass # Fizz-Buzz elif idx % 3 == 0: pass # Fizz elif idx % 5 == 0: pass # Buzz else: pass # Idx
if
должен проверять делимость на 15, потому что любое число, которое делится на 15, также делится на 5 и 3. Предварительное понимание общей структуры полезно независимо от реализации конкретного вывода.print()
прямо в коде:
def fizz_buzz(idx): if idx % 15 == 0: print("fizz-buzz") elif idx % 3 == 0: print("fizz") elif idx % 5 == 0: print("buzz") else: print(idx)
def fizz_buzz(idx): if idx % 15 == 0: return "fizz-buzz" elif idx % 3 == 0: return "fizz" elif idx % 5 == 0: return "buzz" else: return str(idx)
pass
позволяет лучше понять, как впоследствии должна работать программа.pass
, чтобы сначала набросать макет и представить архитектуру.Candy
, но необходимые свойства пока неочевидны. Впоследствии понадобится тщательный анализ требований, но для начала реализации прочих составляющих программы достаточно отобразить, что класс пока не готов:
class Candy: pass
Закомментированный код
if… else
, бывает полезно закомментировать одну из ветвей. В следующем примере expensive_computation()
запускает длительно выполняющийся код, например, перемножение больших массивов чисел. В процессе отладки может потребоваться временно закомментировать вызов expensive_computation()
.
def process(context, input_value): if input_value is not None: expensive_computation(context, input_value) else: logging.info("skipping expensive: %s", input_value)
None
, проверив описание в журналах. Пропуск дорогостоящих расчетов ускорит тестирование.
def process(context, input_value): if input_value is not None: # Временно невыполняемые длительные расчеты # expensive_computation(context, input_value) # Добавляем pass, чтобы сделать код валидным pass else: logging.info("skipping expensive: %s", input_value)
Маркеры для отладчиков
None
. Так можно увидеть, почему этот случай обрабатывается неправильно. Например, в следующей строке отладчик срабатывает, если строка является палиндромом.
for line in filep: if line == line[::-1]: pass # Устанавливаем здесь breakpoint process(line)
pass
ничего не делает, она позволяет установить здесь маркер. Теперь код можно запустить в отладчике и отлавливать строки-палиндромы.Пустые функции и методы
>>> def ignore_arguments(record, status): ... pass ...
class DiscardingIO: def write(self, data): pass
Пустые классы на примере исключений
LookupError
является родительским для KeyError
:
>>> empty={} >>> try: ... empty["some key"] ... except LookupError as exc: ... print("got exception", repr(exc)) ... got exception KeyError('some key') >>> issubclass(KeyError, LookupError) True
KeyError
. Исключение KeyError
перехватывается, хотя в инструкции except
указано LookupError
. Так происходит потому, что KeyError
является подклассом LookupError
.?
!
. и др.
).
class InvalidPasswordError(ValueError): pass class ShortPasswordError(InvalidPasswordError): pass class NoNumbersInPasswordError(InvalidPasswordError): pass class NoSpecialInPasswordError(InvalidPasswordError): pass def check_password(password): if len(password) < 8: raise ShortPasswordError(password) for n in "0123456789": if n in password: break else: raise NoNumbersInPasswordError(password) for s in "?!.": if s in password: break else: raise NoSpecialInPasswordError(password)
>>> from password_checker import check_password >>> def friendly_check(password): ... try: ... check_password(password) ... except InvalidPasswordError as exc: ... print("Invalid password", repr(exc)) ... >>> friendly_check("hello") Invalid password ShortPasswordError('hello') >>> friendly_check("helloworld") Invalid password NoNumbersInPasswordError('helloworld') >>> friendly_check("helloworld1") Invalid password NoSpecialInPasswordError('helloworld1')
перехватывает только friendly_check()
поскольку другие исключения типа InvalidPasswordError
могут представлять исключения, порождаемые в самой программе проверки ошибки. Функция печататет имя и значение исключения, соответствующее правилу. Оператор ValueError
pass
позволил без особых сложностей определить четыре класса исключений.Маркирующие методы
Origin
с двумя подклассами: LoggedIn
и NotLoggedIn
. Каждый запрос должен исходить либо из источника LoggedIn
, либо из NotLoggedIn
, но ничто не должно напрямую создавать экземпляр класса Origin
. Вот минималистичная реализация:
import abc class Origin(abc.ABC): @abc.abstractmethod def description(self): # Этот метод никогда не будет вызван pass class NotLoggedIn(Origin): def description(self): return "unauthenticated connection" class LoggedIn(Origin): def description(self): return "authenticated connection"
abc
соответствует сокращению от abstract base classes. Модуль помогает определять классы, которые не предназначены для создания экземпляров, а служат базой для других классов. Origin
выглядел бы сложнее, в этом примере показана его основа. Метод Origin.description()
никогда не будет вызван – все подклассы его переопределяют.
>>> Origin() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Origin with abstract... >>> logged_in.description() 'authenticated connection' >>> not_logged_in.description() 'unauthenticated connection'
abstractmethod
не могут быть созданы. Любой объект, имеющий Origin
в качестве суперкласса, будет экземпляром класса, который переопределяет description()
. Из-за этого тело в Origin.description()
не имеет значения и его можно заменить инструкцией pass
.zope.interface
для обозначения методов интерфейса и в automat
для входных данных конечного автомата. Во всех этих случаях классы должны иметь методы, но никогда не вызывают их.Альтернативы pass
pass
– не единственный способ «ничего не делать». Любое выражение в Python это валидная инструкция, как и любая константа. Фактически следующие инструкции тоже сами по себе ничего не делают:None
True
0
"hello I do nothing"
pass
– они не идиоматичны. Люди, читающие ваш код, не сразу поймут, зачем вы их использовали. Хотя для записи инструкции pass
требуется больше символов, чем, скажем для 0
, она явным образом показывает, что блок кода намеренно оставлен пустым.Docstrings
pass
в качестве инструкции бездействия. В классах, функциях и методах использование константного строкового выражения приведет к тому, что выражение будет использоваться как атрибут объекта .__ doc__
. Этот атрибут используется функцией интерпретатора help()
, различными генераторами документации и многими IDE. pass
в пустом блоке, так как более полно описывает назначение блока:
class StringReader(Protocol): def read(self, length: int) -> str: """ Считывает строку. """ class Origin(abc.ABC): @abc.abstractmethod def description(self): """ Человекочитаемое описание источника. """ class TooManyOpenParens(ParseError): """ Не все круглые скобки закрыты. """ class DiscardingIO: def write(self, data): """ Игнорируем данные. """
Ellipsis
...
). Эта константа определяется с помощью синглтона Ellipsis
:
>>> ... Ellipsis >>> x = ... >>> type(x), x (<class 'ellipsis'>, Ellipsis)
Ellipsis
использовался для создания многомерных срезов. Однако теперь это также рекомендуемый синтаксис для заполнения блоков в stub-файлах c расширением .pyi
:
# В `.pyi` файле: def add(a: int, b: int)-> int: ...
Вызов исключения
raise NotImplementedError("this should never happen")
. Вызов исключения в этом случае даст дополнительную информацию.Перманентное использование pass
pass
не является временным – инструкция остается в окончательной версии работающего кода. В таких случаях нет более подходящего решения для заполнения пустого блока, чем использование pass
.Применение pass в try … except
try ... except
для определенных исключений нет необходимости как-либо его обрабатывать – главное, чтобы программа продолжала работу. В такой ситуации pass
как нельзя кстати.os.remove()
. Однако функция не только удаляет файл, но и вызывает исключение, если файл отсутствует. Но если файла нет, в нашей задаче нет и необходимости вызывать исключение:
import os def ensure_nonexistence(fname): try: os.remove(fname) except FileNotFoundError: pass
FileNotFoundError
используем pass
, чтобы не блокировать остальной код.pass
часто заменяется записью в журнал. Однако этого не требуется, если ошибка ожидаема и легко интерпретируема.contextlib.suppress()
для подавления исключения. Если нужно обрабатывать одни исключения, игнорируя другие, то проще использовать инструкцию pass
.ensure_nonexistence()
работала и с каталогами, и с файлами, можно использовать следующий подход:
import os import shutil def ensure_nonexistence(fname): try: os.remove(fname) except FileNotFoundError: pass except IsADirectoryError: shutil.rmtree(fname)
и обработка исключения FileNotFoundError
.IsADirectoryError
не имеет значения, поскольку оба исключения except
и FileNotFoundError
наследуются от IsADirectoryError
. Если бы здесь был еще более общий случай, обрабатывающий OSError
, его нужно было поставить после более специфичных вариантов.OSError
Использование pass в цепочках if … elif
if… elif
в каком-то из вариантов бывает просто ничего не нужно делать, но вы не можете пропустить этот elif
, потому что без него результат станет некорректным."twist"
."fizz"
."buzz"
.for
с цепочкой, которая имитирует само описание:
for x in range(100): if x % 20 == 0: print("twist") elif x % 15 == 0: pass elif x % 5 == 0: print("fizz") elif x % 3 == 0: print("buzz") else: print(x)
if
… elif
отражает логику перехода между ограничениями задачи. В этом примере, если мы полностью удалим выражение
if x % 15
, мы изменим поведение программы. То есть такой вариант использования оператора pass
позволяет не только решить задачу, но и сохранить логику кода в соответствии с логикой задачи.Заключение
pass
. Вы готовы использовать ее для повышения скорости разработки и отладки кода на Python.
- 13 views
- 0 Comment