Skip to main content

Python 库将 Python 类实例(对象)转换为平面和嵌套的字典数据结构。它在将 Python 对象转换为 JSON 格式时非常有用,尤其是对于嵌套对象,因为它们不能被 js 很好地处理

项目描述

字典器

构建状态 最新版本 Python 版本 执照

dictfier是一个用于将 Python 类实例(对象)转换/序列化为平面嵌套到字典数据结构中的库。它在将 Python 对象转换为 JSON 格式时非常有用,尤其是对于嵌套对象,因为 json 库不能很好地处理它们

先决条件

蟒蛇版本> = 2.7

安装

pip install dictfier

入门

将平面对象转换为字典

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("Danish", 24)

query = [
    "name",
    "age"
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age': 24}

将嵌套对象转换为字典

import dictfier

class Course(object):
    def __init__(self, code, name):
        self.code = code
        self.name = name

class Student(object):
    def __init__(self, name, age, course):
        self.name = name
        self.age = age
        self.course = course

course = Course("CS201", "Data Structures")
student = Student("Danish", 24, course)

query = [
    "name",
    "age",
    {
        "course": [
            "code",
            "name",
        ]
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{
    'name': 'Danish',
    'age': 24,
    'course': {'code': 'CS201', 'name': 'Data Structures'}
}

将嵌套有可迭代对象的对象转换为字典

import dictfier

class Course(object):
    def __init__(self, code, name):
        self.code = code
        self.name = name

class Student(object):
    def __init__(self, name, age, courses):
        self.name = name
        self.age = age
        self.courses = courses

course1 = Course("CS201", "Data Structures")
course2 = Course("CS205", "Computer Networks")

student = Student("Danish", 24, [course1, course2])

query = [
    "name",
    "age",
    {
        "courses": [
            [
                "code",
                "name",
            ]
        ]
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{
    'name': 'Danish',
    'age': 24,
    'courses': [
        {'code': 'CS201', 'name': 'Data Structures'},
        {'code': 'CS205', 'name': 'Computer Networks'}
    ]
}

实例方法或可调用对象字段呢?

好吧,我们有个好消息,dictfier可以使用将值作为字段返回的可调用对象,这非常简单,您只需将“call=True”作为关键字参数传递给 objfield API 并将您的可调用字段添加到查询中。例如

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def age_in_days(self):
        return self.age * 365

student = Student("Danish", 24)

query = [
    "name",
    {
        "age_in_days": dictfier.objfield("age_in_days", call=True)
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age_in_days': 8760}

您还可以使用newfield API 添加自定义字段。例如

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("Danish", 24)

query = [
    "name",
    "age",
    {
        "school": dictfier.newfield("St Patrick")
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age': 24, 'school': 'St Patrick'}

如果我们想在自定义字段上使用对象字段来进行一些计算怎么办?

好吧,也有一种方法可以做到这一点,dictfier API 提供了useobj挂钩,用于挂钩或拉取当前查询节点上的对象。要使用当前对象,只需定义一个接受单个参数(它是一个对象)的函数并在该函数上执行计算然后返回结果,调用useobj并将定义的函数传递给它。

假设我们要根据年龄字段以年为单位的学生对象,以月为单位计算学生的年龄。下面是我们如何使用useobj钩子来做到这一点。

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("Danish", 24)

def age_in_months(obj):
    # Do the computation here then return the result
    return obj.age * 12

query = [
    "name",

    # This is a custom field which is computed by using age field from a student object
    # Note how age_in_months function is passed to useobj hook(This is very important for API to work)
    {"age_in_months": dictfier.useobj(age_in_months)}
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age_in_months': 288}

如果我们想在自定义字段(重命名 obj 字段)上使用对象字段怎么办?

这可以通过两种方式完成,正如您可能已经猜到的,一种方法是通过传递一个返回您要使用的字段值的函数来使用useobj钩子,另一种简单的方法是使用objfield钩子。就像useobj hook 一样,objfield hook 用于在当前查询节点上挂钩或拉取对象字段。要使用当前对象字段,只需调用objfield并传递您要使用或替换的字段名称。

假设我们想在结果中将年龄字段重命名为age_in_years。下面是我们如何使用objfield钩子来做到这一点。

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("Danish", 24)

query = [
    "name",
    {"age_in_years": dictfier.objfield("age")}
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age_in_years': 24}

如果你想使用useobj钩子,那么你会这样做。

import dictfier

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("Danish", 24)

query = [
    "name",
    {"age_in_years": dictfier.useobj(lambda obj: obj.age)}
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{'name': 'Danish', 'age_in_years': 24}

事实上objfield钩子是通过使用useobj实现的,所以这两种方法在性能方面是相同的,但我想你会同意我的观点,在这种情况下objfielduseobj更具可读性。

您还可以查询useobj挂钩返回的对象,这可以通过将查询作为第二个参数传递给useobj或使用 'query=your_query' 作为 kwarg 来完成。例如

import json
import dictfier

class Course(object):
    def __init__(self, code, name):
        self.code = code
        self.name = name
class Student(object):
    def __init__(self, name, age, course):
        self.name = name
        self.age = age
        self.course = course

course = Course("CS201", "Data Structures")
student = Student("Danish", 24, course)
query = [
    "name",
    "age",
    {
        "course": dictfier.useobj(
            lambda obj: obj.course,
            ["name", "code"]  # This is a query
        )
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{
    'name': 'Danish',
    'age': 24,
    'course': {
        'name': 'Data Structures',
        'code': 'CS201'
    }
}

对于可迭代对象,您可以这样做。

import json
import dictfier

class Course(object):
    def __init__(self, code, name):
        self.code = code
        self.name = name
class Student(object):
    def __init__(self, name, age, courses):
        self.name = name
        self.age = age
        self.courses = courses

course1 = Course("CS201", "Data Structures")
course2 = Course("CS205", "Computer Networks")
student = Student("Danish", 24, [course1, course2])
query = [
    "name",
    "age",
    {
        "courses": dictfier.useobj(
            lambda obj: obj.courses,
            [["name", "code"]]  # This is a query
        )
    }
]

std_info = dictfier.dictfy(student, query)
print(std_info)
# Output
{
    'name': 'Danish',
    'age': 24,
    'courses': [
        {'name': 'Data Structures', 'code': 'CS201'},
        {'name': 'Computer Networks', 'code': 'CS205'}
    ]
}

dictifier 是如何工作的?

dictfier通过使用Query将给定的 Object 递归地转换为相应的 dict来工作(因此适用于嵌套对象)。所以这里重要的是知道如何构造正确的查询以从对象中提取正确的数据。

什么是查询?

查询基本上是一个模板,它告诉 dictfier 从对象中提取什么。它被定义为要提取的对象字段的列表或元组。

示例转换。

当使用以下查询查询平面学生对象时

query = [
    "name",
    "age",
]

dictifier会将其转换为

{
    "name": student.name,
    "age": student.age,
}   

对于嵌套查询,它就像

query = [
    "name",
    "age",
    {
        "course": [
            "code",
            "name",
        ]
    }
]

对应的dict

{
    "name": student.name,
    "age": student.age,
    "course": {
        "code": student.course.code,
        "name": student.course.name,
    }
}

对于可迭代对象,它就像

query = [
    "name",
    "age",
    {
        "course": [
            [
                "code",
                "name",
            ]
        ]
    }
]

将列表或元组放入对象字段的列表或元组中是一种声明 Object 是可迭代的方法。在这种情况下

[
    [
        "code",
        "name",
    ]
]

对应的dict

{
    "name": student.name,
    "age": student.age,
    "courses": [
        {
            "code": course.code,
            "name": course.name,
        }
        for course in student.courses
    ]
}

请注意“课程”上的列表或元组与“名称”和“年龄”等其他字段不同,它使“课程”可迭代,这就是在“课程”查询中具有嵌套列表或元组的原因。

这很简单吧?

如果我想自定义 dictfier 的工作方式怎么办?

您可能会遇到一种情况,您必须更改 dictfier 的工作方式才能获得您想要的结果,不用担心我们会支持您。dictfier是高度可配置的,它允许您配置如何将每种类型的对象转换为字典数据结构。dictifier配置分为三个部分,分别是

  • 平面对象配置(将 flat_obj=function kwarg 传递给 dictfy)

  • 嵌套平面对象配置(将 nested_flat_obj=function kwarg 传递给 dictfy)

  • 嵌套可迭代对象配置(将 nested_iter_obj=function kwarg 传递给 dictfy)

在上述所有情况下,分配给 flat_obj、nested_flat_obj 或 nested_iter_obj 的函数接受三个位置参数,即字段值(对象)和父对象和字段名称。现在考虑一个简单的 ORM 示例,它具有两个关系ManyOne,用于显示对象是如何相关的。

# Customize how dictfier obtains flat obj,
# nested flat obj and nested iterable obj
import dictfier

class Many(object):
    def __init__(self, data):
        self.data = data

class One(object):
    def __init__(self, data):
        self.data = data

class Book(object):
    def __init__(self, pk, title, publish_date):
        self.pk = pk
        self.title = title
        self.publish_date = publish_date

class Mentor(object):
    def __init__(self, pk, name, profession):
        self.pk = pk
        self.name = name
        self.profession = profession

class Course(object):
    def __init__(self, pk, code, name, books):
        self.pk = pk
        self.code = code
        self.name = name
        self.books = Many(books)

class Student(object):
    def __init__(self, pk, name, age, mentor, courses):
        self.pk = pk
        self.name = name
        self.age = age
        self.mentor = One(mentor)
        self.courses = Many(courses)

book1 = Book(1, "Advanced Data Structures", "2018")
book2 = Book(2, "Basic Data Structures", "2010")
book3 = Book(1, "Computer Networks", "2011")

course1 = Course(1, "CS201", "Data Structures", [book1, book2])
course2 = Course(2, "CS220", "Computer Networks", [book3])

mentor = Mentor(1, "Van Donald", "Software Eng")
student = Student(1, "Danish", 24, mentor, [course1, course2])
query = [
    "name",
    "age",
    {   "mentor": [
            "name",
            "profession"
        ],
        "courses": [[
            "name",
            "code",
            {
                "books": [[
                    "title",
                    "publish_date"
                ]]
            }
        ]]
    }
]

result = dictfier.dictfy(
    student,
    query,
    flat_obj=lambda obj, parent: obj,
    nested_iter_obj=lambda obj, parent: obj.data,
    nested_flat_obj=lambda obj, parent: obj.data
)
print(result)
# Output
{
    'name': 'Danish',
    'age': 24,
    'mentor': {'name': 'Van Donald', 'profession': 'Software Eng'},
    'courses': [
        {
            'name': 'Data Structures',
            'code': 'CS201',
            'books': [
                {'title': 'Advanced Data Structures', 'publish_date': '2018'},
                {'title': 'Basic Data Structures', 'publish_date': '2010'}
            ]
        },
        {
            'name': 'Computer Networks',
            'code': 'CS220',
            'books': [
                {'title': 'Computer Networks', 'publish_date': '2011'}
            ]
        }
    ]
}

从上面的示例中,如果您想返回嵌套平面或嵌套可迭代对象的主键(pk)(这在 API 设计和序列化模型中很常见),您可以按如下方式进行。

查询 =  [ 
    “姓名” 
    “年龄” 
    “导师” 
    “课程” 
]

def  get_pk ( obj ,  parent