文档菜单
文档首页
/ / /
PyMongo
/

自定义类型

本页面内容

  • 概述
  • 编码自定义类型
  • 定义类型编解码器类
  • 将编解码器添加到类型注册表
  • 获取集合引用
  • 编码子类型
  • 定义后备编码器
  • 编码未知类型
  • 限制
  • API文档

本指南解释了如何使用PyMongo对自定义类型进行编码和解码。

如果您想存储驱动程序不理解的数据类型,可能需要定义一个自定义类型。例如,BSON库的Decimal128类型与Python的内置Decimal类型不同。尝试使用PyMongo保存Decimal实例将导致InvalidDocument异常,如下面的代码示例所示

from decimal import Decimal
num = Decimal("45.321")
db["coll"].insert_one({"num": num})
Traceback (most recent call last):
...
bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'), of
type: <class 'decimal.Decimal'>

以下部分显示了如何为此Decimal类型定义一个自定义类型。

要编码自定义类型,您必须首先定义一个类型编解码器。类型编解码器描述了如何将自定义类型的实例转换为和从bson模块已能编码的类型。

当您定义类型编解码器时,您的类必须继承自codec_options模块中的某个基本类。下表描述了这些基本类,以及何时以及如何实现它们

基本类
何时使用
要实现的成员
codec_options.TypeEncoder
从该类继承以定义一个将自定义Python类型编码为已知BSON类型的编解码器。
  • python_type属性:此类型编解码器编码的自定义Python类型

  • transform_python()方法:将自定义类型值转换为BSON可以编码的类型的函数

codec_options.TypeDecoder
从该类继承以定义一个将指定的BSON类型解码为自定义Python类型的编解码器。
  • bson_type属性:此类型编解码器解码的BSON类型

  • transform_bson()方法:将标准BSON类型值转换为自定义类型的函数

codec_options.TypeCodec
从该类继承以定义一个可以编码和解码自定义类型的编解码器。
  • python_type属性:此类型编解码器编码的自定义Python类型

  • bson_type属性:此类型编解码器解码的BSON类型

  • transform_bson()方法:将标准BSON类型值转换为自定义类型的函数

  • transform_python()方法:将自定义类型值转换为BSON可以编码的类型的函数

因为示例Decimal自定义类型可以转换为和从Decimal128实例,因此必须定义如何编码和解码此类型。因此,Decimal类型编解码器类必须继承自TypeCodec基本类

from bson.decimal128 import Decimal128
from bson.codec_options import TypeCodec
class DecimalCodec(TypeCodec):
python_type = Decimal
bson_type = Decimal128
def transform_python(self, value):
return Decimal128(value)
def transform_bson(self, value):
return value.to_decimal()

定义自定义类型编解码器后,必须将其添加到PyMongo的类型注册表中,这是驱动程序可以编码和解码的类型列表。为此,创建一个TypeRegistry类实例,并在列表中传入您的类型编解码器类实例。如果您创建了多个自定义编解码器,可以将它们全部传递给TypeRegistry构造函数。

以下代码示例将DecimalCodec类型编解码器实例添加到类型注册表

from bson.codec_options import TypeRegistry
decimal_codec = DecimalCodec()
type_registry = TypeRegistry([decimal_codec])

注意

实例化后,注册表不可变,向注册表中添加编解码器的唯一方法是创建一个新的注册表。

最后,定义一个codec_options.CodecOptions实例,将你的TypeRegistry对象作为关键字参数传递。将你的CodecOptions对象传递给get_collection()方法以获取一个可以使用你自定义类型的集合

from bson.codec_options import CodecOptions
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)

然后你可以对Decimal类的实例进行编码和解码

