Главная страница
Навигация по странице:

  • Закомментированный и мертвый код

  • >>> import random >>> def coinFlip(): ... if random.randint(0, 1): ... return Heads! ... else: ... return Tails!

  • Переменные с числовыми суффиксами

  • Классы, которые должны быть функциями или модулями

  • >>> import random >>> class Dice: ... def __init__(self, sides=6): ... self.sides = sides ... def roll(self): ... return random.randint(1, self.sides)

  • Списковые включения внутри списковых включений

  • >>> spam = [] >>> for number in range(100): ... if number % 5 != 0: ... spam.append(str(number)) ... >>> spam

  • >>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]] >>> nestedStrList = [[str(i) for i in sublist] for sublist in nestedIntList] >>> nestedStrList

  • >>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]] >>> nestedStrList = [] >>> for sublist in nestedIntList: ... nestedStrList.append([str(i) for i in sublist])

  • >>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]

  • Чистыйкод дляпродолжающи х


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница9 из 40
    1   ...   5   6   7   8   9   10   11   12   ...   40
    «Магические» числа
    Не приходится удивляться тому, что в программировании используются числа.
    Но некоторые числа, встречающиеся в исходном коде, могут сбить с толку других программистов (или вас через пару недель после написания программы). Для при- мера возьмем число
    604800
    в следующей строке:
    expiration = time.time() + 604800
    Функция time.time()
    возвращает целое число, представляющее текущее время.
    Можно предположить, что переменная expiration представляет некий будущий момент, который наступит через 604 800 секунд. Но число
    604800
    выглядит зага- дочно: что оно означает? Комментарий поможет прояснить ситуацию:
    expiration = time.time() + 604800 # Срок действия истекает через неделю.

    98
    Глава 5.Поиск запахов в коде
    Это хорошее решение, но еще лучше заменить такие «магические» числа кон- стантами. Константы представляют собой переменные, имена которых записаны в верхнем регистре, это означает, что они не должны изменяться после исходного присваивания. Обычно константы определяются как глобальные переменные в на- чале файла с исходным кодом:
    # Константы для разных промежутков времени:
    SECONDS_PER_MINUTE = 60
    SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE
    SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR
    SECONDS_PER_WEEK = 7 * SECONDS_PER_DAY
    expiration = time.time() + SECONDS_PER_WEEK # Срок действия истекает
    # через неделю.
    Используйте отдельные константы для «магических» чисел, предназначенных для разных целей, даже если их числовые значения совпадают. Например, в колоде
    52 карты, а в году 52 недели. Но если в вашей программе используются обе вели- чины, нужно поступить примерно так:
    NUM_CARDS_IN_DECK = 52
    NUM_WEEKS_IN_YEAR = 52
    print('This deck contains', NUM_CARDS_IN_DECK, 'cards.')
    print('The 2-year contract lasts for', 2 * NUM_WEEKS_IN_YEAR, 'weeks.')
    При выполнении этого кода результат будет выглядеть так:
    This deck contains 52 cards.
    The 2-year contract lasts for 104 weeks.
    (Колода содержит 52 карты.
    Двухлетний контракт длится 104 недели.)
    Использование разных констант позволяет независимо изменять их в будущем. Ко- нечно, значения констант не должны изменяться во время выполнения кода. Но это не означает, что программист никогда не обновит их в исходном коде. Например, если в будущей версии кода появится дополнительная карта-джокер, константу cards можно изменить независимо от weeks
    :
    NUM_CARDS_IN_DECK = 53
    NUM_WEEKS_IN_YEAR = 52
    «Магическими» числами также иногда называют нечисловые значения. Например, строковые значения могут использоваться как константы. Возьмем следующую программу, которая предлагает пользователю указать направление и выводит

    «Магические» числа
    99
    преду преждение, если пользователь выбрал 'north'
    . Из-за опечатки 'nrth'
    воз- никает ошибка, вследствие чего предупреждение не выводится:
    while True:
    print('Set solar panel direction:')
    direction = input().lower()
    if direction in ('north', 'south', 'east', 'west'):
    break print('Solar panel heading set to:', direction)
    if direction == 'nrth':

    print('Warning: Facing north is inefficient for this panel.')
    Найти такую ошибку нелегко: хотя в строке 'nrth'
    совершена опечатка, она остается синтаксически правильным кодом Python. Программа не завершается аварийно, а предупреждение легко упустить из виду. Но если вы допустите ту же опечатку при использовании констант, ошибка будет обнаружена, потому что Python заметит, что константа
    NRTH
    не существует:
    # Константы для разных промежутков времени:
    NORTH = 'north'
    SOUTH = 'south'
    EAST = 'east'
    WEST = 'west'
    while True:
    print('Set solar panel direction:')
    direction = input().lower()
    if direction in (NORTH, SOUTH, EAST, WEST):
    break print('Solar panel heading set to:', direction)
    if direction == NRTH:

    print('Warning: Facing north is inefficient for this panel.')
    Из-за исключения
    NameError
    , выдаваемого в строке кода с опечаткой
    NRTH

    , ошибка становится очевидной при запуске программы:
    Set solar panel direction:
    west
    Solar panel heading set to: west
    Traceback (most recent call last):
    File "panelset.py", line 14, in
    if direction == NRTH:
    NameError: name 'NRTH' is not defined
    «Магические» числа свидетельствуют о наличии запаха кода, потому что они не выполняют свое предназначение, затрудняют чтение и обновление кода и повышают риск опечаток, которые так трудно обнаружить. Проблема решается использова- нием констант.

    100
    Глава 5.Поиск запахов в коде
    Закомментированный и мертвый код
    Поместить код в комментарий, чтобы он не выполнялся, — временная мера. Воз- можно, вы хотите пропустить часть строк, чтобы протестировать другую функцио- нальность; закомментированные строки вы легко вернете позднее. Но если заком- ментированный код так и останется на месте, для читателя останется абсолютной тайной, почему он был удален и при каких условиях он может опять стать частью программы. Рассмотрим следующий пример:
    doSomething()
    #doAnotherThing()
    doSomeImportantTask()
    doAnotherThing()
    Возникает целый ряд вопросов: почему вызов doAnotherThing()
    был закоммен- тирован? Будет ли он снова включен в программу? Почему не был закоммен- тирован второй вызов doAnotherThing()
    ? Изначально в коде было два вызова doAnotherThing()
    или только один вызов, который переместился в точку после вызова doSomeImportantTask()
    ? Почему закомментированный код не был удален, для этого есть какая-то причина? На все эти вопросы нет очевидных ответов.
    Мертвым называется код, который недоступен или никогда не может быть выпол- нен на логическом уровне. Код внутри функции после команды return
    , команды if
    , условие которой всегда равно
    False
    , или код функции, которая никогда не вызы- вается в программе, — все это примеры мертвого кода. Чтобы увидеть пример на практике, введите следующую команду в интерактивной оболочке:
    >>> import random
    >>> def coinFlip():
    ... if random.randint(0, 1):
    ... return 'Heads!'
    ... else:
    ... return 'Tails!'
    ... return 'The coin landed on its edge!'
    ...
    >>> print(coinFlip())
    Tails!
    1
    Строка 'The coin landed on its edge!'
    является мертвым кодом, потому что код в блоках if и else возвращает управление до того, как программа сможет достичь этой строки. Мертвый код дезинформирует, поскольку читающие его программисты предполагают, что он составляет активную часть программы, тогда как по сути это закомментированный код.
    1
    Heads — орел; Tails — решка; The coin landed on its edge — Монета встала ребром (англ.). —
    Примеч. пер.

    Отладочный вывод
    101
    Заглушки (stubs) являются исключением из этих правил. Они представляют со- бой заменители для будущего кода (например, функции и классы, которые еще не были реализованы). Вместо реального кода заглушка содержит команду pass
    , которая ничего не делает. (Она также называется пустой операцией.) Команда pass существует как раз для того, чтобы вы могли создавать заглушки в тех местах, где с точки зрения синтаксиса языка должен присутствовать какой-то код:
    >>> def exampleFunction():
    ... pass
    Если вызвать эту функцию, она не сделает ничего. Вместо этого она лишь показы- вает, что когда-нибудь в нее будет добавлен код.
    Также возможен другой вариант: чтобы предотвратить случайный вызов не- реализованной функции, можно использовать заглушку из команды raise
    NotImplementedError
    . Тем самым вы покажете, что функция еще не готова к вызову:
    >>> def exampleFunction():
    ... raise NotImplementedError
    >>> exampleFunction()
    Traceback (most recent call last):
    File "", line 1, in
    File "", line 2, in exampleFunction
    NotImplementedError
    Исключение
    NotImplementedError предупредит вас о том, что в программе была случайно вызвана заглушка (функция или метод).
    Закомментированный код и мертвый код считаются запахами кода, потому что они могут создать у программиста ошибочное впечатление, будто код является испол- няемой частью программы. Вместо этого следует удалить их и использовать систему контроля версий (например, Git или Subversion) для отслеживания изменений. Кон- тролю версий я посвятил главу 12. С системой контроля версий вы можете удалить код из своей программы, а при необходимости позднее вернуть его обратно.
    Отладочный вывод
    Отладочный вывод — это практика включения в программу временных вызовов print()
    для вывода значений переменных и повторного запуска программы.
    Последовательность процесса, как правило, такова.
    1. Обнаружение ошибки в программе.
    2. Включение вызовов print()
    для некоторых переменных, чтобы узнать их текущие значения.

    102
    Глава 5.Поиск запахов в коде
    3. Перезапуск программы.
    4. Включение новых вызовов print()
    , потому что предыдущие вызовы не предоставили достаточной информации.
    5. Перезапуск программы.
    6. Предыдущие этапы следует повторять несколько раз, пока вы наконец не найдете причину ошибки.
    7. Перезапуск программы.
    8. Вы понимаете, что забыли удалить какие-то вызовы print()
    , и удаляете их.
    Отладочный вывод обманчиво прост и быстр. Но часто он требует многих циклов перезапуска программы, пока не будет выведена информация, необходимая для исправления ошибки. Проблема решается при помощи отладки или организации журнального вывода в программе. При использовании отладчика вы можете вы- полнять свои программы по одной строке и проверять значения любых переменных.
    Иногда кажется, что работа с отладчиком занимает больше времени, чем простая вставка вызова print()
    , но в долгосрочной перспективе она экономит время.
    Журнальные файлы сохраняют большие объемы информации из вашей программы, чтобы вы могли сравнить результаты одного запуска с другим. В Python встроенный модуль logging предоставляет функциональность, необходимую для простого со- хранения журнальной информации, всего в трех строках кода:
    import logging logging.basicConfig(filename='log_filename.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
    logging.debug('This is a log message.')
    После импортирования модуля logging и настройки его базовой конфигурации можно вызвать метод logging.debug()
    для записи информации в текстовый файл — в отличие от использования print()
    для вывода ее на экран. В отличие от отладоч- ного вывода вызов logging.debug()
    очевидно показывает, какой вывод содержит отладочную информацию, а какой является результатом нормального выполнения программы. Подробнее об отладке вы можете прочитать в главе 11 книги «Automate the Boring Stuff with Python», 2nd edition (No Starch, 2019), доступной по адресу
    https://autbor.com/2e/c11/.
    1
    Переменные с числовыми суффиксами
    При написании программ вам может понадобиться набор переменных для хране- ния однотипных данных. И здесь возникает искушение повторно использовать
    1
    Свейгарт Э. Автоматизация рутинных задач с помощью Python. 2-е изд.

    Классы, которые должны быть функциями или модулями
    103
    имя переменной, добавив к нему числовой суффикс. Например, если вы обраба- тываете форму ввода регистрационных данных, на которой пользователю пред- лагается дважды ввести пароль для предотвращения опечаток, две введенные строки можно сохранить в переменных с именами password1
    и password2
    . Эти числовые суффиксы не описывают, что содержат переменные и чем они отлича- ются. Также они не показывают, сколько всего таких переменных: существуют ли также переменные password3
    или password4
    ? Попробуйте создавать разные имена, вместо того чтобы бездумно добавлять числовые суффиксы. В примере с паролями лучше использовать имена password и confirm_password
    ("пароль"
    и "подтвердить пароль")
    Другой пример: если у вас есть функция, которая получает две пары координат на плоскости, она может иметь параметры x1
    , y1
    , x2
    и y2
    . Но имена с числовыми суффиксами не передают такой информации, как имена start_x
    , start_y
    , end_x и end_y
    . Также очевидно, что переменные start_x и start_y связаны друг с другом, чего не скажешь о x1
    и y1
    Если количество числовых суффиксов больше двух, стоит подумать об использо- вании структур: списка или множества для хранения данных в виде коллекции.
    Например, значения pet1Name
    , pet2Name
    , pet3Name и т. д. можно хранить в списке с именем petNames
    Эти замечания относятся не ко всем переменным, которые заканчиваются цифрой.
    Например, вполне нормально иметь переменную с именем enableIPv6
    , потому что 6 является частью имени собственного IPv6, а не числовым суффиксом. Но если вы используете числовые суффиксы для серии переменных, подумайте о том, чтобы заменить их структурой данных — например, списком или словарем.
    Классы, которые должны быть функциями
    или модулями
    Программисты, работающие на таких языках, как Java, привыкли создавать классы для организации кода их программ. Например, возьмем класс
    Dice с методом roll()
    :
    >>> import random
    >>> class Dice:
    ... def __init__(self, sides=6):
    ... self.sides = sides
    ... def roll(self):
    ... return random.randint(1, self.sides)
    ...
    >>> d = Dice()
    >>> print('You rolled a', d.roll())
    You rolled a 1

    104
    Глава 5.Поиск запахов в коде
    Может показаться, что перед вами хорошо организованный код, но подумайте, что нам здесь действительно нужно: случайное число от 1 до 6. Стоит заменить весь класс простым вызовом функции:
    >>> print('You rolled a', random.randint(1, 6))
    You rolled a 6
    По сравнению с другими языками Python использует свободный подход к органи- зации кода, потому что код не обязан существовать в классе или другой шаблонной структуре. Если вы обнаруживаете, что создаете объекты только для того, чтобы вызвать одну функцию, или пишете классы, содержащие только статические ме- тоды, возможно, стоит лучше написать функции.
    В Python для группировки функций используются модули вместо классов. Так как классы все равно должны находиться в модуле, размещение этого кода в классах только добавляет в ваш код лишний организационный уровень. В главах 15–17 я рассмотрю принципы объектно-ориентированного проектирования более по- дробно. Джек Дидерих (Jack Diederich) в своем докладе «Перестаньте писать классы» на конференции PyCon 2012 рассказывает и о других возможностях из- быточного усложнения кода Python.
    Списковые включения внутри
    списковых включений
    Списковые включения (list comprehensions) предоставляют компактный механизм создания сложных списковых значений. Например, чтобы создать список цифр в числах от 0 до 100, из которого исключены все числа, кратные 5, обычно исполь- зуют цикл for
    :
    >>> spam = []
    >>> for number in range(100):
    ... if number % 5 != 0:
    ... spam.append(str(number))
    ...
    >>> spam
    ['1', '2', '3', '4', '6', '7', '8', '9', '11', '12', '13', '14', '16', '17',
    '86', '87', '88', '89', '91', '92', '93', '94', '96', '97', '98', '99']
    Однако тот же список можно создать всего одной строкой кода с использованием синтаксиса спискового включения:
    >>> spam = [str(number) for number in range(100) if number % 5 != 0]
    >>> spam

    Списковые включения внутри списковых включений
    105
    ['1', '2', '3', '4', '6', '7', '8', '9', '11', '12', '13', '14', '16', '17',
    '86', '87', '88', '89', '91', '92', '93', '94', '96', '97', '98', '99']
    Также в Python существует синтаксис включений множеств и словарных вклю- чений:
    >>> spam = {str(number) for number in range(100) if number % 5 != 0}

    >>> spam
    {'39', '31', '96', '76', '91', '11', '71', '24', '2', '1', '22', '14', '62',
    '4', '57', '49', '51', '9', '63', '78', '93', '6', '86', '92', '64', '37'}
    >>> spam = {str(number): number for number in range(100) if number % 5 != 0}

    >>> spam
    {'1': 1, '2': 2, '3': 3, '4': 4, '6': 6, '7': 7, '8': 8, '9': 9, '11': 11,
    '92': 92, '93': 93, '94': 94, '96': 96, '97': 97, '98': 98, '99': 99}
    Включение множества

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

    создает значение-словарь и использует двоеточие для разделения ключа и значения во включении. Включения компактны, и они могут сделать код более удобочитае- мым. Но обратите внимание на то, что включения создают список, множество или словарь на основании итерируемого объекта (в данном примере — объекта диапа- зона, возвращаемого вызовом range(100)
    ). Списки, множества и словари являются итерируемыми объектами, это означает, что включения могут вкладываться во включения, как в следующем примере:
    >>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
    >>> nestedStrList = [[str(i) for i in sublist] for sublist in nestedIntList]
    >>> nestedStrList
    [['0', '1', '2', '3'], ['4'], ['5', '6'], ['7', '8', '9']]
    Но вложенные списковые включения (или вложенные включения множеств/
    словарные включения) упаковывают значительную сложность в небольшой объем кода, что усложняет его чтение. Лучше развернуть списковое включение в один или несколько циклов for
    :
    >>> nestedIntList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
    >>> nestedStrList = []
    >>> for sublist in nestedIntList:
    ... nestedStrList.append([str(i) for i in sublist])
    ...
    >>> nestedStrList
    [['0', '1', '2', '3'], ['4'], ['5', '6'], ['7', '8', '9']]
    Включения также могут содержать множественные выражения for
    , хотя в та- ких ситуациях также часто появляется нечитаемый код. Например, следующее

    106
    Глава 5.Поиск запахов в коде списковое включение создает неструктурированный список на базе вложенного списка:
    >>> nestedList = [[0, 1, 2, 3], [4], [5, 6], [7, 8, 9]]
    1   ...   5   6   7   8   9   10   11   12   ...   40


    написать администратору сайта