为CT_P自动注册与CT_Run相关的方法

news2024/11/20 15:20:18

概述

在docx.oxml.text.paragraph模块中定义了CT_P段落对象元素类,但是CT_P中并未定义add_r等与CT_Run相关的方法。在不断探索源码逻辑的过程中,对这种自动为类注册合适的方法的功能进行了梳理——xmlchemy这个模块设计的真好!!!

大体逻辑如下:

  1. CT_P中包含类属性“r”, 该类属性存储的是ZeroOrMore实例对象——docx.oxml.xmlchemy模块中定义了ZeroOrMore子元素类对象,以及与之相似的OneAndOnlyOne、OneOrMore、ZeroOrOne、ZeroOrOneChoice等子元素类对象。这些类对象均继承_BaseChildElement,并重新定义了populate_class_members方法。正是该方法为许多BaseOxmlElement类对象自动化添加合适的方法。
  2. CT_P继承BaseOxmlElement, BaseOxmlElement是MetaOxmlElement类型,因此在创建CT_P时,会调用MetaOxmlElement的初始化方法,该初始化方法会检查新建类的属性字典,并判断属性字典中的值是否是ZeroOrMore等子元素类,如果是则调用populate_class_members,为新建的类注册合适的方法。

本文以docx.oxml.text.paragraph.CT_P类创建为例,将重点对MetaOxmlElement元类、_BaseChildElement类的功能进行详细记录。注意:本文档参考的版本信息为python_docx=1.1.0

MetaOxmlElement

MetaOxmlElement元类的源码定义如下:

class MetaOxmlElement(type):
    """Metaclass for BaseOxmlElement."""

    def __init__(cls, clsname: str, bases: Tuple[type, ...], namespace: Dict[str, Any]):
        dispatchable = (
            OneAndOnlyOne,
            OneOrMore,
            OptionalAttribute,
            RequiredAttribute,
            ZeroOrMore,
            ZeroOrOne,
            ZeroOrOneChoice,
        )
        for key, value in namespace.items():
            if isinstance(value, dispatchable):
                value.populate_class_members(cls, key)
  1. 元类是创建类的类,其功能|角色与type类似。
  2. clsname是待创建的类对象名称,base是待创建类对象的父类,namespace是待创建类对象的namespace,可简单理解为待创建类对象的属性字典。
  3. for key, value in namespace.items()迭代过程中,如果待创建类对象的属性值为dispatchable中的某种类型,则调用populate_class_members方法,注意传入的cls是指父节点,key是dispatchable对象对应的名称

BaseOxmlElement

BaseOxmlElement基础类是docx.oxml子包中所有元素类的基础类,其角色与etree.ElementBase类似,源码定义如下:

class BaseOxmlElement(  # pyright: ignore[reportGeneralTypeIssues]
    etree.ElementBase, metaclass=MetaOxmlElement
):
    """Effective base class for all custom element classes.

    Adds standardized behavior to all classes in one place.
    """
  1. BaseOxmlElement继承etree.ElementBase,因此可以直接使用etree.ElementBase中的find、findall等方法。
  2. BaseOxmlElement是MetaOxmlElement类型,如果新创建一个基于BaseOxmlElement的子类,则子类的类型任然是MetaOxmlElement,并且该子类创建时会调用MetaOxmlElement.__init__,但是实例化创建的子类,会调用etree.ElementBase的初始化方法。

_BaseChildElement

_BaseChildElement是所有子元素的基础类对象,ZeroOrMore等类均继承该类。在该类中定义了诸多公用的方法,下面先介绍一部分,后续将结合CT_P创建过程逐步介绍。

class _BaseChildElement:
    """Base class for the child-element classes.

    The child-element sub-classes correspond to varying cardinalities, such as ZeroOrOne
    and ZeroOrMore.
    """

    def __init__(self, nsptagname: str, successors: Tuple[str, ...] = ()):
        super(_BaseChildElement, self).__init__()
        self._nsptagname = nsptagname
        self._successors = successors

    def populate_class_members(
        self, element_cls: MetaOxmlElement, prop_name: str
    ) -> None:
        """Baseline behavior for adding the appropriate methods to `element_cls`."""
        self._element_cls = element_cls
        self._prop_name = prop_name
  1. 初始化方法中,需要传入命名空间前缀的元素标签名,以及该元素的先驱元素——就是在一个父节点下,排在当前节点之前的所有其它子节点。比如段落对象中,一般段落属性对象会排在第一位。
  2. populate_class_members方法中,element_cls传入的实参是该节点的父节点元素,父节点的类型是MetaOxmlElement,prop_name表示属性名。从该方法的注释可以看出,该方法是为父节点对象添加合适的方法

