---
title: "Unicode 数据"
version: 3.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/3.1/ref/unicode/
canonical: https://djangodocs.dev/zh-hans/3.1/ref/unicode/
---
# Unicode 数据

Django 处处支持 Unicode 数据。

本文档告诉你，如果你要编写使用 ASCII 以外编码的数据或模板的应用程序，你需要知道什么。

## 创建数据库

确保你的数据库被配置成能够存储任意字符串数据。通常，这意味着给它一个 UTF-8 或 UTF-16 的编码。如果你使用更严格的编码——例如，latin1（iso8859-1）——你将无法在数据库中存储某些字符，信息将丢失。

- MySQL 用户，请参考 [MySQL 手册](https://dev.mysql.com/doc/refman/en/charset-database.html) ，了解如何设置或更改数据库字符集编码。
- PostgreSQL 用户，请参考 [PostgreSQL 手册](https://www.postgresql.org/docs/current/multibyte.html) （PostgreSQL 9 中的 22.3.2 节），了解以正确编码创建数据库的细节。
- 关于如何设置（[第 2 节](https://docs.oracle.com/en/database/oracle/oracle-database/18/nlspg/index.html) ）或改变（[第 11 节](https://docs.oracle.com/en/database/oracle/oracle-database/18/nlspg/choosing-character-set.html) ）数据库字符集编码，请参考 [Oracle 手册](https://docs.oracle.com/en/database/oracle/oracle-database/18/nlspg/character-set-migration.html) 。
- SQLite 用户，你不需要做什么。SQLite 一直使用 UTF-8 作为内部编码。

所有 Django 的数据库后端都会自动将字符串转换为合适的编码来与数据库对话。它们也会自动将从数据库中获取的字符串转换为字符串。你甚至不需要告诉 Django 你的数据库使用什么编码：这都是透明的处理。

更多内容，请看下面的“数据库 API”一节。

## 一般字符串处理

当你在 Django 中使用字符串时——例如，在数据库查询、模板渲染或其他任何地方——你有两种选择来编码这些字符串。你可以使用普通字符串或字节字符串（以 'b' 开头）。

> **Warning**
>
> 一个字节字符串并没有携带任何关于其编码的信息。为此，我们必须做一个假设，Django 假设所有的字节字符串都是 UTF-8 编码。
>
> 如果你传递给 Django 的字符串是用其他格式编码的，那么事情会以有趣的方式出错。通常情况下，Django 会在某些时候引发一个 `UnicodeDecodeError`。

如果你的代码只使用 ASCII 数据，那么使用普通的字符串是安全的，可以随意传递它们，因为 ASCII 是 UTF-8 的一个子集。

不要傻傻的认为如果你将 [`DEFAULT_CHARSET`](/zh-hans/3.1/ref/settings/#std-setting-DEFAULT_CHARSET) 设置为 `'utf-8'` 以外的其他编码，你就可以在你的字节字符串中使用其他编码。[`DEFAULT_CHARSET`](/zh-hans/3.1/ref/settings/#std-setting-DEFAULT_CHARSET) 只适用于模板渲染后生成的字符串（和电子邮件）。Django 对内部的字节字符串总是采用 UTF-8 编码。原因是 [`DEFAULT_CHARSET`](/zh-hans/3.1/ref/settings/#std-setting-DEFAULT_CHARSET) 的配置实际上并不在你的控制之下（如果你是应用开发者的话）。它是在安装和使用你的应用程序的人的控制之下——如果这个人选择了不同的配置，你的代码仍然必须继续工作。因此，它不能依赖该配置。

在大多数情况下，当 Django 处理字符串时，它会在做其他事情之前将它们转换为字符串。所以，一般来说，如果你传入一个字节字符串，要准备好在结果中收到一个字符串。

### 翻译后的字符串

除了字符串和字节字符串之外，还有第三种类型的字符串类对象，你在使用 Django 时可能会遇到。框架的国际化特性引入了“惰性翻译”的概念——一个已经被标记为翻译的字符串，但其实际的翻译结果直到该对象被用于字符串时才被确定。这个功能在以下情况下非常有用：在使用字符串之前，翻译的 locale 是未知的，即使该字符串可能是在第一次导入代码时创建的。

通常情况下，你不必担心惰性翻译的问题。只是要注意，如果你检查一个对象，并且它声称是 `django.utils.functional.__proxy__` 对象，它就是一个惰性翻译。调用 `str()` 作为参数调用 `str()` 将生成一个当前语言环境下的字符串。

关于惰性翻译对象的更多细节，请参考 [国际化](/zh-hans/3.1/topics/i18n/) 文档。

### 有用的实用工具函数

因为有些字符串操作会反复出现，所以 Django 提供了一些有用的函数，这些函数可以使处理字符串和字节字符串对象变得更加容易。

#### 转换函数

`django.utils.encoding` 模块包含了一些函数，这些函数可以方便地在字符串和字节字符串之间来回转换。

- `smart_str(s, encoding='utf-8', strings_only=False, errors='strict')` 将输入转换为字符串。`encoding` 参数指定了输入的编码。（例如，Django 内部在处理表单输入数据时使用了这个参数，这些数据可能不是 UTF-8 编码的。） `strings_only` 参数，如果设置为 True，将导致 Python 数字、布尔值和 `None` 不会被转换为字符串（它们保持原来的类型）。`errors` 参数取 Python 的 `str()` 函数接受的任何值来处理错误。
- `force_str(s, encoding='utf-8', strings_only=False, errors='strict')` 几乎在所有情况下都和 `smart_str()` 相同。区别在于当第一个参数是 [惰性翻译](/zh-hans/3.1/topics/i18n/translation/#lazy-translations) 实例时。`smart_str()` 保留了惰性翻译，而 `force_str()` 将这些对象强制为字符串（导致翻译执行）。通常，你会想使用 `smart_str()`。然而，`force_str()` 在模板标签和过滤器中是有用的，它们绝对 *必须* 有一个字符串来工作，而不仅仅是可以转换为字符串的东西。
- `smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')` 本质上与 `smart_str()` 相反。它迫使第一个参数变成一个字节字符串。`strings_only` 参数的行为与 `smart_str()` 和 `force_str()` 相同。这与 Python 内置的 `str()` 函数的语义略有不同，但在 Django 内部的一些地方需要区分。

通常，你只需要使用 `force_str()`。在任何可能是字符串或字节字符串的输入数据上尽可能早地调用它，从那时起，你可以将结果视为始终是字符串。

#### URI 和 IRI 处理

网络框架必须处理 URL（是 IRI 的一种）。URL 的一个要求是，它们只能使用 ASCII 字符进行编码。然而，在国际环境中，您可能需要从 [**IRI**](https://datatracker.ietf.org/doc/html/rfc3987.html) 构建一个 URL —— 非常宽泛地讲，一个可以包含 Unicode 字符的 [**URI**](https://datatracker.ietf.org/doc/html/rfc2396.html)。使用这些函数来引用 IRI 并将其转换为 URI。

- [`django.utils.encoding.iri_to_uri()`](/zh-hans/3.1/ref/utils/#django.utils.encoding.iri_to_uri) 函数，它实现了 [**RFC 3987 Section 3.1**](https://datatracker.ietf.org/doc/html/rfc3987.html#section-3.1) 所要求的从 IRI 到 URI 的转换。
- 来自 Python 标准库的 [`urllib.parse.quote()`](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.quote) 和 [`urllib.parse.quote_plus()`](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.quote_plus) 函数。

这两组函数的目的略有不同，因此必须将它们区分开来。通常，你会在 IRI 或 URI 路径的个别部分使用 `quote()`，这样任何保留的字符如“&”或“%”都会被正确编码。然后，你将 `iri_to_uri()` 应用于整个 IRI，它将任何非 ASCII 字符转换为正确的编码值。

> **Note**
>
> 从技术上讲，说 `iri_to_uri()` 实现了 IRI 规范中的全部算法是不正确的。它并不（尚未）执行算法的国际域名编码部分。

`iri_to_uri()` 函数不会改变 URL 中允许的 ASCII 字符。因此，例如，当传递给 `iri_to_uri()` 时，字符“%”不会被进一步编码。这意味着你可以将一个完整的 URL 传递给这个函数，它不会弄乱查询字符串或类似的东西。

举个例子可以说明这里的情况：

```
>>> from urllib.parse import quote
>>> from django.utils.encoding import iri_to_uri
>>> quote('Paris & Orléans')
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri('/favorites/François/%s' % quote('Paris & Orléans'))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'
```

如果你仔细观察，你可以看到第二个例子中由 `quote()` 生成的部分在传递给 `iri_to_uri()` 时没有被双引号。这是一个非常重要和有用的功能。这意味着你可以构建你的 IRI，而不用担心它是否包含非 ASCII 字符，然后在最后调用 `iri_to_uri()` 对结果进行处理。

类似的，Django 提供了 [`django.utils.encoding.uri_to_iri()`](/zh-hans/3.1/ref/utils/#django.utils.encoding.uri_to_iri)，它按照 [**RFC 3987 Section 3.2**](https://datatracker.ietf.org/doc/html/rfc3987.html#section-3.2) 实现了从 URI 到 IRI 的转换。

举例说明：

```
>>> from django.utils.encoding import uri_to_iri
>>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
'/♥♥/?utf8=✓'
>>> uri_to_iri('%A9hello%3Fworld')
'%A9hello%3Fworld'
```

在第一个例子中，UTF-8 字符没有被引用。在第二个例子中，百分比编码保持不变，因为它们位于有效的 UTF-8 范围之外或代表一个保留字符。

`iri_to_uri()` 和 `uri_to_iri()` 函数都是幂等的，这意味着以下内容始终为真：

```
iri_to_uri(iri_to_uri(some_string)) == iri_to_uri(some_string)
uri_to_iri(uri_to_iri(some_string)) == uri_to_iri(some_string)
```

所以你可以安全地在同一个 URI／IRI 上多次调用它，而不会有重复引用问题的风险。

## 模型

因为所有的字符串都是以 `str` 对象的形式从数据库中返回的，所以当 Django 从数据库中检索数据时，基于字符的模型字段（CharField、TextField、URLField 等）将包含 Unicode 值。这 *总是* 这样的情况，即使数据可以放入 ASCII 字节字符串中。

你可以在创建模型或填充字段时传入字节字符串，Django 会在需要时将其转换为字符串。

### 在 `get_absolute_url()` 中注意

URL 只能包含 ASCII 字符。如果你从可能是非 ASCII 码的数据中构建一个 URL，要注意将结果编码成适合 URL 的方式。[`reverse()`](/zh-hans/3.1/ref/urlresolvers/#django.urls.reverse) 函数会自动为你处理这个问题。

如果你是手动构建一个 URL（即 *不* 使用 `reverse()` 函数)，你就需要自己进行编码。在这种情况下，请使用 [上面](#id1) 记载的 `iri_to_uri()` 和 `quote()` 函数。例如：

```
from urllib.parse import quote
from django.utils.encoding import iri_to_uri

def get_absolute_url(self):
    url = '/person/%s/?x=0&y=0' % quote(self.location)
    return iri_to_uri(url)
```

即使 `self.location` 是类似于“Jack visited Paris & Orléans”，这个函数也会返回一个正确编码的 URL。（事实上，在上面的例子中，`iri_to_uri()` 的调用并不是绝对必要的，因为所有非 ASCII 字符都会在第一行的引号中被删除。）

## 模板

手动创建模板时使用字符串：

```
from django.template import Template
t2 = Template('This is a string template.')
```

但常见的情况是从文件系统中读取模板。如果你的模板文件没有采用 UTF-8 编码存储，请调整 [`TEMPLATES`](/zh-hans/3.1/ref/settings/#std-setting-TEMPLATES) 配置。内置的 [`django`](/zh-hans/3.1/topics/templates/#module-django.template.backends.django) 后端提供了 `'file_charset'` 选项，可以改变从磁盘读取文件时使用的编码。

[`DEFAULT_CHARSET`](/zh-hans/3.1/ref/settings/#std-setting-DEFAULT_CHARSET) 配置控制了渲染模板的编码。默认配置为 UTF-8。

### 模板标签和过滤器

在编写自己的模板标签和过滤器时，要记住几个小技巧：

- 总是从模板标签的 `render()` 方法和模板过滤器中返回字符串：
- 在这些地方使用 `force_str()`，而不是 `smart_str()`。标签渲染和过滤器调用是在模板渲染时发生的，所以推迟将懒惰翻译对象转换为字符串没有好处。这时只用字符串工作更容易。

## 文件

如果你打算允许用户上传文件，你必须确保用于运行 Django 的环境被配置成可以使用非 ASCII 文件名。如果你的环境配置不正确，当保存文件名包含非 ASCII 字符的文件时，你会遇到 `UnicodeEncodeError` 异常。

文件系统对 UTF-8 文件名的支持有所不同，可能取决于环境。在交互式 Python shell 中运行检查你当前的配置：

```
import sys
sys.getfilesystemencoding()
```

这应该输出“UTF-8”。

`LANG` 环境变量负责设置 Unix 平台上的预期编码。请查阅操作系统和应用服务器的文档，了解设置这个变量的适当语法和位置。

在你的开发环境中，你可能需要在你的 `~.bashrc` 中添加一个类似于这样的设置：

```
export LANG="en_US.UTF-8"
```

## 表单提交

HTML 表单提交是一个棘手的领域。无法保证提交的数据会包含编码信息，这意味着框架可能不得不猜测提交数据的编码。

Django 采用了一种“惰性”的方式来解码表单数据。`HttpRequest` 对象中的数据只有在你访问它时才会被解码。事实上，大部分数据根本没有被解码。只有 `HttpRequest.GET` 和 `HttpRequest.POST` 数据结构有任何解码应用。这两个字段将作为 Unicode 数据返回其成员。`HttpRequest` 的所有其他属性和方法将完全按照客户端提交的数据返回。

默认情况下，[`DEFAULT_CHARSET`](/zh-hans/3.1/ref/settings/#std-setting-DEFAULT_CHARSET) 配置被用作表单数据的假定编码。如果你需要为一个特定的表单改变这个配置，你可以在一个 `HttpRequest` 实例上设置 `encoding` 属性。例如：

```
def some_view(request):
    # We know that the data must be encoded as KOI8-R (for some reason).
    request.encoding = 'koi8-r'
    ...
```

你甚至可以在访问了 `request.GET` 或 `request.POST` 之后改变编码，所有后续的访问都将使用新的编码。

大多数开发人员不需要担心更改表单编码，但对于那些与编码无法控制的传统系统对话的应用程序来说，这是一个有用的功能。

Django 不会对文件上传的数据进行解码，因为这些数据通常被视为字节的集合，而不是字符串。任何自动解码都会改变字节流的含义。
