延遲評估能將文字翻譯延遲到請求上下文,避免在渲染範本前就執行翻譯。在 Flask 中,使用 lazy_gettext 函式即可實作。搭配 ngettext 函式,可以根據單複數變化調整翻譯內容,例如頁面計數的顯示。全域語言切換功能則透過修改 URL 規則,在每個請求前取得語言設定,並利用自定義的 url_for 函式自動加入語言引數,實作無縫切換。除了文字翻譯,Babel 也提供數字、貨幣等格式的本地化功能,例如使用 numbers.format_currency 根據不同地區顯示貨幣格式。最後,文章也提及了日誌記錄和錯誤處理,例如設定檔案日誌和在發生錯誤時傳送電子郵件通知,提升應用程式的穩定性和可維護性。

實作延遲評估與 gettext/ngettext 函式

延遲評估是一種評估策略,它會將表示式的評估延遲到需要其值時才進行;也就是說,這是一種「需要時才呼叫」的機制。在我們的應用程式中,可能有多個文字例項是在渲染範本時才被評估的。這通常發生在我們將文字標記為可在請求上下文之外進行翻譯時,因此我們會將這些文字的評估延遲到實際需要時才進行。

準備工作

讓我們從前一個配方中的應用程式開始。現在,我們希望產品和類別建立表單中的標籤顯示翻譯後的值。

實作步驟

請按照以下步驟實作翻譯的延遲評估:

  1. 要將產品和類別表單中的所有欄位標籤標記為可翻譯的,請對 my_app/catalog/models.py 進行以下修改:

from flask_babel import lazy_gettext as _

class NameForm(FlaskForm): name = StringField( _(‘Name’), validators=[InputRequired()])

class ProductForm(NameForm): price = DecimalField(_(‘Price’), validators=[ InputRequired(), NumberRange(min=Decimal(‘0.0’)) ]) category = CategoryField( _(‘Category’), validators=[InputRequired()], coerce=int ) image = FileField( _(‘Product Image’), validators=[FileRequired()])

請注意,所有欄位標籤都包含在 `_()` 中,以被標記為可翻譯。

2. 現在,執行 `extract` 和 `update pybabel` 命令以更新 `messages.po` 檔案,然後填寫相關翻譯並執行 `compile` 命令。有關詳細資訊,請參閱前面的配方「新增語言」。

3. 現在,開啟產品建立頁面,網址為 `http://127.0.0.1:5000/product-create`。它是否按預期工作?不!正如大多數人已經猜到的那樣,這種行為的原因是此文字在請求上下文之外被標記為可翻譯。要使其正常運作,請將匯入陳述式修改為以下內容:
```python
from flask_babel import lazy_gettext as _
  1. 現在,我們有更多的文字需要翻譯。假設我們要翻譯產品建立閃現訊息內容,其內容如下:

flash(_(‘The product %(name)s has been created’, name=name), ‘success’)

結果翻譯後的文字將是 `La produit %(name)s a été créée` 之類別的內容。

5. 在某些情況下,我們需要根據專案數量(即單數或複數名稱)來管理翻譯。這由 `ngettext()` 方法處理。讓我們舉一個例子,我們要在 `products.html` 範本中顯示頁數。為此,請新增以下程式碼:
```html
{{ ngettext('%(num)d page', '%(num)d pages',
products.pages) }}

在這裡,如果只有一頁,範本將渲染 page;如果有多頁,則渲染 pages

程式碼解密:

  1. 使用 _() 函式將文字標記為可翻譯。
  2. 使用 lazy_gettext 替代 gettext 以實作延遲評估。
  3. 使用 ngettext() 處理單數和複數翻譯。

實作全域語言切換動作

在前面的配方中,我們看到語言會根據瀏覽器中的目前語言偏好設定而變更。現在,我們希望有一種機制可以切換正在使用的語言,而不受瀏覽器中的語言影響。在本配方中,我們將瞭解如何處理應用程式層級的語言變更。

準備工作

我們從上一個配方「實作延遲評估與 gettext/ngettext 函式」的應用程式開始,進行修改以容納變更,從而啟用語言切換。我們將在所有路由中新增一個額外的 URL 部分,以允許我們新增目前語言。

實作步驟

請按照以下步驟瞭解如何全域實作語言切換:

  1. 首先,修改所有 URL 規則以容納一個額外的 URL 部分。@catalog.route('/') 將變成 @catalog.route('/<lang>/'),而 @catalog.route('/home') 將變成 @catalog.route('/<lang>/home')。同樣地, @catalog.route('/product-search/<int:page>') 將變成 @catalog.route('/<lang>/product-search/<int:page>')。所有 URL 規則都需要進行相同的修改。

  2. 現在,新增一個函式,它將把 URL 中傳遞的語言新增到全域代理物件 g

@app.before_request def before(): if request.view_args and ’lang’ in request.view_args: g.current_lang = request.view_args[’lang’] request.view_args.pop(’lang’)