ZeroOrMore

ZeroOrMore是一种子元素类,其表示某一父节点允许拥有任意多个该种子节点对象。在word文档中,这是最常见的一种子节点元素了,比如word文档允许包含任意多个paragraph,单个paragraph允许包含任意多个run节点。ZeroOrMore的源码定义如下:

class ZeroOrMore(_BaseChildElement):
    """Defines an optional repeating child element for MetaOxmlElement."""

    def populate_class_members(
        self, element_cls: MetaOxmlElement, prop_name: str
    ) -> None:
        """Add the appropriate methods to `element_cls`."""
        super(ZeroOrMore, self).populate_class_members(element_cls, prop_name)
        self._add_list_getter()
        self._add_creator()
        self._add_inserter()
        self._add_adder()
        self._add_public_adder()
        delattr(element_cls, prop_name)

继承_BaseChildElement,并实现自定义的populate_class_members——为父节点添加合适的方法。

  1. super(ZeroOrMore, self).populate_class_members调用父类的方法,将父节点、属性名称存储进实例对象。
  2. _add_*等一组方法表示为父节点添加对象的方法,后续详细介绍。
  3. delattr语句删除父节点中的属性名。

CT_P创建过程分解

CT_P表示<w:p>元素,是word文档中的核心元素类。其在oxml中的源码定义如下:

