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

  • Отображение и фильтрация со списковыми включениями

  • [str(n) for n in [8, 16, 18, 19, 12, 1, 6, 7]]

  • [n for n in [8, 16, 18, 19, 12, 1, 6, 7] if n % 2 == 0]

  • Возвращаемые значения всегда должны иметь один тип данных

  • Выдача исключений и возвращение кодов ошибок

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


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница22 из 40
    1   ...   18   19   20   21   22   23   24   25   ...   40
    ПРИМЕЧАНИЕ
    В реальном коде лучше использовать команды def, вместо того чтобы присваивать лямб- да-функции неизменяемым переменным. Лямбда-функции специально создавались для ситуаций, в которых функции не нуждаются в имени.
    Синтаксис лямбда-функций хорошо подходит для определения небольших функ- ций, которые служат аргументами для вызова других функций. Например, у функ- ции sorted()
    есть ключевой аргумент key
    , который позволяет задать функцию.
    Вместо того чтобы сортировать элементы списка на основании значения элементов, она сортирует в зависимости от возвращаемого значения функции. В следующем примере sorted()
    передается лямбда-функция, которая возвращает периметр заданного прямоугольника. В результате функция sorted()
    сортирует по вычис- ленному периметру из списка
    [width,
    height]
    , а не непосредственно по списку
    [width,
    height]
    :
    >>> rects = [[10, 2], [3, 6], [2, 4], [3, 9], [10, 7], [9, 9]]
    >>> sorted(rects, key=lambda rect: (rect[0] * 2) + (rect[1] * 2))
    [[2, 4], [3, 6], [10, 2], [3, 9], [10, 7], [9, 9]]
    Вместо того чтобы сортировать, например, значения
    [10,
    2]
    или
    [3,
    6]
    , функция теперь выполняет сортировку на основании возвращаемых значений периметров
    24
    и
    18
    . Лямбда-функции являются удобным синтаксическим сокращением: вы можете задать одну маленькую лямбда-функцию вместо определения новой именованной функции командой def
    Отображение и фильтрация со списковыми включениями
    В более ранних версиях Python функции map()
    и filter()
    были обычными функци- ями высшего порядка, которые могли преобразовывать и фильтровать списки, часто при помощи лямбда-функций. Отображение способно строить списки значений на основании значений из другого списка. Фильтрация позволяет создать список, который содержит только те значения из другого списка, которые соответствуют некоторому критерию.

    Функциональное программирование
    211
    Например, если вы хотите создать новый список, содержащий строки вместо це- лых чисел
    [8,
    16,
    18,
    19,
    12,
    1,
    6,
    7]
    , можно передать функции map()
    этот список и лямбда-функцию lambda n:
    str(n)
    :
    >>> mapObj = map(lambda n: str(n), [8, 16, 18, 19, 12, 1, 6, 7])
    >>> list(mapObj)
    ['8', '16', '18', '19', '12', '1', '6', '7']
    Функция map()
    возвращает объект map
    , который можно получить в форме списка, для чего он передается функции list()
    . Отображенный список теперь содержит строковые значения на основании целых значений из исходного списка. Функ- ция filter()
    работает аналогично, но в этом случае аргумент лямбда-функции определяет, какие элементы должны остаться в списке (если лямбда-функция возвращает
    True
    ) или быть отфильтрованными (если она возвращает
    False
    ).
    Например, передача функции lambda n:
    n
    %
    2
    ==
    0
    позволяет отфильтровать все нечетные числа:
    >>> filterObj = filter(lambda n: n % 2 == 0, [8, 16, 18, 19, 12, 1, 6, 7])
    >>> list(filterObj)
    [8, 16, 18, 12, 6]
    Функция filter()
    возвращает объект-фильтр, который можно снова передать функции list()
    . В отфильтрованном списке остаются только четные числа.
    Однако создание отображенных или отфильтрованных списков функциями map()
    и filter()
    в Python считается устаревшим. Вместо этого рекомендуется создавать их при помощи списковых включений. Списковые включения не только освобожда- ют вас от необходимости писать лямбда-функции, но и работают быстрее функций map()
    и filter()
    Следующий код воспроизводит пример с функцией map(
    ) с использованием спи- скового включения:
    >>> [str(n) for n in [8, 16, 18, 19, 12, 1, 6, 7]]
    ['8', '16', '18', '19', '12', '1', '6', '7']
    Обратите внимание: часть спискового включения str(n)
    похожа на lambda n: str(n)
    А следующий фрагмент воспроизводит пример с функцией filter()
    с использо- ванием спискового включения:
    >>> [n for n in [8, 16, 18, 19, 12, 1, 6, 7] if n % 2 == 0]
    [8, 16, 18, 12, 6]
    Обратите внимание: часть спискового включения if n
    %
    2
    ==
    0
    похожа на lambda n:
    n
    %
    2
    ==
    0

    212
    Глава 10.Написание эффективных функций
    Во многих языках существует концепция функций как первоклассных объектов, что делает возможным существование функций высшего порядка, включая функции отображения и фильтрации.
    Возвращаемые значения всегда должны иметь
    один тип данных
    Python является языком с динамической типизацией; это означает, что функции и методы Python способны возвращать значения любого типа данных. Но чтобы ваши функции были более предсказуемыми, вы должны стремиться к тому, чтобы они возвращали значения только одного типа данных.
    Например, следующая функция в зависимости от случайного числа возвращает целое число или строковое значение:
    >>> import random
    >>> def returnsTwoTypes():
    ... if random.randint(1, 2) == 1:
    ... return 42
    ... else:
    ... return 'forty two'
    Когда вы пишете код с вызовом этой функции, легко забыть, что вам нужно обра- батывать разные типы данных. Продолжим этот пример: допустим, что вы вызвали returnsTwoTypes()
    и хотите преобразовать возвращенное число в шестнадцатерич- ную форму:
    >>> hexNum = hex(returnsTwoTypes())
    >>> hexNum
    '0x2a'
    Встроенная функция Python hex()
    возвращает строку с шестнадцатеричным представлением переданного ей числа. Этот код работает при условии, что returnsTwoTypes()
    вернет целое число; возникает впечатление, что этот код сво- боден от ошибок. Но когда returnsTwoTypes()
    возвращает строку, выдается ис- ключение:
    >>> hexNum = hex(returnsTwoTypes())
    Traceback (most recent call last):
    File "", line 1, in
    TypeError: 'str' object cannot be interpreted as an integer
    (Объект 'str'
    не может быть интерпретирован как integer
    .)
    Конечно, вы постоянно должны помнить о необходимости обрабатывать все воз- можные типы данных возвращаемого значения. Но в реальности об этом легко

    Возвращаемые значения всегда должны иметь один тип данных
    213
    забыть. Чтобы предотвратить такие ошибки, всегда стремитесь к тому, чтобы ваши функции возвращали значения только одного типа данных. Это не является жест- ким требованием, и иногда просто невозможно предотвратить возвращение функ- цией значений разных типов данных. Но чем ближе вы подходите к возвращению только одного типа, тем проще и надежнее будут ваши функции.
    Есть один случай, на который необходимо обратить особое внимание: не возвра- щайте
    None из функций (единственное исключение — если ваша функция всегда возвращает
    None
    ). Значение
    None
    — единственное значение типа данных
    NoneType
    Появляется искушение написать функцию, которая возвращает
    None
    , сообщая тем самым о возникшей ошибке (эта практика рассматривается в следующем разделе «Выдача исключений и возвращение кодов ошибок»), но возвращение
    None лучше зарезервировать для функций, у которых возвращаемое значение не имеет смысла.
    Дело в том, что возвращение
    None как признака ошибки становится распространен- ным источником неперехваченных исключений 'NoneType'
    object has no attribute
    (Объект 'NoneType
    ' не имеет атрибута…):
    >>> import random
    >>> def sometimesReturnsNone():
    ... if random.randint(1, 2) == 1:
    ... return 'Hello!'
    ... else:
    ... return None
    >>> returnVal = sometimesReturnsNone()
    >>> returnVal.upper()
    'HELLO!'
    >>> returnVal = sometimesReturnsNone()
    >>> returnVal.upper()
    Traceback (most recent call last):
    File "", line 1, in
    AttributeError: 'NoneType' object has no attribute 'upper'
    Сообщение об ошибке выглядит довольно туманно. Вероятно, вы не сразу отследите его происхождение до функции, которая обычно возвращает ожидаемый результат, но также может вернуть
    None при возникновении ошибки. Проблема возникла из-за того, что sometimesReturnsNone()
    возвращает значение
    None
    , которое затем присваивается переменной returnVal
    . Но сообщение об ошибке заставляет думать, что проблема возникла при вызове метода upper()
    В своем докладе на конференции в 2009 году компьютерный теоретик Тони Хоар
    (Tony Hoare) извинился за то, что он изобрел null
    -ссылку (общий аналог значения
    None в Python) в 1965 году. Он сказал: «Я называю это своей ошибкой на милли- ард долларов. <…> Я не устоял перед искушением включить null
    -ссылки просто

    214
    Глава 10.Написание эффективных функций потому, что их было так легко реализовать. Это привело к неисчислимым ошибкам, уязвимостям и системным сбоям, которые за последние 40 лет, вероятно, создали проблемы и неприятности на миллиард долларов». Полное выступление можно просмотреть на https://autbor.com/billiondollarmistake.
    Выдача исключений и возвращение
    кодов ошибок
    В Python термины «исключение» (exception) и «ошибка» (error) имеют прибли- зительно одинаковый смысл: аномальная ситуация в программе, которая обычно указывает на возникшую проблему. Исключения стали популярным языковым механизмом в 1980-е и 1990-е годы в C++ и Java. Ими заменили коды ошибок (error codes) — значения, возвращаемые функциями для обозначения проблемы. Пре- имущество исключений состоит в том, что возвращаемые значения связаны только с предназначением функции и не указывают на присутствие ошибки.
    Коды ошибок также способны создавать проблемы в ваших программах. Напри- мер, метод строк Python find()
    обычно возвращает индекс, по которому была найдена подстрока, а если найти подстроку не удалось, возвращается код ошиб- ки
    -1
    . Но поскольку индекс
    -1
    также может использоваться для отсчета индекса от конца строки, случайное использование
    -1
    в качестве кода ошибки создаст ошибку. Чтобы понять, как это происходит, введите следующий фрагмент в ин- терактивной оболочке:
    >>> print('Letters after b in "Albert":', 'Albert'['Albert'.find('b') + 1:])
    Letters after b in "Albert": ert
    >>> print('Letters after x in "Albert":', 'Albert'['Albert'.find('x') + 1:])
    Letters after x in "Albert": Albert
    Часть кода 'Albert'.find('x')
    при вычислении возвращает код ошибки
    -1
    В результате выражение 'Albert'['Albert'.find('x')
    +
    1:]
    преобразуется в 'Albert'[-1
    +
    1:]
    , что далее дает результат 'Albert'[0:]
    и, наконец,
    'Albert'
    Очевидно, это не то поведение, которое ожидалось от кода. При вызове index()
    вместо find()
    , как в 'Albert'['Albert'.index('x')
    +
    1:]
    , возникло бы исключение.
    Проблема становится очевидной, и проигнорировать ее не удастся.
    С другой стороны, метод строк index()
    выдает исключение
    ValueError
    , если он не может найти подстроку. Если исключение не будет обработано, оно приведет к аварийному завершению программы — обычно это лучше, чем ошибка, которая осталась незамеченной.
    Имена классов исключений часто завершаются словом
    Error
    , когда исключение ука- зывает на фактическую ошибку — такую как
    ValueError
    ,
    NameError или
    SyntaxError

    Итоги
    215
    К категории классов исключений, представляющих аномальные ситуации, которые не обязательно являются ошибками, относятся
    StopIteration
    ,
    KeyboardInterrupt и
    SystemExit
    Итоги
    Функции предоставляют популярный механизм группировки кода наших про- грамм; при их написании необходимо принимать ряд решений: какое имя им при- своить, какой размер они должны иметь, сколько у них должно быть параметров и сколько аргументов должно передаваться для этих параметров. Синтаксисы
    *
    и
    **
    в командах def позволяют функциям получать переменное количество параметров; такие функции называются вариадическими.
    Хотя Python не является языком функционального программирования, в нем реализованы многие возможности, используемые в языках функционального про- граммирования. Функции являются первоклассными объектами; это означает, что их можно сохранять в переменных и передавать как аргументы других функций
    (которые в этом случае называются функциями высшего порядка). Лямбда-функ- ции предоставляют короткий синтаксис для определения анонимных функций как аргументов функций высшего порядка. Самые распространенные функции высшего порядка в Python — map()
    и filter()
    , хотя предоставляемая ими функциональность быстрее реализуется списковыми включениями.
    Возвращаемые значения функций всегда должны иметь постоянный тип данных.
    Возвращаемые значения не должны использоваться как коды ошибок; для этой цели следует использовать исключения. В частности, значение
    None часто определяется как код ошибки.

    11
    Комментарии, doc-строки
    и аннотации типов
    Комментарии и документация в исходном коде не менее важны, чем сам код. Причина в том, что программный про- дукт никогда не бывает полностью готовым; всегда прихо- дится вносить в него изменения — как при добавлении новых возможностей, так и при исправлении ошибок. Как однажды заметили специалисты по теории вычислений Гарольд Абельсон
    (Harold Abelson), Джеральд Джей Зюссман (Gerald Jay Sussman) и Джулия Зюсс- ман (Julie Sussman), «программы пишутся для того, чтобы их читали люди, и лишь изредка для того, чтобы они выполнялись машинами».
    Комментарии, doc-строки и аннотации типов помогают поддерживать код в работо- способном состоянии. Комментарии представляют собой короткие объяснения на естественном языке, которые записываются прямо в исходном коде; компьютер их игнорирует. Комментарии содержат полезные заметки, предупреждения и напоми- нания для сторонних читателей кода, а иногда и для самого автора кода в будущем.
    Почти каждому программисту доводилось задавать себе вопрос: «Кто написал это нечитаемое месиво?», только чтобы вспомнить ответ: «Это я».
    Doc-строки представляют собой форму документирования функций, методов и модулей, специфическую для Python. Когда вы задаете комментарии в формате doc-строки, автоматизированные средства (такие как генераторы документации или встроенный модуль Python help()
    ) позволяют разработчикам легко найти информацию о вашем коде.
    Аннотации типов (type hints) представляют собой директивы, которые можно до- бавить в исходный код Python для указания типов данных переменных, параметров и возвращаемых значений. Это позволяет средствам статического анализа кода

    Комментарии
    217
    убедиться в том, что ваш код не сгенерирует исключений, обусловленных непра- вильными типами значений. Аннотации типов впервые появились в Python 3.5, но так как они создаются на основе комментариев, их можно использовать в любой версии Python.
    Итак, в этой главе я расскажу о трех упомянутых способах встраивания докумен- тации в код с целью улучшения его читабельности. Внешняя документация — ру- ководства пользователя, электронные учебники и справочные материалы — важна, но в этой книге она не рассматривается. Если вы захотите узнать больше о внешней документации, поищите информацию о генераторе документации Sphinx (https://
    www.sphinx-doc.org/).
    Комментарии
    Как и большинство языков программирования, Python поддерживает одностроч- ные и многострочные комментарии. Любой текст, заключенный между знаком
    #
    и концом строки, является однострочным комментарием. Хотя в Python нет спе- циального синтаксиса для многострочных комментариев, в этом качестве можно использовать многострочный текст в тройных кавычках. В конце концов, строковое значение само по себе еще не заставляет интерпретатор Python что-либо сделать.
    Рассмотрим пример:
    # Это однострочный комментарий.
    """А это многострочный текст, который также работает как многострочный комментарий. """
    Если комментарий занимает несколько строк, лучше использовать один много- строчный блок, чем несколько последовательных однострочных комментариев.
    Второй вариант хуже читается, как видно из следующего примера:
    """Хороший способ записи комментариев,
    занимающих несколько строк. """
    # А это плохой способ
    # записи комментариев,
    # занимающих несколько строк.
    Комментарии и документацию программисты зачастую включают в код задним числом, а некоторые даже считают, что от них больше вреда, чем пользы. Но как я уже объяснял в подразделе «Миф: комментарии излишни» на с. 111, коммен- тарии абсолютно обязательны, если вы хотите писать профессиональный, удо- бочитаемый код. В этом разделе вы научитесь писать полезные комментарии, которые предоставляют дельную информацию читателю без ущерба для удобо- читаемости программы.

    218
    Глава 11.Комментарии, doc-строки и аннотации типов
    Стиль комментариев
    Примеры комментариев, отвечающих правилам хорошего стиля:
    # Комментарий, относящийся к следующему коду:

    someCode()
    # Более длинный комментарий, который занимает несколько строк

    # из нескольких последовательных однострочных комментариев.
    #

    # Такие комментарии называются блоковыми.
    if someCondition:
    # Комментарий о другом коде:

    someOtherCode() # Встроенный комментарий.

    Как правило, комментарии лучше размещать в отдельной строке, а не в конце строки с кодом. В большинстве случаев лучше использовать полноценные предложения с соответствующим регистром символов и знаками препинания, а не короткие фразы или отдельные слова

    . При этом комментарии должны подчиняться тем же ограничениям длины строки, что и исходный код. Комментарии, занимающие несколько строк

    , могут состоять из нескольких последовательных однострочных комментариев (такие комментарии называются блоковыми). Абзацы в коммен- тариях разделяются пустым однострочным комментарием

    . Уровень отступа комментария должен соответствовать уровню отступов в комментируемом коде

    Комментарии, следующие за строкой кода, называются встроенными (inline)

    , и код отделяется от комментария минимум двумя пробелами.
    В однострочных комментариях после знака
    #
    ставят пробел:
    #Комментарий не должен начинаться сразу же после знака #.
    Комментарии могут включать ссылки на URL с сопутствующей информацией, но ссылки не должны заменять комментарии, потому что связанный с ними контент может исчезнуть из интернета в любой момент:
    # Подробное объяснение некоторого аспекта кода с дополнительной
    # информацией по URL. Подробнее см. https://example.com
    Все эти соглашения являются делом стиля, а не контента, но они вносят свой вклад в удобочитаемость комментариев. Чем лучше читаются комментарии, тем с большей вероятностью программисты обратят на них внимание, — а комментарии приносят пользу только в том случае, если программисты их читают.
    1   ...   18   19   20   21   22   23   24   25   ...   40


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