此方法將在每個請求之前執行,並將目前語言新增到 `g`。

3. 但是,這意味著應用程式中的所有 `url_for()` 呼叫都需要被修改,以便可以傳遞一個名為 `lang` 的額外引數。幸運的是,有一個簡單的方法可以解決這個問題,如下所示:
```python
from flask import url_for as flask_url_for

@app.context_processor
def inject_url_for():
 return {
     'url_for': lambda endpoint, **kwargs:
         flask_url_for(
             endpoint, lang=g.get('current_lang', 'en'), **kwargs
         )
 }
url_for = inject_url_for()['url_for']

在上述程式碼中,我們首先從 flask 匯入了 url_for 作為 flask_url_for。然後,我們更新了應用程式上下文處理器,使其具有 url_for() 函式,這是 Flask 提供的 url_for() 的修改版本,以便將 lang 作為一個額外引數。同時,我們使用了與檢視中相同的 url_for() 方法。

程式碼解密:

  1. 使用 @app.before_request 修飾符在每個請求之前執行函式。
  2. 使用 g 物件儲存目前語言。
  3. 使用 url_for() 的自定義版本自動包含語言引數。

實作全域語言切換功能

在開發國際化的 Flask 應用程式時,支援多語言是基本需求之一。本章節將探討如何實作語言切換功能,以及如何處理不同地區的數字、貨幣等格式。

語言切換功能運作原理

執行目前的應用程式,你會注意到所有的 URL 都包含語言部分。以下兩個截圖展示了渲染後的範本樣式。

當存取 http://127.0.0.1:5000/en/home 時,首頁會以英文顯示,如下圖所示: 此圖示 首頁(英文)

而當存取 http://127.0.0.1:5000/fr/home 時,首頁則會以法文顯示,如下: 此圖示 首頁(法文)

更多關於本地化(l10n)的內容

本地化不僅僅是翻譯文字,不同地區對於數字、小數、貨幣等的格式也有所不同。例如,1.5 百萬美元在荷蘭語中會寫成 1,5 Mio USD,而在英文中則是 $1,500,000。Babel 函式庫讓實作這些格式變得非常簡單。

使用 Babel 處理數字格式範例

from babel import numbers

print(numbers.format_number(12345, 'en_US'))  # 輸出:12,345
print(numbers.format_number(12345, 'fr_FR'))  # 輸出:12 345
print(numbers.format_number(12345, 'de_DE'))  # 輸出:12.345
print(numbers.format_decimal(12.345, locale='de_DE'))  # 輸出:12,345
print(numbers.format_decimal(12.345, locale='en_US'))  # 輸出:12.345
print(numbers.format_currency(12.345, 'USD', locale='en_US'))  # 輸出:$12.34
print(numbers.format_currency(12345789, 'USD', locale='en_US'))  # 輸出:$12,345,789.00
print(numbers.format_compact_currency(12345789, 'USD', locale='de_DE'))  # 輸出:12 Mio. $
print(numbers.format_compact_currency(12345789, 'USD', locale='en_US'))  # 輸出:$12M

內容解密:

  1. numbers.format_number 用於根據指定地區格式化數字,例如 en_USfr_FR 等。
  2. numbers.format_decimal 用於格式化小數,同樣根據地區調整小數點符號。
  3. numbers.format_currency 用於格式化貨幣,會根據貨幣程式碼和地區進行適當的格式化。
  4. numbers.format_compact_currency 用於簡潔地格式化大額貨幣,例如以百萬或千為單位顯示。

更多關於 Babel 的數字格式化功能,請參考 Babel 檔案

第三部分:進階 Flask 技術

在完成 Flask 網站應用程式的開發後,下一步便是測試、佈署和維護。本文的最後一部分將涵蓋這些重要主題,從開發轉向佈署後的相關活動。

測試與佈署的重要性

撰寫單元測試是確保應用程式穩健性的重要手段,不僅能檢查已開發的功能,還能提前發現未來開發中可能出現的問題。佈署後,測量應用程式的效能是至關重要的。

本部分的章節將介紹如何使用現代技術如 Docker 和 Kubernetes 來佈署 Flask 應用程式,並探討如何整合 GPT 技術來增強應用程式的功能。

本部分包含以下章節:

  • 第 10 章:偵錯、錯誤處理與測試
  • 第 11 章:佈署與佈署後的工作
  • 第 12 章:微服務與容器化技術
  • 第 13 章:Flask 與 GPT 的整合
  • 第 14 章:額外的技巧與竅門

第 10 章:偵錯、錯誤處理與測試

到目前為止,本文主要聚焦於開發 Flask 應用程式及其功能的逐步擴充套件。然而,瞭解應用程式的穩定性和執行狀況同樣重要。這就需要有效的偵錯、錯誤處理和測試機制。

為何需要偵錯與測試?

即使程式碼本身沒有問題,應用程式在實際環境中仍可能出現各種錯誤。有效的紀錄日誌(logging)和偵錯能力,能夠幫助開發者快速找出問題所在,甚至在使用者發現之前就解決潛在問題。

Python 的內建紀錄日誌系統與 Flask 無縫整合,本章將首先介紹如何使用這套系統,接著介紹 Sentry 這類別能夠進一步簡化偵錯和錯誤紀錄的服務。

除錯、錯誤處理和測試

在應用程式開發中,測試的重要性已經被充分討論過了,現在我們來看看如何為Flask應用程式撰寫測試案例。同時,我們也會探討如何衡量程式碼覆寫率以及對應用程式進行效能分析,以找出瓶頸。

設定基本檔案日誌

預設情況下,Flask 不會在任何地方記錄任何資訊,除了錯誤的堆積疊追蹤會被傳送到記錄器。在開發模式下執行應用程式時,會產生很多堆積疊追蹤,但在生產環境中,我們沒有這種便利。幸運的是,日誌函式庫提供了多種日誌處理程式,可以根據需求使用。

操作步驟

  1. 首先,修改 my_app/__init__.py 檔案,這是應用程式的設定檔:

app.config[‘LOG_FILE’] = ‘application.log’ if not app.debug: import logging from logging import FileHandler, Formatter file_handler = FileHandler(app.config[‘LOG_FILE’]) app.logger.setLevel(logging.INFO) app.logger.addHandler(file_handler) file_handler.setFormatter(Formatter( ‘%(asctime)s %(levelname)s: %(message)s ' ‘[in %(pathname)s:%(lineno)d]’ ))

   在這裡,我們新增了一個設定引數來指定日誌檔案的位置。然後,檢查應用程式是否已經在偵錯模式下,如果不是,則新增一個檔案處理程式來記錄日誌,記錄層級為 `INFO`。

2. 接下來,在需要的地方新增日誌記錄器。例如,在 `my_app/catalog/views.py` 中:
   ```python
