One place for hosting & domains

      Обработка

      Обработка данных входящих запросов в Flask


      Введение

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

      В этом учебном модуле мы создадим приложение Flask с тремя маршрутами, которое будет принимать строки запросов, данные форм и объекты JSON.

      Предварительные требования

      Для данного обучающего руководства вам потребуется следующее:

      • Для данного проекта потребуется установить Python в локальной среде.
      • В этом проекте мы будем использовать инструмент Pipenv, предоставляющий отличные возможности упаковки для программирования на Python. Он предоставляет возможности Pipfile, pip и virtualenv в одной команде.
      • Для тестирования конечных точек API потребуется установить Postman или другой подобный инструмент.

      Для этого учебного модуля мы использовали версии Pipenv v2020.11.15, Python v3.9.0 и Flask v1.1.2.

      Настройка проекта

      Чтобы продемонстрировать разные способы использования запросов, мы создадим приложение Flask. Хотя в примере используется упрощенная структура отображения функций и маршрутов, уроки этого учебного модуля можно применить к любым методам организации представлений, включая представления на базе классов, эскизов или расширений, таких как Flask-Via.

      Прежде всего, вам нужно будет создать каталог для проекта. Откройте терминал и запустите следующую команду:

      • mkdir flask_request_example

      Затем перейдите в новый каталог:

      Затем установите Flask. Откройте терминал и запустите следующую команду:

      Команда pipenv создаст среду virtualenv для этого проекта, Pipfile, install flask и Pipfile.lock.

      Для активации virtualenv этого проекта запустите следующую команду:

      Чтобы получить доступ к входящим данным в Flask, вам нужно будет использовать объект request. В объекте request хранятся все входящие данные запроса, включая тип MIME, источник, IP-адрес, необработанные данные, метод HTTP, заголовки и т. д.

      Хотя вся информация в объекте request может быть полезной для наших целей, в этом учебном модуле мы уделим основное внимание данным, которые обычно предоставляются вызывающей стороной напрямую.

      Чтобы получить доступ к запрашиваемому объекту в Flask, вам потребуется импортировать его из библиотеки Flask:

      from flask import request
      

      После этого у вас появится возможность использовать его в любых ваших функциях представлений.

      Используйте редактор кода для создания файла app.py. Импортируйте Flask и объект request. Также установите маршруты для query-example, form-example и json-example:

      app.py

      # import main Flask class and request object
      from flask import Flask, request
      
      # create the Flask app
      app = Flask(__name__)
      
      @app.route('/query-example')
      def query_example():
          return 'Query String Example'
      
      @app.route('/form-example')
      def form_example():
          return 'Form Data Example'
      
      @app.route('/json-example')
      def json_example():
          return 'JSON Object Example'
      
      if __name__ == '__main__':
          # run app in debug mode on port 5000
          app.run(debug=True, port=5000)
      

      Затем откройте терминал и запустите приложение с помощью следующей команды:

      Приложение будет запущено на порту 5000, чтобы вы могли просмотреть каждый маршрут в браузере, используя следующие ссылки:

      http://127.0.0.1:5000/query-example (or localhost:5000/query-example)
      http://127.0.0.1:5000/form-example (or localhost:5000/form-example)
      http://127.0.0.1:5000/json-example (or localhost:5000/json-example)
      

      Код устанавливает три маршрута, и при открытии каждого маршрута вы увидите сообщения "Query String Example", "Form Data Example" и "JSON Object Example".

      Использование аргументов запроса

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

      Строка запроса выглядит следующим образом:

      example.com?arg1=value1&arg2=value2
      

      Строка запроса начинается после знака (?) вопроса:

      example.com?arg1=value1&arg2=value2
      

      В ней содержатся пары ключ-значение, разделенные символом амперсанда (&):

      example.com?arg1=value1&arg2=value2
      

      В каждой паре после ключа идет знак равенства (=), а затем идет значение.

      arg1 : value1
      arg2 : value2
      

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

      Давайте добавим строку запроса в маршрут query-example. В этом гипотетическом примере, мы укажем имя языка программирования, которое будет отображаться на экране. Создайте ключ "language" и значение "Python":

      http://127.0.0.1:5000/query-example?language=Python
      

      Если вы запустите приложение и перейдете к этому URL, вы увидите сообщение "Query String Example".

      Вам нужно будет программировать часть, обрабатывающую аргументы запроса. Этот код считает ключ language, используя request.args.get('language') или request.args['language'].

      При вызове request.args.get('language') приложение продолжит работу, если ключ language отсутствует по указанному URL. В этом случае данный метод будет иметь результат None.

      При вызове request.args['language'] приложение возвращает ошибку 400, если ключ language отсутствует по указанному URL.

      При работе со строками запросов, рекомендуется использовать request.args.get(), чтобы предотвратить возможные сбои в работе приложения.

      Давайте прочитаем ключ language и выведем его.

      Измените маршрут query-example в app.py с помощью следующего кода:

      app.py

      @app.route('/query-example')
      def query_example():
          # if key doesn't exist, returns None
          language = request.args.get('language')
      
          return '''<h1>The language value is: {}</h1>'''.format(language)
      

      Затем запустите приложение и перейдите к URL:

      http://127.0.0.1:5000/query-example?language=Python
      

      Браузер должен вывести следующее сообщение:

      Output

      The language value is: Python

      Аргумент из URL привязывается к переменной language, а затем возвращается через браузер.

      Чтобы добавить дополнительные параметры запроса, вы можете добавить амперсанды и новые пары ключ-значение в конце URL. Создайте ключ "framework" и значение "Flask":

      http://127.0.0.1:5000/query-example?language=Python&framework=Flask
      

      Если вам нужно больше, продолжайте добавлять амперсанды и пары ключ-значение. Создайте ключ "website" и значение "DigitalOcean":

      http://127.0.0.1:5000/query-example?language=Python&framework=Flask&website=DigitalOcean
      

      Чтобы получить доступ к этим значениям, мы все равно используем request.args.get() или request.args[]. Давайте используем оба варианта, чтобы продемонстрировать, что произойдет при отсутствии ключа. Измените маршрут query_example, чтобы назначить значение результатов переменным и вывести их:

      @app.route('/query-example')
      def query_example():
          # if key doesn't exist, returns None
          language = request.args.get('language')
      
          # if key doesn't exist, returns a 400, bad request error
          framework = request.args['framework']
      
          # if key doesn't exist, returns None
          website = request.args.get('website')
      
          return '''
                    <h1>The language value is: {}</h1>
                    <h1>The framework value is: {}</h1>
                    <h1>The website value is: {}'''.format(language, framework, website)
      

      Затем запустите приложение и перейдите к URL:

      http://127.0.0.1:5000/query-example?language=Python&framework=Flask&website=DigitalOcean
      

      Браузер должен вывести следующее сообщение:

      Output

      The language value is: Python The framework value is: Flask The website value is: DigitalOcean

      Удалите ключ language из URL:

      http://127.0.0.1:5000/query-example?framework=Flask&website=DigitalOcean
      

      Браузер должен вывести следующее сообщение со словом None, если для language будет отсутствовать значение:

      Output

      The language value is: None The framework value is: Flask The website value is: DigitalOcean

      Удалите ключ framework из URL:

      http://127.0.0.1:5000/query-example?language=Python&website=DigitalOcean
      

      Браузер должен вывести сообщение об ошибке, потому что он ожидает получить значение framework:

      Output

      werkzeug.exceptions.BadRequestKeyError werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand. KeyError: 'framework'

      Теперь вы должны понимать, как следует обрабатывать строки запросов. Перейдем к следующему типу входящих данных.

      Использование данных форм

      Данные форм поступают из форм, отправленных на маршрут в виде запроса POST. Вместо отображения данных в URL (кроме случаев отправки форм в виде запроса GET) данные форм передаются приложению незаметно. Хотя вы не видите передаваемые данные форм, ваше приложение может их считывать.

      Чтобы продемонстрировать это, давайте изменим маршрут form-example в app.py так, чтобы принимать запросы GET и POST и возвращать форму:

      app.py

      # allow both GET and POST requests
      @app.route('/form-example', methods=['GET', 'POST'])
      def form_example():
          return '''
                    <form method="POST">
                        <div><label>Language: <input type="text" name="language"></label></div>
                        <div><label>Framework: <input type="text" name="framework"></label></div>
                        <input type="submit" value="Submit">
                    </form>'''
      

      Затем запустите приложение и перейдите к URL:

      http://127.0.0.1:5000/form-example
      

      Браузер должен отображать форму с двумя полями ввода: одно language и одно для framework, а также кнопку отправки.

      Важнее всего знать об этой форме то, что она выполняет запрос POST к тому же маршруту, который сгенерировал форму. Все ключи, которые считываются в приложении, поступают от атрибутов name в полях ввода формы. В этом случае language и framework являются именами полей ввода, и поэтому у вас будет доступ к ним в приложении.

      Внутри функции просмотра вам нужно будет проверить метод запроса: GET или POST. Если это запрос GET, вы можете вывести форму. В противном случае это запрос POST, и вам нужно обработать входящие данные.

      Измените маршрут form-example в app.py, добавив следующий код:

      app.py

      # allow both GET and POST requests
      @app.route('/form-example', methods=['GET', 'POST'])
      def form_example():
          # handle the POST request
          if request.method == 'POST':
              language = request.form.get('language')
              framework = request.form.get('framework')
              return '''
                        <h1>The language value is: {}</h1>
                        <h1>The framework value is: {}</h1>'''.format(language, framework)
      
          # otherwise handle the GET request
          return '''
                 <form method="POST">
                     <div><label>Language: <input type="text" name="language"></label></div>
                     <div><label>Framework: <input type="text" name="framework"></label></div>
                     <input type="submit" value="Submit">
                 </form>'''
      

      Затем запустите приложение и перейдите к URL:

      http://127.0.0.1:5000/form-example
      

      Введите в поле language значение Python, а в поле framework — значение Flask. Затем нажмите кнопку Submit.

      Браузер должен вывести следующее сообщение:

      Output

      The language value is: Python The framework value is: Flask

      Теперь вы понимаете, как обрабатывать данные формы. Перейдем к следующему типу входящих данных.

      Использование данных JSON

      Данные JSON обычно создаются процессом, который вызывает маршрут.

      Пример объекта JSON:

      {
        "language": "Python",
        "framework": "Flask",
        "website": "Scotch",
        "version_info": {
          "python": "3.9.0",
          "flask": "1.1.2"
        },
        "examples": ["query", "form", "json"],
        "boolean_test": true
      }
      

      Такая структура позволяет передавать более сложные данные, чем строки запросов и данные форм. В этом примере вы видите вложенные объекты JSON и массив элементов. Этот формат данных может обрабатываться Flask.

      Измените маршрут form-example в app.py, чтобы принимать запросы POST и игнорировать другие запросы, в частности GET:

      app.py

      @app.route('/json-example', methods=['POST'])
      def json_example():
          return 'JSON Object Example'
      

      Для строк запросов и данных форм в этом учебном модуле мы использовали браузер, но для отправки объекта JSON мы используем Postman, чтобы отправлять персонализированные запросы в URL.

      Примечание. Если вам нужна помощь в навигации по интерфейсу Postman для отправки запросов, воспользуйтесь официальной документацией.

      Добавьте URL в Postman и измените тип на POST. На вкладке body переключитесь на raw и выберите JSON из раскрывающегося списка.

      Эти настройки необходимы, чтобы Postman мог правильно отправлять данные JSON и чтобы ваше приложение Flask понимало, что получает данные JSON:

      POST http://127.0.0.1:5000/json-example
      Body
      raw JSON
      

      Затем скопируйте в поле ввода текста предыдущий пример JSON.

      Отправьте запрос. Вы должны получить ответ "JSON Object Example". Это не так интересно, но это ожидаемо, потому что код для обработки ответа данных JSON еще нужно написать.

      Чтобы читать данные, вам нужно понимать, как Flask преобразует данные JSON в структуры данных Python:

      • Все объекты конвертируются в словари Python. {"key" : "value"} в JSON соответствуют somedict['key'], который возвращает значение в Python.
      • Массив в JSON конвертируется в список в Python. Поскольку синтаксис одинаковый, приведем список примеров: [1,2,3,4,5]
      • Значения в кавычках объекта JSON станут строками в Python.
      • Логические операторы true и false становятся True и False в Python.
      • Числа без кавычек становятся числами в Python.

      Теперь давайте поработаем с кодом, чтобы считывать входящие данные JSON.

      Вначале добавим все содержимое объекта JSON в переменную, используя request.get_json().

      request.get_json() конвертирует объект JSON в данные Python. Давайте назначим данные входящего запроса в переменные и выведем их, внеся следующие изменения в маршрут json-example:

      app.py

      # GET requests will be blocked
      @app.route('/json-example', methods=['POST'])
      def json_example():
          request_data = request.get_json()
      
          language = request_data['language']
          framework = request_data['framework']
      
          # two keys are needed because of the nested object
          python_version = request_data['version_info']['python']
      
          # an index is needed because of the array
          example = request_data['examples'][0]
      
          boolean_test = request_data['boolean_test']
      
          return '''
                 The language value is: {}
                 The framework value is: {}
                 The Python version is: {}
                 The item at index 0 in the example list is: {}
                 The boolean value is: {}'''.format(language, framework, python_version, example, boolean_test)
      

      Обратите внимание на процедуру доступа к элементам, находящимся не на верхнем уровне. Поскольку мы вводим вложенный объект, используется ['version']['python']. А ['examples'][0] используется для доступа к индексу 0 в массиве example.

      Если объект JSON, отправленный с запросом, не имеет ключа, доступ к которому осуществляется через функцию view, запрос не будет выполняться. Если вы не хотите, чтобы запрос выдавал сбой при отсутствии ключа, вам нужно будет проверить наличие ключа, прежде чем пытаться получить к нему доступ.

      app.py

      # GET requests will be blocked
      @app.route('/json-example', methods=['POST'])
      def json_example():
          request_data = request.get_json()
      
          language = None
          framework = None
          python_version = None
          example = None
          boolean_test = None
      
          if request_data:
              if 'language' in request_data:
                  language = request_data['language']
      
              if 'framework' in request_data:
                  framework = request_data['framework']
      
              if 'version_info' in request_data:
                  if 'python' in request_data['version_info']:
                      python_version = request_data['version_info']['python']
      
              if 'examples' in request_data:
                  if (type(request_data['examples']) == list) and (len(request_data['examples']) > 0):
                      example = request_data['examples'][0]
      
              if 'boolean_test' in request_data:
                  boolean_test = request_data['boolean_test']
      
          return '''
                 The language value is: {}
                 The framework value is: {}
                 The Python version is: {}
                 The item at index 0 in the example list is: {}
                 The boolean value is: {}'''.format(language, framework, python_version, example, boolean_test)
      

      Запустите приложение и отправьте пример запроса JSON с помощью Postman. В ответе вы увидите следующее:

      Output

      The language value is: Python The framework value is: Flask The Python version is: 3.9 The item at index 0 in the example list is: query The boolean value is: false

      Теперь вы должны понимать принципы обработки объектов JSON.

      Заключение

      В этом учебном модуле мы создали приложение Flask с тремя маршрутами, которое будет принимать строки запросов, данные форм и объекты JSON.

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

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

      Если вы хотите узнать больше о Flask, на нашей странице тем по Flask вы найдете полезные упражнения и проекты по программированию.



      Source link

      Обработка ошибок в Go


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

      В отличие от других языков программирования, где разработчикам нужно обрабатывать ошибки с помощью специального синтаксиса, ошибки в Go — это значения с типом error, возвращаемые функциями, как и любые другие значения. Для обработки ошибок в Go мы должны проверить ошибки, которые могут возвращать функции, решить, существует ли ошибка, а также принять надлежащие меры для защиты данных и сообщить пользователям или операторам, что произошла ошибка.

      Создание ошибок

      Прежде чем мы сможем обработать ошибку, нам нужно ее создать. Стандартная библиотека предоставляет две встроенные функции для создания ошибок: errors.New и fmt.Errorf. Обе эти функции позволяют нам указывать настраиваемое сообщение об ошибке, которое вы можете отображать вашим пользователям.

      errors.New получает один аргумент — сообщение об ошибке в виде строки, которую вы можете настроить, чтобы предупредить ваших пользователей о том, что пошло не так.

      Попробуйте запустить следующий пример, чтобы увидеть ошибку, созданную с помощью errors.New, которая выполняет стандартный вывод:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func main() {
          err := errors.New("barnacles")
          fmt.Println("Sammy says:", err)
      }
      

      Output

      Sammy says: barnacles

      Мы использовали функцию errors.New из стандартной библиотеки для создания нового сообщения об ошибке со строкой "barnacles" в качестве сообщения об ошибке. Мы выполняли требование конвенции, используя строчные буквы для сообщения об ошибке, как показано в руководстве по стилю для языка программирования Go.

      Наконец, мы использовали функцию fmt.Println для объединения сообщения о ошибке со строкой "Sammy says:".

      Функция fmt.Errorf позволяет динамически создавать сообщение об ошибке. Ее первый аргумент — это строка, которая содержит ваше сообщение об ошибке с заполнителями, такими как %s для строки и %d для целых чисел. fmt.Errorf интерполирует аргументы, которые находятся за этой форматированной строкой, на эти заполнители по порядку:

      package main
      
      import (
          "fmt"
          "time"
      )
      
      func main() {
          err := fmt.Errorf("error occurred at: %v", time.Now())
          fmt.Println("An error happened:", err)
      }
      

      Output

      An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

      Мы использовали функцию fmt.Errorf для создания сообщения об ошибке, которое будет включать текущее время. Форматированная строка, которую мы предоставили fmt.Errorf, содержит директиву форматирования %v, которая указывает fmt.Errorf использовать формат по умолчанию для первого аргумента, предоставленного после форматированной строки. Этот аргумент будет текущим временем, предоставленным функцией time.Now из стандартной библиотеки. Как и в предыдущем примере, мы добавляем в сообщение об ошибке короткий префикс и выводим результат стандартным образом, используя fmt.Println.

      Обработка ошибок

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

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

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          err := boom()
      
          if err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      Здесь мы определяем функцию под именем boom(), которая возвращает error, которую мы создаем с помощью errors.New. Затем мы вызываем эту функцию и захватываем ошибку в строке err := boom(). После получения этой ошибки мы проверяем, присутствует ли она, с помощью условия if err ! = nil. Здесь условие всегда выполняет оценку на true, поскольку мы всегда возвращаем error из boom().

      Это не всегда так, поэтому лучше использовать логику, обрабатывающую случаи, когда ошибка отсутствует (nil) и случаи, когда ошибка есть. Когда ошибка существует, мы используем fmt.Println для вывода ошибки вместе с префиксом, как мы делали в предыдущих примерах. Наконец, мы используем оператор return, чтобы пропустить выполнение fmt.Println("Anchors away!"), поскольку этот код следует выполнять только при отсутствии ошибок.

      Примечание: конструкция if err !​​​ = nil, показанная в последнем примере, является стандартной практикой обработки ошибок в языке программирования Go. Если функция может генерировать ошибку, важно использовать оператор if, чтобы проверить наличие ошибки. Таким образом, код Go естественным образом имеет логику “happy path”на первом уровне условия и логику “sad path”на втором уровне условия.

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

      Запустите следующую программу, чтобы увидеть те же результаты, что и в нашем предыдущем примере, но в этот раз с помощью оператора if для сокращения количества шаблонного кода:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          if err := boom(); err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      Как и ранее, у нас есть функция boom(), которая всегда возвращает ошибку. Мы присвоим ошибку, возвращаемую boom(), переменной err в первой части оператора if. Эта переменная err будет доступна во второй части оператора if после точки с запятой. Мы должны убедиться в наличии ошибки и вывести нашу ошибку с коротким префиксом, как мы уже делали до этого.

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

      Возврат ошибок вместе со значениями

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

      Чтобы создать функцию, которая возвращает несколько значений, мы перечислим типы всех возвращаемых значений внутри скобок в сигнатуре функции. Например, функция capitalize, которая возвращает string и error, будет объявлена следующим образом: func capitalize(name string) (string, error) {}. Часть (string, error) сообщает компилятору Go, что эта функция возвращает строку и ошибку в указанном порядке.

      Запустите следующую программу, чтобы увидеть вывод функции, которая возвращает string и error:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          name, err := capitalize("sammy")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
      
          fmt.Println("Capitalized name:", name)
      }
      

      Output

      Capitalized name: SAMMY

      Мы определяем capitalize() как функцию, которая принимает строку (имя, которое нужно указать с большой буквы) и возвращает строку и значение ошибки. В main() мы вызываем capitalize() и присваиваем два значения, возвращаемые функцией, для переменных name и err, разделив их запятой с левой стороны оператора :=. После этого мы выполняем нашу проверку if err ! = nil, как показано в предыдущих примерах, используя стандартный вывод и fmt.Println, если ошибка присутствует. Если ошибок нет, мы выводим Capitalized name: SAMMY.

      Попробуйте изменить строку "sammy" в name, err := capitalize("sammy")​​​ на пустую строку ("") и получите вместо этого ошибку Could not capitalize: no name provided.

      Функция capitalize возвращает ошибку, когда вызов функции предоставляет пустую строку в качестве параметра name. Когда параметр name не является пустой строкой, capitalize() использует strings.ToTitle для замены строчных букв на заглавные для параметра name и возвращает nil для значения ошибки.

      Существует несколько конвенций, которым следует этот пример и которые типичны для Go, но не применяются компилятором Go. Когда функция возвращает несколько значений, включая ошибку, конвенция просит, чтобы мы возвращали error последним элементом. При возвращении ошибки функцией с несколькими возвращаемыми значениями, идиоматический код Go также устанавливает для любого значения, не являющегося ошибкой, нулевое значение. Нулевое значение — это, например, пустая строка для string, 0 для целых чисел, пустая структура для структур и nil для интерфейса и типов указателя и т. д. Мы более подробно познакомимся с нулевыми значениями в нашем руководстве по переменным и константам.

      Сокращение шаблонного кода

      Соблюдение этих конвенций может стать трудновыполнимой задачей в ситуациях, когда существует множество значений, возвращаемых функцией. Мы можем использовать анонимную функцию для сокращения объема кода. Анонимные функции — это процедуры для переменных. В отличие от функций, описанных в предыдущих примерах, они доступны только в функциях, где вы их объявили, что делает их идеальным инструментом для использования в коротких элементах вспомогательной логики.

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

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, int, error) {
          handle := func(err error) (string, int, error) {
              return "", 0, err
          }
      
          if name == "" {
              return handle(errors.New("no name provided"))
          }
      
          return strings.ToTitle(name), len(name), nil
      }
      
      func main() {
          name, size, err := capitalize("sammy")
          if err != nil {
              fmt.Println("An error occurred:", err)
          }
      
          fmt.Printf("Capitalized name: %s, length: %d", name, size)
      }
      

      Output

      Capitalized name: SAMMY, length: 5

      Внутри main() мы получим три возвращаемых аргумента из capitalize: name, size и err. Затем мы проверим, возвращает ли capitalize error, убедившись, что переменная err не равна nil. Это важно сделать, прежде чем пытаться использовать любое другое значение, возвращаемое capitalize, поскольку анонимная функция handle может задать для них нулевые значения. Поскольку ошибок не возникает, потому что мы предоставили строку ​​​"sammy"​​​, мы выведем состоящее из заглавных букв имя и его длину.

      Вы снова можете попробовать заменить "sammy" на пустую строку ("") и увидеть ошибку (An error occurred: no name provided).

      Внутри capitalize мы определяем переменную handle как анонимную функцию. Она получает одну ошибку и возвращает идентичные значения в том же порядке, что и значения, возвращаемые capitalize. handle задает для них нулевые значения и перенаправляет error, переданную в качестве аргумента, как конечное возвращаемое значение. Таким образом мы можем вернуть любые ошибки, возникающие в capitalize, с помощью оператора return перед вызовом handle с error в качестве параметра.

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

      Обработка ошибок функций с несколькими возвращаемыми значениями

      Когда функция возвращает множество значений, Go требует, чтобы каждое из них было привязано к переменной. В последнем примере мы делали это, указав имена двух значений, возвращаемых функцией capitalize. Эти имена должны быть разделены запятыми и отображаться слева от оператора :=. Первое значение, возвращаемое capitalize, будет присвоено переменной name, а второе значение (error) будет присваиваться переменной err. Бывает, что нас интересует только значение ошибки. Вы можете пропустить любые нежелательные значения, которые возвращает функция, с помощью специального имени переменной _.

      В следующей программе мы изменили наш первый пример с функцией capitalize для получения ошибки, передав функции пустую строку (""). Попробуйте запустить эту программу, чтобы увидеть, как мы можем изучить только ошибку, убрав первое возвращаемое значение с переменной _:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          _, err := capitalize("")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
          fmt.Println("Success!")
      }
      

      Output

      Could not capitalize: no name provided

      Внутри функции main() на этот раз мы присвоим состоящее из заглавных букв имя (строка, возвращаемая первой) переменной с нижним подчеркиванием (_). В то же самое время мы присваиваем error, которую возвращает capitalize, переменной err. Теперь мы проверим, существует ли ошибка в if err ! = nil. Поскольку мы жестко задали пустую строку как аргумент для capitalize в строке _, err := capitalize(""), это условие всегда будет равно true. В результате мы получим вывод "Could not capitalize: no name provided" при вызове функции fmt.Println в теле условия if. Оператор return после этого будет пропускать fmt.Println("Success!").

      Заключение

      Мы познакомились с многочисленными способами создания ошибок с помощью стандартной библиотеки и узнали, как создавать функции, возвращающие ошибки идиоматическим способом. В этом обучающем руководстве мы успешно создали различные ошибки, используя функции errors.New и fmt.Errorf стандартной библиотеки. В будущих руководствах мы рассмотрим, как создавать собственные типы ошибок для предоставления более полной информации пользователям.



      Source link

      Обработка паник в Go


      Введение

      Ошибки, возникающие в программе, относятся к двум широким категориям: ожидаемые программистом ошибки и ошибки, возникновения которых не ожидалось. Интерфейс error, который мы изучили в двух предыдущих статьях, посвященных обработке ошибок, в значительной степени имеет дело с ошибками, которые мы ожидаем при написании программ Go. Интерфейс error позволяет признать редкую возможность возникновения ошибок при вызове функций, чтобы мы могли реагировать надлежащим образом на подобные ситуации.

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

      Знакомство с паниками

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

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

      Паники при выходе за границы

      Когда вы попытаетесь получить доступ к индексу, выходящему за пределы длины среза или массива, Go будет генерировать панику.

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

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          names := []string{
              "lobster",
              "sea urchin",
              "sea cucumber",
          }
          fmt.Println("My favorite sea creature is:", names[len(names)])
      }
      

      В результате мы получаем следующий вывод:

      Output

      panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /tmp/sandbox879828148/prog.go:13 +0x20

      Имя паники в выводе предоставляет подсказку: panic: runtime error: index out of range. Мы создали срез с тремя морскими существами. Затем мы попытались получить последний элемент среза посредством индексации этого среза с помощью длины среза, используя встроенную функцию len. Необходимо помнить, что срезы и массивы ведут отчет с нуля, т. е. первый элемент будет иметь индекс ноль, а последний элемент этого среза — индекс 2. Мы пытаемся получить доступ к элементу среза с индексом 3, однако в срезе нет такого элемента, потому что он находится за пределами среза. У среды для выполнения нет других вариантов, кроме как прекращать работу и осуществлять выход, поскольку мы попросили сделать что-то невозможное. Также Go не может проверить во время компиляции, что этот код будет пытаться сделать это, поэтому с помощью компилятора нельзя поймать эту ошибку.

      Обратите внимание также, что весь последующий код не был запущен. Это объясняется тем, что паника — это событие, которое приводит к полному прекращению выполнения вашей программы Go. Полученное сообщение содержит несколько элементов, которые помогают диагностировать причину паники.

      Структура паники

      Паники состоят из сообщения, указывающего причину паники, и трассировки стека, помогающего локализовать место в вашем коде, где была сгенерирована паника.

      Первая часть любой паники — это сообщение. Оно всегда будет начинаться со строки panic:, за которой следует строка, которая изменяется в зависимости от причины паники. Паника из предыдущего упражнения содержит следующее сообщение:

      panic: runtime error: index out of range [3] with length 3
      

      Строка runtime error:​​​, идущая после префикса panic:, указывает, что паника была сгенерирована средой выполнения языка программирования. Эта паника указывает, что мы пытались использовать индекс [3], который выходит из границы длины среза, равной 3.

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

      goroutine 1 [running]:
      main.main()
          /tmp/sandbox879828148/prog.go:13 +0x20
      

      Эта трассировка стека из предыдущего примера показывает, что наша программа сгенерировала панику из файла /tmp/sandbox879828148/prog.go на строке номер 13. Кроме того, она указывает нам, что эта паника была сгенерирована в функции main() из пакета main.

      Трассировка стека разбита на отдельные блоки — один для каждой goroutine в вашей программе. Каждое исполнение программы Go производится одной или несколькими гоурутинами, каждая из которых может самостоятельно и одновременно выполнять части вашего кода Go. Каждый блок начинается с заголовка goroutine X [state]:. Заголовок предоставляет номер идентификатора гоурутины и состояние, в котором она находилась при возникновении паники. После заголовка трассировка стека показывает функцию, выполняемую программой во время генерации паники, а также имя файла и номер строки, где исполняется функция.

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

      Ссылочные типы со значением nil

      Язык программирования Go имеет указатели, относящиеся к определенным экземплярам определенного типа, которые существуют в памяти компьютера во время исполнения. Указатели могут иметь значение nil, указывающее, что они не указывают ни на что. Когда мы пытаемся вызвать методы с помощью указателя со значением nil, среда выполнения Go будет генерировать панику. Аналогично переменные, хранящие типы интерфейсов, также будут генерировать паники при вызове методов с их помощью. Чтобы увидеть паники, сгенерированные в таких случаях, воспользуйтесь следующим примером:

      package main
      
      import (
          "fmt"
      )
      
      type Shark struct {
          Name string
      }
      
      func (s *Shark) SayHello() {
          fmt.Println("Hi! My name is", s.Name)
      }
      
      func main() {
          s := &Shark{"Sammy"}
          s = nil
          s.SayHello()
      }
      

      Получаемые в этом случае паники будут выглядеть следующим образом:

      Output

      panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba] goroutine 1 [running]: main.(*Shark).SayHello(...) /tmp/sandbox160713813/prog.go:12 main.main() /tmp/sandbox160713813/prog.go:18 +0x1a

      В данном примере мы определили структуру с именем Shark. Shark имеет один метод, определенный для указателя получателя с именем SayHello, который будет выводить приветствие при поступлении запроса. Внутри тела функции main мы создаем новый экземпляр структуры Shark и запрашиваем указатель на нее с помощью оператора &. Этот указатель привязан к переменной s. Затем мы переопределяем переменную s со значением nil с помощью оператора s = nil. Наконец, мы пытаемся вызвать метод SayHello с переменной s. Вместо получения дружественного сообщения от Sammy, мы получим панику, потому что мы пытались получить доступ к недействительному адресу в памяти. Поскольку переменная s равна nil, когда функция SayHello вызывается, она пытается получить доступ к полю Name типа *Shark. Поскольку это указатель получателя, а получатель в данном случае nil, генерируется паника, поскольку он не может разыменовывать указатель nil.

      Хотя мы задали для s значение nil явно в данном примере, на практике это происходит менее явно. Когда вы увидите паники с разыменованием нулевого указателя, убедитесь, что вы настроили корректно любые ссылочные переменные, которые задали.

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

      Использование встроенной функции panic

      Также мы можем генерировать наши собственные паники с помощью встроенной функции panic. В качестве аргумента потребуется одна строка, которая будет сообщением паники, которую мы будем генерировать. Обычно это сообщение менее многословное, чем простая перезапись нашего кода для возвращения ошибки. Более того, мы можем использовать это в наших собственных пакетах, чтобы указать разработчикам, что они могли допустить ошибку при использовании кода нашего пакета. При возможности рекомендуется возвращать значения error для потребителей нашего пакета.

      Запустите этот код, чтобы увидеть, как паника генерируется функцией, вызываемой другой функцией:

      package main
      
      func main() {
          foo()
      }
      
      func foo() {
          panic("oh no!")
      }
      

      Вывод полученной паники выглядит следующим образом:

      Output

      panic: oh no! goroutine 1 [running]: main.foo(...) /tmp/sandbox494710869/prog.go:8 main.main() /tmp/sandbox494710869/prog.go:4 +0x40

      Здесь мы определяем функцию foo, которая вызывает встроенную функцию panic со строкой "oh no!". Эта функция вызывается нашей функцией main. Обратите внимание, что вывод содержит сообщение: panic: oh no!​​​ и трассировка стека отображает одну гоурутину с двумя строками в трассировке стека: одна для функции main() и одна для нашей функции foo().

      Мы увидели, что паники вызывают прекращение работы программы в момент генерации. Это может создавать проблемы при наличии открытых ресурсов, которые необходимо надлежащим образом закрыть. Go обеспечивает механизм выполнения какого-либо кода всегда, даже при наличии паники.

      Отсроченные функции

      Ваша программа может иметь ресурсы, которые она должна очищать надлежащим образом, даже при обработке паники средой выполнения. Go позволяет отложить исполнение вызова функции, пока вызывающая функция не завершит работу. Отсроченные функции запускаются даже при наличии паники, а также используются как механизм обеспечения защиты от хаотического характера паники. Функции откладываются при обычном вызове с префиксом для всего объявления в виде ключевого слова defer, например, defer sayHello(). Запустите этот пример, чтобы увидеть, как сообщение может быть выведено, даже если была сгенерирована паника:

      package main
      
      import "fmt"
      
      func main() {
          defer func() {
              fmt.Println("hello from the deferred function!")
          }()
      
          panic("oh no!")
      }
      

      Вывод, получаемый из этого примера, будет выглядеть следующим образом:

      Output

      hello from the deferred function! panic: oh no! goroutine 1 [running]: main.main() /Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55

      Внутри функции main в данном примере мы вначале используем ключевое слово defer для вызова анонимной функции, которая выводит сообщение "hello from the deferred function!". Функция main сразу же генерирует панику с помощью функции panic. В выводе этой программы мы вначале видим, что отсроченная функция выполняется и выводит свое сообщение. После этого следует паника, которую мы генерируем в main.

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

      Обработка паник

      Паники имеют единый механизм восстановления — встроенную функцию recover. Эта функция позволяет перехватить панику по ходу работы через стек вызовов и не допустить неожиданное прекращение работы вашей программы. Она имеет строгие правила для использования, но может быть бесценной в рабочем приложении.

      Поскольку функция recover входит в пакет builtin, она может вызываться без импорта дополнительных пакетов:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          return a / b
      }
      

      Результат выполнения будет выглядеть так:

      Output

      2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero we survived dividing by zero!

      Наша функция main в данном примере вызывает функцию, которую мы определили, divideByZero. В этой функции мы используем defer для вызова анонимной функции, которая отвечает за работу с любыми паниками, которые могут возникнуть при исполнении divideByZero. Внутри этой отсроченной анонимной функции мы вызываем функцию recover и присваиваем ошибку, которую она возвращает, для переменной. Если divideByZero генерирует панику, это значение error будет настроено, в противном случае это значение будет nil. Сравнив переменную err с nil, мы можем обнаружить наличие паники, а в данном случае мы будем регистрировать панику с помощью функции log.Println, как при любой другой ошибке.

      После этой отсроченной анонимной функции мы вызываем другую функцию, которую мы определили, divide, и попытаемся вывести ее результаты с помощью fmt.Println. Предоставленные аргументы приведут к тому, что divide будет выполнять деление на ноль, что вызовет панику.

      В выводе этого примера мы вначале видим сообщение журнала от анонимной функции, которая восстанавливает панику, за которым следует сообщение we survived dividing by zero!. Мы действительно сделали это благодаря встроенной функции recover, которая останавливает вызывающую катастрофические последствия панику, которая могла вызвать прекращение работы программы Go.

      Значение err, возвращаемое recover(), — это то же самое значение, которое было предоставлено при вызове panic(). Поэтому особенно важно убедиться, что значение err равно nil только при отсутствии паники.

      Обнаружение паник с помощью recover

      Функция recover опирается на значение ошибки при определении того, была ли сгенерирована паника или нет. Поскольку аргументом для функции panic служит пустой интерфейс, это может быть любой тип. Нулевое значение для любого типа, включая пустой интерфейс, — это nil. Необходимо избегать применения nil в качестве аргумента для panic, как показано в данном примере:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          if b == 0 {
              panic(nil)
          }
          return a / b
      }
      
      

      Результат будет выглядеть так:

      Output

      we survived dividing by zero!

      Этот пример повторяет предыдущий пример, использующий recover, с некоторыми изменениями. Функция divide была изменена, чтобы проверить, имеет ли разделитель b значение 0. Если да, она генерирует панику с помощью встроенной функции panic с аргументом nil. Вывод на этот раз не включает сообщение журнала, демонстрируя, что паника возникала даже при его создании с помощью функции divide. Именно это молчаливое поведение служит причиной того, что очень важно убедиться, что аргумент функции panic не равен nil.

      Заключение

      Мы рассмотрели ряд способов создания паник в Go и то, как их можно остановить с помощью встроенной функции recover. Хотя вы не обязательно будете использовать панику самостоятельно, надлежащее восстановление для паник — это важный шаг в подготовке к работе приложений Go.

      Также вы можете ознакомиться с нашей серией статей о программировании на языке Go.



      Source link