文档首页 → 开发应用程序 → Python 驱动程序 → PyMongo
自定义类型
概述
本指南解释了如何使用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类型。 |
|
codec_options.TypeDecoder | 通过继承此类来定义一个编解码器,该编解码器将指定的BSON类型解码为自定义Python类型。 |
|
codec_options.TypeCodec | 通过继承此类来定义可以同时编码和解码自定义类型的编解码器。 |
|
因为示例自定义类型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): 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,)
您可以为接收到的对象定义一个回退编码器,该编码器将对象序列化为具有自定义子类型的 二进制
实例。这种自定义子类型允许您定义一个 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 类型的编码行为,如
int
和str
。如果您尝试使用一个或多个作用于内置类型的编解码器实例化类型注册表,PyMongo 会引发一个TypeError
。此限制也适用于所有标准类型的所有子类型。您无法链式使用类型编解码器。一旦自定义类型值被编解码器的
transform_python()
方法转换,它 必须 转换为默认可 BSON 编码的类型,或者可以被回退编码器转换为一个可 BSON 编码的类型。它 不能 被另一个类型编解码器进行第二次转换。在解码命令响应文档时,
Database.command()
方法不应用自定义类型解码器。gridfs
类不会对它接收或返回的任何文档应用自定义类型编码或解码。
API 文档
有关编码和解码自定义类型的更多信息,请参阅以下 API 文档: