延遲評估能將文字翻譯延遲到請求上下文,避免在渲染範本前就執行翻譯。在 Flask 中,使用 lazy_gettext 函式即可實作。搭配 ngettext 函式,可以根據單複數變化調整翻譯內容,例如頁面計數的顯示。全域語言切換功能則透過修改 URL 規則,在每個請求前取得語言設定,並利用自定義的 url_for 函式自動加入語言引數,實作無縫切換。除了文字翻譯,Babel 也提供數字、貨幣等格式的本地化功能,例如使用 numbers.format_currency 根據不同地區顯示貨幣格式。最後,文章也提及了日誌記錄和錯誤處理,例如設定檔案日誌和在發生錯誤時傳送電子郵件通知,提升應用程式的穩定性和可維護性。
實作延遲評估與 gettext/ngettext 函式
延遲評估是一種評估策略,它會將表示式的評估延遲到需要其值時才進行;也就是說,這是一種「需要時才呼叫」的機制。在我們的應用程式中,可能有多個文字例項是在渲染範本時才被評估的。這通常發生在我們將文字標記為可在請求上下文之外進行翻譯時,因此我們會將這些文字的評估延遲到實際需要時才進行。
準備工作
讓我們從前一個配方中的應用程式開始。現在,我們希望產品和類別建立表單中的標籤顯示翻譯後的值。
實作步驟
請按照以下步驟實作翻譯的延遲評估:
- 要將產品和類別表單中的所有欄位標籤標記為可翻譯的,請對
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 _
- 現在,我們有更多的文字需要翻譯。假設我們要翻譯產品建立閃現訊息內容,其內容如下:
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。
程式碼解密:
- 使用
_()函式將文字標記為可翻譯。 - 使用
lazy_gettext替代gettext以實作延遲評估。 - 使用
ngettext()處理單數和複數翻譯。
實作全域語言切換動作
在前面的配方中,我們看到語言會根據瀏覽器中的目前語言偏好設定而變更。現在,我們希望有一種機制可以切換正在使用的語言,而不受瀏覽器中的語言影響。在本配方中,我們將瞭解如何處理應用程式層級的語言變更。
準備工作
我們從上一個配方「實作延遲評估與 gettext/ngettext 函式」的應用程式開始,進行修改以容納變更,從而啟用語言切換。我們將在所有路由中新增一個額外的 URL 部分,以允許我們新增目前語言。
實作步驟
請按照以下步驟瞭解如何全域實作語言切換:
首先,修改所有 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 規則都需要進行相同的修改。現在,新增一個函式,它將把 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() 方法。
程式碼解密:
- 使用
@app.before_request修飾符在每個請求之前執行函式。 - 使用
g物件儲存目前語言。 - 使用
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
內容解密:
numbers.format_number用於根據指定地區格式化數字,例如en_US、fr_FR等。numbers.format_decimal用於格式化小數,同樣根據地區調整小數點符號。numbers.format_currency用於格式化貨幣,會根據貨幣程式碼和地區進行適當的格式化。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 不會在任何地方記錄任何資訊,除了錯誤的堆積疊追蹤會被傳送到記錄器。在開發模式下執行應用程式時,會產生很多堆積疊追蹤,但在生產環境中,我們沒有這種便利。幸運的是,日誌函式庫提供了多種日誌處理程式,可以根據需求使用。
操作步驟
- 首先,修改
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 錯誤)的電子郵件。