@catalog.route('/')
@catalog.route('/<lang>/')
@catalog.route('/<lang>/home')
@template_or_json('home.html')
def home():
    products = Product.query.all()
    app.logger.info(
        'Home page with total of %d products'
        % len(products)
    )
    return {'count': len(products)}

@catalog.route('/<lang>/product/<id>')
def product(id):
    product = Product.query.filter_by(id=id).first()
    if not product:
        app.logger.warning('Requested product not found.')
        abort(404)
    return render_template('product.html',
                           product=product)

在上述程式碼中,我們在 home()product() 檢視處理器中新增了日誌記錄器。

工作原理

上述步驟將在根應用程式資料夾中建立一個名為 application.log 的檔案。指定的日誌記錄器將會記錄到 application.log 中,內容類別似於下面的片段:

2023-01-02 13:01:25,125 INFO: Home page with total of 0 products [in /Users/apple/workspace/flask-cookbook-3/Chapter-10/Chapter-10/my_app/catalog/views.py:72]
2023-01-02 13:01:27,657 WARNING: Requested product not found. [in /Users/apple/workspace/flask-cookbook-3/Chapter-10/Chapter-10/my_app/catalog/views.py:82]

更多資訊

我們可能還想要記錄所有 404 錯誤的發生。為此,可以稍微修改 errorhandler 方法:

@app.errorhandler(404)
def page_not_found(e):
    app.logger.error(e)
    return render_template('404.html'), 404

在錯誤發生時傳送電子郵件

當應用程式發生意外錯誤時,接收通知是一個好主意。設定這個功能很容易,並且為錯誤處理過程增加了很多便利。

操作步驟

首先,在 my_app/__init__.py 中的設定中新增一個處理程式:

RECEPIENTS = ['some_receiver@gmail.com']
if not app.debug:
    import logging
    from logging import FileHandler, Formatter
    from logging.handlers import SMTPHandler
    # ... (其他設定)
    mail_handler = SMTPHandler(
        ("smtp.gmail.com", 587), 'sender@gmail.com',
        RECEPIENTS,
        'Error occurred in your application',
        ('some_email@gmail.com', 'some_gmail_password'),
        secure=())
    mail_handler.setLevel(logging.ERROR)
    app.logger.addHandler(mail_handler)
    # ... (其他設定)

在這裡,我們有一個電子郵件地址列表,當錯誤發生時,將會傳送錯誤通知郵件給這些地址。注意,我們將 mail_handler 的日誌層級設為 ERROR,因為只有在關鍵問題發生時才需要傳送電子郵件。

工作原理

要觸發內部應用程式錯誤,只需在任何處理器中誤拼某些關鍵字。你將會在你的信箱中收到一封電子郵件,郵件中包含了設定的格式和完整的堆積疊追蹤資訊。

重點提示

始終確保在執行應用程式時將偵錯旗標設為關閉,以啟用應用程式記錄和傳送內部應用程式錯誤(500 錯誤)的電子郵件。