Docs 菜单

Docs 主页开发应用程序Python 驱动程序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

然后,您可以将 sublass 的类型编解码器添加到类型注册表中,并对自定义类型的实例进行编码:

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类,该类在检索时识别 pickle 工件,并将其透明地解码回 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 文档:

  • TypeCodec

  • TypeEncoder

  • TypeDecoder

  • TypeRegistry

  • CodecOptions

← 专用数据格式