class CT_P(BaseOxmlElement):
    """`<w:p>` element, containing the properties and text for a paragraph."""

    add_r: Callable[[], CT_R]
    get_or_add_pPr: Callable[[], CT_PPr]
    hyperlink_lst: List[CT_Hyperlink]
    r_lst: List[CT_R]

	...
    r = ZeroOrMore("w:r")
    ...
  1. CT_P继承BaseOxmlElement,因此CT_P是MetaOxmlElement类型。在创建CT_P类的过程中,python解释器会遍历CT_P的定义,收集所有类属性——在CT_P定义中打上断点、进行调试。然后执行MetaOxmlElement.__init__(cls, clsname="CT_P", bases=(BaseOxmlElement,), namespace={...r: ZeroOrMore...}注意MetaOxmlElement初始化时传入的cls是CT_P,即待创建的类对象。namespace是一个字典,存储CT_P中定义的所有类属性与方法、以及一些模块信息,这里简化了,因为本文主要关注如何为CT_P自动添加合适的方法。
  2. 在执行MetaOxmlElement初始化方法中,当key="r" and value=ZeroOrMore("w:r") 时,就会调用ZeroOrMore的populate_class_members(CT_P, "r")。下述分项记录一下五条语句:
    def populate_class_members(
        self, element_cls: MetaOxmlElement, prop_name: str
    ) -> None:
        """Add the appropriate methods to `element_cls`."""
		...
        self._add_list_getter()
        self._add_creator()
        self._add_inserter()
        self._add_adder()
        self._add_public_adder()
		...

self._add_list_getter

_add_list_getter方法定义在_BaseChildElement中,其定义如下:

    def _add_list_getter(self):
        """Add a read-only ``{prop_name}_lst`` property to the element class to retrieve
        a list of child elements matching this type."""
        prop_name = "%s_lst" % self._prop_name
        property_ = property(self._list_getter, None, None)
        setattr(self._element_cls, prop_name, property_)

此时,self._prop_name存储的属性名称为“r”,即prop_name等于“r_lst”。第三句中的self._element_cls此时存储的父节点为“CT_P”,即第三句将self._list_getter方法设置为CT_P的可读特性。self._list_getter同样定义在_BaseChildElement中:

    @property
    def _list_getter(self):
        """Return a function object suitable for the "get" side of a list property
        descriptor."""

        def get_child_element_list(obj: BaseOxmlElement):
            return obj.findall(qn(self._nsptagname))

        get_child_element_list.__doc__ = (
            "A list containing each of the ``<%s>`` child elements, in the o"
            "rder they appear." % self._nsptagname
        )
        return get_child_element_list
  1. 由于在CT_P中定义r = ZeroOrMore("w:r"),因此self._nsptagname等于“w:r”,qn函数是将命名空间前缀名称转换为限定性名称,即将“w:r”转换为“{http://schemas.openxmlformats.org/wordprocessingml/2006/main}r”
  2. findall是etree.BaseElement的方法,即查找CT_P节点下的所有CT_R子节点。

self._add_creator

_add_creator方法同样定义在_BaseChildElement内,其功能是为父节点添加一个合适的创建子节点的方法。源码定义如下:

    def _add_creator(self):
        """Add a ``_new_{prop_name}()`` method to the element class that creates a new,
        empty element of the correct type, having no attributes."""
        creator = self._creator
        creator.__doc__ = (
            'Return a "loose", newly created ``<%s>`` element having no attri'
            "butes, text, or children." % self._nsptagname
        )
        self._add_to_class(self._new_method_name, creator)

    @property
    def _creator(self) -> Callable[[BaseOxmlElement], BaseOxmlElement]:
        """Callable that creates an empty element of the right type, with no attrs."""
        from docx.oxml.parser import OxmlElement

        def new_child_element(obj: BaseOxmlElement):
            return OxmlElement(self._nsptagname)

        return new_child_element

    def _add_to_class(self, name: str, method: Callable[..., Any]):
        """Add `method` to the target class as `name`, unless `name` is already defined
        on the class."""
        if hasattr(self._element_cls, name):
            return
        setattr(self._element_cls, name, method)
  1. 在此处,会为CT_P新增一个_new_r方法,即在CT_P下创建一个空的CT_Run节点。
  2. self._creator是_BaseChildElement类的一个特性,该特性返回一个可调用对象,可调用对象的输入为BaseOxmlElement对象,输出是一个空的BaseOxmlElement实例对象。
  3. _add_to_class方法将_creator方法绑定到CT_P的_new_r特性上。

self._add_inserter

    def _add_inserter(self):
        """Add an ``_insert_x()`` method to the element class for this child element."""

        def _insert_child(obj: BaseOxmlElement, child: BaseOxmlElement):
            obj.insert_element_before(child, *self._successors)
            return child

        _insert_child.__doc__ = (
            "Return the passed ``<%s>`` element after inserting it as a chil"
            "d in the correct sequence." % self._nsptagname
        )
        self._add_to_class(self._insert_method_name, _insert_child)
  1. 此处即为CT_P新增一个_insert_r方法。
  2. 此处_insert_r中的obj应该是CT_P实例,child应是CT_Run,即将实参CT_Run插入到CT_P中合适的位置。

self._add_adder

    def _add_adder(self):
        """Add an ``_add_x()`` method to the element class for this child element."""

        def _add_child(obj: BaseOxmlElement, **attrs: Any):
            new_method = getattr(obj, self._new_method_name)
            child = new_method()
            for key, value in attrs.items():
                setattr(child, key, value)
            insert_method = getattr(obj, self._insert_method_name)
            insert_method(child)
            return child

        _add_child.__doc__ = (
            "Add a new ``<%s>`` child element unconditionally, inserted in t"
            "he correct sequence." % self._nsptagname
        )
        self._add_to_class(self._add_method_name, _add_child)
  1. _add_adder为CT_P新增一个_add_r方法
  2. 该方法会综合利用之前新增的_new_r与_insert_r方法。在_add_child执行逻辑中,new_method新建一个CT_Run实例,然后为新建的CT_Run设置属性值,最后调用_insert_r将新创建CT_Run插入到CT_P中的合适位置并返回。

self._add_public_adder

    def _add_public_adder(self):
        """Add a public ``add_x()`` method to the parent element class."""

        def add_child(obj: BaseOxmlElement):
            private_add_method = getattr(obj, self._add_method_name)
            child = private_add_method()
            return child

        add_child.__doc__ = (
            "Add a new ``<%s>`` child element unconditionally, inserted in t"
            "he correct sequence." % self._nsptagname
        )
        self._add_to_class(self._public_add_method_name, add_child)
  1. 为CT_P新增一个add_r方法
  2. add_r方法的本质就是获取_add_r、并执行,得到一个新创建的CT_Run节点。
  3. 下图中显示CT_P中已经包含_new_r,_insert_r,_add_r, add_r四个自动新增的方法:
    为CT_P自动注册与CT_Run相关的方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1389350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rust跟我学(一):模块编写与使用

在rust中,单元文件可以被称为模块。 以下内容,将以get_local_info为例,讲解mod在工程中的使用。 先看下图,network.rs是src文件夹里的一个文件,我们可以单独把这个文件理解成一个模块。Rust比较智能,它可以将文件名称直接理解成一个模块,这在go里面是做不到的。 Rust其实…

​​社交媒体与新闻:Facebook在信息传播中的作用

社交媒体已经成为我们获取和传播新闻的主要渠道之一&#xff0c;而Facebook作为社交媒体的巨头&#xff0c;在信息传播中扮演着举足轻重的角色。本文将深入探讨社交媒体对新闻传播的影响&#xff0c;聚焦于Facebook在这一领域的独特作用&#xff0c;以及这种作用对我们的新闻体…

2024北京智博会:共赴科技盛会,助力跨界融合与实体经济深度发展

2024年6月&#xff0c;一场备受瞩目的科技盛会将在北京亦创国际会展中心拉开帷幕。作为国内外科技交流的重要平台&#xff0c;2024北京智博会将汇集众多科技领域的精英和企业&#xff0c;共同探讨跨界应用、实体经济深度融合等议题&#xff0c;为推动科技强国发展注入新动力。 …

SSL之mkcert构建本地自签名

文章目录 1. 什么是SSL2. mkcert&#xff1a;快速生成自签名证书2.1 mkcert的工作流程如下&#xff1a;2.2 window 本地实现自签证书2.2.1 下载安装2.2.2 下载,生成本地 SSL2.2.3 生成 pem 自签证书,可供局域网内使用其他主机访问。2.2.4 使用-psck12 生成*.p12 文件 2.3 Sprin…

7.11、Kali Linux中文版虚拟机安装运行教程

目录 一、资源下载准备工作 二、安装教程 三、kali linux换源 四、apt-get update 报错 一、资源下载准备工作 linux 中文版镜像历史版本下载:http://old.kali.org/kali-images/ 大家可以自行选择版本下载&#xff0c;本人下载的是2021版本 二、安装教程 打开vmvare wokst…

修炼九阳神功——“函数”

目录 前言 1. 函数的概念 2. 库函数 2.1 标准库和头⽂件 2.2 库函数的使用方法 2.2.1 功能 2.2.2 头⽂件包含 2.2.3 实践 2.2.4 库函数⽂档的⼀般格式 3. 自定义函数 3.1 函数的语法形式 3.2 函数的举例 ​编辑 4. 形参和实参 4.1 实参 4.2 形参 4.3 实参和形…

【dc-dc】世微AP5127平均电流型LED降压恒流驱动器 双色切换的LED灯驱动方案

这是一款双色切换的LED灯方案&#xff0c;12-50V 降压恒流,输出&#xff1a;6V 2.5A ​ 这是一款PWM工作模式 , 高效率、 外围简单、内置功率管&#xff0c;适用于 输入的 高 精度降压 LED 恒流驱动芯片。输出大功率可 达 25W&#xff0c;电流 2.5A。 可实现全亮/半亮功能切换…

Ansible Filter滤波器的使用(二)

一、【说在前面】 Ansible Filter一般被称为滤波器或者叫过滤器。 这个东西初次听到以为是什么科学计算的东西&#xff0c;但是想来ansible不太可能有什么滤波操作&#xff0c;所以这个东西本质是一个数值筛选器&#xff0c;内置函数&#xff0c;本质是一个为了做区别化的工具…

“15个必备的自动化测试工具,助你构建2024年的自动化策略!“

以下为作者观点&#xff1a; 如何选择正确的自动化测试工具&#xff1f;自动化测试工具是旨在通过自动化测试脚本验证功能或非功能软件需求的应用程序&#xff0c;帮助加快发布速度、提高项目质量并强化成果。 自动化测试工具可以帮助开发测试人员轻松创建、运行和维护测试&a…

闲鱼宝库亮相!闲鱼商品详情关键词搜索电商API接口助你畅享无尽好货!

随着互联网的快速发展&#xff0c;电商平台的崛起已经改变了人们的购物习惯。而在众多电商平台中&#xff0c;闲鱼作为一款社区二手交易平台&#xff0c;一直备受用户喜爱。如今&#xff0c;闲鱼宝库正式亮相&#xff0c;为用户带来了更加全面、详细的商品详情关键词搜索电商AP…

两整数之和

题目链接 两整数之和 题目描述 注意点 不使用 运算符 和 - ​​​​​​​&#xff0c;计算并返回两整数之和-1000 < a, b < 1000 解答思路 需要用位运算来模拟加法&#xff0c;关键是要找到相加的和以及进位1的部分。如果不考虑进位的话&#xff0c;相加可以运用异…

Spring - 如何控制多个 AOP 切面执行顺序?

众所周知&#xff0c;Spring 声明式事务是基于 AOP 实现的&#xff0c;那么&#xff0c;如果我们在同一个方法自定义多个 AOP&#xff0c;我们如何指定他们的执行顺序呢&#xff1f; 三种解决方案 1、通过实现 org.springframework.core.Ordered 接口 Component Aspect S…

​LeetCode解法汇总83. 删除排序链表中的重复元素

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给定一个已排序的链表的头 head &#xf…

【论文笔记合集】卷积神经网络之深度可分离卷积(Depthwise Separable Convolution)

本文作者&#xff1a; slience_me 我看的论文地址&#xff1a;MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications 内容 1. 标准卷积 假设输入为DFDFM&#xff0c;输出为输入为DFDFN&#xff0c;卷积核为DKDKM&#xff0c;共有N个卷积核进…

【AIGC入门一】Transformers 模型结构详解及代码解析

Transformers 开启了NLP一个新时代&#xff0c;注意力模块目前各类大模型的重要结构。作为刚入门LLM的新手&#xff0c;怎么能不感受一下这个“变形金刚的魅力”呢&#xff1f; 目录 Transformers ——Attention is all You Need 背景介绍 模型结构 位置编码 代码实现&…

51单片机学习总结(自学)

1、模块化编程 c语言模块化编程实现思路设计代码 具体的程序实现代码如下所示 1&#xff1a;程序的头文件 2&#xff1a;程序的函数文件 3&#xff1a;程序的主文件控制函数的实现 持续更新中......

算法部署过程中如何确保数据的安全?

在数字化时代&#xff0c;数据安全成为了企业和个人面临的一项主要挑战。随着技术的迅速发展&#xff0c;尤其在算法部署过程中&#xff0c;确保敏感数据的安全性变得更加复杂和关键。在这个背景下&#xff0c;软件加密和授权机制的作用显得尤为重要。软件加密不仅仅是转换数据…

高纯气体市场调研:预计2029年将达到331亿美元

高纯气体应用领域极宽&#xff0c;在半导体工业&#xff0c;高纯氮、氢、氩、氦可作为运载气和保护气;高纯气体可作为配制混合气的底气。随着LED和半导体的发展&#xff0c;对于其原物料生产的所需要的高纯气体&#xff0c;特别是7N级别的高纯氨气的需求不断增加&#xff0c;近…

CMake TcpServer项目 生成静态库.a / 动态库.so

CMake 实战构建TcpServer项目 静态库/动态库-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135608829?spm1001.2014.3001.5501 在这篇博客的基础上&#xff0c;我们把头文件放在include里边&#xff0c;把源文件放在src里边&#xff0c;重新构建 hehedali…

深入理解 PyTorch 激活函数:从基础到高效实用技巧(4)

目录 torch.nn.functional激活层详解 tanh 1. 函数用途 2. 参数详解 3. Tanh函数的定义及数学解释 4. 使用示例 sigmoid 1. 函数用途 2. 参数详解 3. Sigmoid函数的定义及数学解释 4. 使用示例 hardsigmoid 1. 函数用途 2. 参数详解 3. Hardsigmoid函数的定义及…