import pprint
collection.insert_one({"num": Decimal("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal('45.321')}

要查看MongoDB如何存储自定义类型的实例,创建一个新的集合对象,不包含自定义编解码器选项,然后使用它检索包含自定义类型的文档。以下示例显示PyMongo将Decimal类的实例存储为Decimal128

import pprint
new_collection = db.get_collection("test")
pprint.pprint(new_collection.find_one())
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}

你可能还需要编码继承自你的自定义类型的一个或多个类型。考虑以下Decimal类的子类型,它包含一个返回其值作为整数的方法

class DecimalInt(Decimal):
def get_int(self):
return int(self)

如果你在没有先为它注册类型编解码器的情况下尝试保存DecimalInt类的实例,PyMongo将引发错误

collection.insert_one({"num": DecimalInt("45.321")})
Traceback (most recent call last):
...
bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'),
of type: <class 'decimal.Decimal'>

要编码DecimalInt类的实例,你必须为该类定义一个类型编解码器。此类型编解码器必须从父类编解码器DecimalCodec继承,如下例所示

class DecimalIntCodec(DecimalCodec):
@property
def python_type(self):
# The Python type encoded by this type codec
return DecimalInt

然后你可以将子类的类型编解码器添加到类型注册表中,并编码自定义类型的实例

import pprint
from bson.codec_options import CodecOptions
decimal_int_codec = DecimalIntCodec()
type_registry = TypeRegistry([decimal_codec, decimal_int_codec])
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)
collection.insert_one({"num": DecimalInt("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal('45.321')}

注意

DecimalCodec类的transform_bson()方法导致这些值被解码为Decimal,而不是DecimalInt

您还可以注册一个 回退编码器,一个用于编码由 BSON 无法识别的类型且未注册类型编解码器的可调用对象。回退编码器接受一个无法编码的值作为参数,并返回一个 BSON 可编码值。

以下回退编码器将 Python 的 Decimal 类型编码为 Decimal128

def fallback_encoder(value):
if isinstance(value, Decimal):
return Decimal128(value)
return value

声明回退编码器后,执行以下步骤

  • 构造一个新的 TypeRegistry 类实例。使用 fallback_encoder 关键字参数传入回退编码器。

  • 构造一个新的 CodecOptions 类实例。使用 type_registry 关键字参数传入 TypeRegistry 实例。

  • 调用 get_collection() 方法。使用 codec_options 关键字参数传入 CodecOptions 实例。

以下代码示例显示了此过程

type_registry = TypeRegistry(fallback_encoder=fallback_encoder)
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)

然后您可以使用此引用的集合来存储 Decimal 类的实例

import pprint
collection.insert_one({"num": Decimal("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}

注意

回退编码器在尝试使用标准 BSON 编码器和任何配置的类型编码器编码给定值失败后调用。因此,在配置了针对同一自定义类型的类型编码器和回退编码器的类型注册表中,类型编码器中指定的行为优先。

因为回退编码器不需要预先声明它们要编码的类型,所以您可以在 TypeEncoder 无效的情况下使用它们。例如,您可以使用回退编码器将任意对象保存到 MongoDB。考虑以下任意自定义类型

class MyStringType(object):
def __init__(self, value):
self.__value = value
def __repr__(self):
return "MyStringType('%s')" % (self.__value,)
class MyNumberType(object):
def __init__(self, value):
self.__value = value
def __repr__(self):
return "MyNumberType(%s)" % (self.__value,)

您可以定义一个回退编码器,将接收到的对象序列化并作为具有自定义子类型的 Binary 实例返回。此自定义子类型允许您定义一个 TypeDecoder 类,该类在检索时识别序列化的工件,并将它们透明地解码回 Python 对象

import pickle
from bson.binary import Binary, USER_DEFINED_SUBTYPE
from bson.codec_options import TypeDecoder
def fallback_pickle_encoder(value):
return Binary(pickle.dumps(value), USER_DEFINED_SUBTYPE)
class PickledBinaryDecoder(TypeDecoder):
bson_type = Binary
def transform_bson(self, value):
if value.subtype == USER_DEFINED_SUBTYPE:
return pickle.loads(value)
return value

然后您可以将 PickledBinaryDecoder 添加到集合的编解码器选项中,并使用它来编码和解码您的自定义类型

from bson.codec_options import CodecOptions,TypeRegistry
codec_options = CodecOptions(
type_registry=TypeRegistry(
[PickledBinaryDecoder()], fallback_encoder=fallback_pickle_encoder
)
)
collection = db.get_collection("test", codec_options=codec_options)
collection.insert_one(
{"_id": 1, "str": MyStringType("hello world"), "num": MyNumberType(2)}
)
my_doc = collection.find_one()
print(isinstance(my_doc["str"], MyStringType))
print(isinstance(my_doc["num"], MyNumberType))
True
True

PyMongo 类型编解码器和回退编解码器有以下限制

  • 您无法自定义 PyMongo 已理解的 Python 类型的编码行为,例如 intstr。如果您尝试使用一个或多个作用于内置类型的编解码器实例化一个类型注册表,PyMongo 将引发一个 TypeError。此限制也适用于所有标准类型的子类型。

  • 您不能链式使用类型编解码器。一旦自定义类型值通过编解码器的 transform_python() 方法转换,必须 得到一个默认情况下可被 BSON 编码的类型,或者可以被回退编解码器转换成可被 BSON 编码的类型。它不能被不同的类型编解码器再次转换。

  • 在解码命令响应文档时,Database.command() 方法不应用自定义类型解码器。

  • gridfs 类不会对其接收或返回的任何文档应用自定义类型编码或解码。

有关自定义类型编码和解码的更多信息,请参阅以下 API 文档

  • Codec

  • Encoder

  • Decoder

  • Registry

  • 编解码器选项

返回

数据格式