news 2026/10/16 1:03:56

Odoo二开必修:模块继承之模型与视图继承完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Odoo二开必修:模块继承之模型与视图继承完整实战

做Odoo二开的都知道,最怕的不是业务逻辑复杂,而是你接手的时候,系统已经跑了大半年,客户天天在用。这时候你说"这个字段我得改核心源码才能加",没人敢让你动。我早期带团队时,有个同事就是直接改了某标准模块的源文件来加一个客户分类字段,结果客户升级系统后,那个文件被覆盖,字段全没了,数据倒是还在表里,但界面一片混乱。那次之后,我定了个规矩:凡是二开需求,一律通过模块继承来做,不碰任何标准模块的源代码。

Odoo模块继承是官方为二次开发准备的核心机制,它允许你在不改动原模块代码的前提下,给现有模型加字段、改方法、调整视图、扩展菜单和报表。这篇是"模块继承第一讲",我默认你已经能写一个最简单的Odoo模块(有manifest、有模型、有视图、能安装),下面我重点拆解三种继承方式里最常用、最值得先吃透的两种:模型继承(_inherit)和视图继承(inherit_id)。这两种搞定,市面上八九成的二次开发需求你都能接得住。

1. 从"改源码"到"写模块",Odoo继承机制到底解决了什么问题

先说一个反常识的事情:Odoo的源码几乎完全可以当作文档来看,但它绝不适合直接修改。原因很简单,模块化架构决定了标准模块之间互相依赖,你今天改了某个模型里的字段逻辑,可能没影响当前功能,但下次官方升级或安装新模块时,这个改动就变成了冲突源。

1.1 继承机制的本质:在不破坏原模型的前提下叠加能力

Odoo的模型层基于ORM实现,它的继承不是传统编程里"子类扩展父类"那么简单,更像是给同一个模型动态叠加补丁。当你写下:

class ResPartner(models.Model): _inherit = 'res.partner'

这段代码的意思是:"请在原有合作伙伴模型上再加点东西",而不是"请创建一个新模型"。原模型的所有字段、方法、约束都还在,你的代码只是往里面追加内容。这种设计的好处极其明显:逻辑解耦。你的模块负责你的功能,标准模块负责标准功能,两者在代码层面互不侵入,但在运行时无缝融合。

1.2 改源码与写模块的真实代价对比

我见过太多刚入门的开发者纠结这个问题。直接改源码确实爽,加字段到标准视图里,保存,刷新,完事。但代价你迟早要还:

对比维度直接改源码写模块继承
开发速度快,改完即用稍慢,需要搭模块骨架
升级兼容性升级必冲突,手动合并几乎无感,继承自动合并
多环境同步容易漏改文件模块即部署单元
团队协作代码冲突频繁各自模块互不干扰
卸载回滚几乎不可能卸载模块即恢复原状

我后来带项目有个习惯,接任何需求先问一句:"这个功能能不能用继承实现?"如果答案是肯定的,就走模块化路线。有一次客户要求把采购订单的编号前缀改成自定义格式,我们做了一个只有几十行代码的继承模块,后来客户升级系统,前后不到十分钟就完成了,那边的IT负责人自己都惊讶。

1.3 第一讲你该掌握到什么程度

继承机制本身有三个层次:模型字段扩展、业务方法重写、视图结构修改。第一讲不贪多,主攻前两个加上第三个的基础用法。也就是说,学完这篇,你应该能做到:给现有模型加字段、加默认值、加选择项,把现有方法的逻辑篡改一部分且不影响原始流程,以及在表单视图里插入、调整字段位置。

如果你能顺手把这三个能力组合起来用,比如"加一个字段,这个字段影响某个按钮的行为,同时把这个字段显示在指定位置",那你对Odoo的理解就超过了大多数只写业务模块的开发新手。

2. 三种继承方式:模型继承、视图继承、模块依赖继承,第一讲先吃透哪几种

很多人一查资料,看到"继承有三四种写法",直接被唬住了。实际上,日常开发里用得最多的是两类,剩下的是特定场景才需要。我在这一节帮你把概念彻底理清。

2.1 模型继承:两种写法要分清

模型继承有两种典型写法,效果完全不同,这是最容易被混淆的点。

第一种是扩展式继承,也是最常用的:

class SaleOrder(models.Model): _inherit = 'sale.order'

这种写法不创建新模型,只对'sale.order'现有模型打补丁。你在里面定义的字段会直接加到'sale.order'模型上,定义的方法会直接替换或扩展同名方法。这是二开最主力的方式。

第二种是原型继承,也叫复制继承:

class CustomOrder(models.Model): _inherit = 'sale.order' _name = 'custom.order'

注意,这里同时定义了_inherit和_name。含义是:以sale.order为模板复制出一个新模型custom.order。新模型拥有原模型的所有字段,但本质上和原模型没有ORM层面的耦合。这种写法主要用于你不想污染原模型、但要复用一套完整字段设计的情况,比如做特殊单据类型时。

第一讲你只需要吃透第一种,第二种等做到复杂业务再回头补也不晚。

2.2 视图继承:界面改动的正确打开方式

视图继承的核心思路和模型继承如出一辙:通过inherit_id指定要继承的原始视图,然后在arch结构里用XPath定位到某个具体位置,插入或修改内容。

我先给你一个感性的认识:

<record id="view_partner_form_inherit" model="ir.ui.view"> <field name="name">res.partner.form.inherit.customer_source</field> <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> <xpath expr="//field[@name='city']" position="after"> <field name="customer_source"/> </xpath> </field> </record>

这段代码的意思是:"把合作伙伴表单视图找出来,在city字段后面插入一个叫customer_source的字段。"inherit_id指向你要改的原始视图,xpath负责告诉你改在哪里。

2.3 三种继承方式一张表看懂分工

继承方式核心关键字作用对象第一讲掌握等级
模型扩展继承_inherit模型字段与方法必须掌握
模型原型继承_inherit+_name复制模型生成新模型了解即可
视图继承inherit_id视图界面结构与行为必须掌握

顺带解释一个常见困惑:为什么讲继承一定要扯到depends?因为你要继承res.partner,你的模块必须在manifest的depends里声明依赖基础模块base;你要继承销售订单,就要声明依赖sale。这不是走形式,Odoo在升级模块时会根据依赖关系决定加载顺序。依赖声明错误,继承的模型或视图根本没被加载,报错的时候你会莫名其妙。

3. 模型继承实操:_inherit、字段扩展与方法重写的完整代码拆解

这一节是硬核部分,我以一个我从实际项目里提炼出来的需求为例带着你完整过一遍。需求背景是这样:客户做销售业务,想知道每个客户是通过什么渠道来的,比如官网、老客户转介绍、广告。他们希望这个字段直接挂在合作伙伴表单上,并且当销售订单确认时,如果是转介绍来的客户,自动在订单备注里写一句话提醒跟单员回访。

这个需求典型地覆盖了模型继承的三个动作:加字段、上加字段相关的界面、重写方法。我们一部分一部分来。

3.1 搭建模块骨架,别把manifest写错

先建一个模块目录,我习惯命名为partner_customer_source:

partner_customer_source/ ├── __init__.py ├── __manifest__.py ├── models/ │ ├── __init__.py │ └── res_partner.py └── views/ └── res_partner_views.xml

__manifest__.py是模块的门面,第一讲你重点关注depends和data这两项:

{ 'name': '客户来源管理扩展', 'version': '16.0.1.0.0', 'category': 'Sales', 'depends': ['base', 'sale'], 'data': [ 'views/res_partner_views.xml', ], 'installable': True, 'application': False, }

这里有两个关键点。第一,depends为什么既要base又要sale?因为我们要给res.partner加字段,它属于基础模型,声明base是底线;同时我们要重写sale.order的方法,必须声明依赖sale。第二,data里只放视图文件,没有模型文件,因为模型文件是通过Python的import机制加载的,不需要在data中声明。

3.2 用_inherit给既有模型加字段

models/res_partner.py里面这样写:

from odoo import models, fields class ResPartner(models.Model): _inherit = 'res.partner' customer_source = fields.Selection( [('website', '官网询盘'), ('referral', '老客户转介绍'), ('ad', '广告投放'), ('other', '其他')], string='客户来源', default='other', help='记录该客户第一次接触公司的渠道' )

这段代码要这么理解:_inherit = 'res.partner'告诉Odoo我要扩展这个现有模型,customer_source字段此时就真的成为了合作伙伴模型的一个字段。它跟你在标准模块里定义字段没有本质区别,可以正常用在搜索、分组、列表中。

我来解释一下这个字段设计里的实用细节。用Selection是因为渠道这种东西枚举值通常可控;default='other'是为了防止历史数据为空导致列表视图排序或过滤异常;help写清楚业务含义,鼠标悬停会显示,对后续维护的人非常友好。

然后,因为我的业务场景要求销售订单确认时读取这个字段,所以还需要在销售订单模型里同样通过继承追加一条关联逻辑。新建models/sale_order.py:

from odoo import models, fields class SaleOrder(models.Model): _inherit = 'sale.order' note = fields.Text(string='备注')

等等,sale.order本身已经有note字段了。如果你在扩展继承里重新定义一个同名普通字段,会造成字段定义冲突。正确的做法是:如果只是想要一个备注字段,直接用原生的note即可。我在这里特意提这个,是想提醒你一个第一讲必须建立的意识:扩展字段之前,先确认原模型里有没有同名字段,别重复定义。你可以从界面开发者菜单里查看模型字段列表,或者直接查ir.model.fields表。

既然原模型已有note,那models/sale_order.py就不需要写任何字段了,我们只需要在后续重写方法时直接使用order.note。

3.3 方法重写:super()的写法和返回值处理

在前面的基础上,现在我要重写action_confirm()这个方法。先看代码:

from odoo import models, fields, api class SaleOrder(models.Model): _inherit = 'sale.order' def action_confirm(self): res = super().action_confirm() for order in self: if order.partner_id.customer_source == 'referral': order.note = (order.note or '') + '本单来自老客户转介绍,请在交付后安排回访。' return res

关键点全在super()这一行。它的作用是:先把控制权交还给被我们覆盖的原始方法,让标准模块的确认逻辑完整执行一遍,然后我们拿到返回结果,再做扩展处理。

这里有一个新手特别容易犯的错:在super()调用之后立刻return,导致后面的扩展代码永远不会执行。比如:

# 错误写法 def action_confirm(self): res = super().action_confirm() return res for order in self: # 这行永远执行不到 ...

还有一种错误是忘记保存返回值。action_confirm()在不同的Odoo版本里返回值不一样,有时是bool,有时是action字典。如果你在方法里做了很多操作却漏了return res,前端点击确认按钮时行为会变得异常,有时代理单能确认但页面不跳转,有时代理单根本没建出来。

之所以在super()之后做扩展,是因为订单确认动作本身可能修改了state、生成了仓库单据、创建了日志。如果我先遍历再调super(),我基于的状态就是旧状态,扩展逻辑可能写错上下文。先执行原始逻辑,再在结果基础上调整,是方法扩展最安全的姿势。

另外要注意super()在Odoo里的使用风格。新版Python推荐直接写super().action_confirm(),不需要传参,简洁且安全。老代码里你会看到super(SaleOrder, self).action_confirm(),那是Python 2时代遗留写法,现在没必要模仿。

3.4 字段顺序、默认值与相关字段的补充说明

围绕模型继承还有几个冷门但实用的细节值得第一讲就掌握。

字段在表单里的显示顺序,通常由视图里的位置决定,跟模型里定义顺序无关。但如果你用fields_view_get动态加字段,或者在列表视图里没显式写出字段顺序,默认顺序就按ORM模型字段的创建顺序来。继承字段的创建顺序晚于原生字段,所以在列表视图如果不指定,继承字段会排在很后面。

再说related字段。如果你要继承的源字段在关联模型上,可以用related直接拉过来:

source = fields.Selection(related='partner_id.customer_source', string='客户来源', readonly=True)

这样在订单行甚至订单层面都能直接看到客户来源,不需要手动同步值。related字段是Odoo的高级特性,但在第一讲里我建议你只读使用,不要想着通过它自动回写,等完全理解了字段存储机制再玩双向联动。

关于默认值的补充:Selection字段设置default='other'只是界面层的预选值,不是数据库层面的约束。如果某条记录没有设置这个字段,查询结果可能是False。所以我在扩展方法里判断时用的是== 'referral',而不是if order.partner_id.customer_source:,因为'other'和False都有可能。这种细节在大批量数据处理时非常关键,经常有人的判断逻辑莫名失效,查到最后是默认值没有真正落到数据上。

4. 视图继承实操:XPath定位、arch结构修改与属性调整

模型层的字段加好了,方法也重写了,现在必须让用户界面把这些新东西显示出来。视图继承是这中间最难、也最体现经验的部分。

4.1 在XML中声明视图继承的完整姿势

还是用前面那个views/res_partner_views.xml:

<?xml version="1.0" encoding="UTF-8"?> <odoo> <record id="view_partner_form_inherit" model="ir.ui.view"> <field name="name">res.partner.form.inherit.customer_source</field> <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> <xpath expr="//field[@name='city']" position="after"> <field name="customer_source" required="False"/> </xpath> </field> </record> </odoo>

几点说明:

name字段理论上全局可以不唯一,但在项目里养成用"模型+类型+业务含义"的命名习惯,比如res.partner.form.inherit.customer_source,后续排查视图问题时光看名字就知道它继承自谁。model声明这个视图作用于哪个模型。最关键的是inherit_id里的ref,它指向你要修改的目标视图的外部ID。

base.view_partner_form是Odoo基础模块里合作伙伴表单视图的外部ID。问题来了:我怎么知道每个标准视图的外部ID?最简单的办法是利用开发者模式。在开发模式下,打开视图界面,下拉框选择对应视图,就能看到它的External ID。如果你没有开发模式权限,也可以去源码里翻base/views/res_partner_views.xml,不过那确实费劲。

4.2 XPath定位的几种写法,按场景选择

xpath的expr属性是整个视图继承的核心,它决定了你要操作的位置。最常见的写法是基于字段名定位:

<xpath expr="//field[@name='city']" position="after">

这种写法的完整含义是:找到文档树中field标签且name属性等于city的第一个节点,在它后面插入内容。

除了字段,还可以用其他属性定位。比如找按钮:

<xpath expr="//button[@string='Confirm Sale']" position="attributes">

或者用更宽泛的属性组合。expr本质是XPath语法,理论上任何标准的XPath表达式都可用。但我在实践中总结出一条铁律:能通过field[@name='xxx']定位就不要绕弯子。理由有两个,第一是标准模块里字段名相对稳定,第二是name属性在视图arch中语义清晰,不容易误伤。

如果你要操作的元素在重复区域里,比如订单行明细里面某个字段:

<xpath expr="//field[@name='order_line']/tree/field[@name='product_id']" position="after">

这里的关键是层级关系必须写对。order_line是一个One2many字段,它内部有子tree,你要定位的是这个树视图内部的字段。很多新手写成全局的//field[@name='product_id'],如果表单里恰好还有其他地方有同名product_id,就会定位出多个节点,直接报错。

4.3 常用操作:加字段、改显示、加按钮、替换文本

视图继承在position属性上有五种操作,第一讲你必须掌握两种,其他的了解即可。

**after和before**是最常用的插入类操作,用法前面已经示范过。在某个字段前或后插入新字段,这是加字段的标准姿势。

**attributes**用于修改已有节点的属性。比如你想把某个字段的必填属性动态改掉:

<xpath expr="//field[@name='customer_source']" position="attributes"> <attribute name="required">True</attribute> </xpath>

再比如你想给按钮添加确认弹窗:

<xpath expr="//button[@name='action_confirm']" position="attributes"> <attribute name="confirm">确定要确认这个订单吗?</attribute> </xpath>

**replace**用于整体替换一个节点。比如你要替换某个提示文本:

<xpath expr="//div[@name='some_info']" position="replace"> <div>新的提示内容</div> </xpath>

replace能不留痕迹地改界面,但风险也高——如果目标节点里包含其他模块继承添加的内容,替换会把这些内容一起干掉。我建议能用attributes解决的绝不replace,这是惨痛教训换来的。

关于视图继承,还有一个新版版本细节。Odoo 13之后,继承视图默认就是mode="extension",你不需要在record里显式写mode,系统会自动把它当作扩展视图处理。旧资料里教人写mode="primary"或mode="extension",那是Odoo 11时代的老黄历,现在别照搬。

4.4 视图继承后的优先级与字段归属问题

当你写了多个视图继承同一个原始视图时,会出现叠加效应。Odoo会根据sequence属性和模块加载顺序决定这些扩展视图的应用顺序。默认sequence是16,如果你希望自己的修改在最后生效,就把sequence设大一些;要覆盖别人的修改,就设小一些。

再看字段归属。在表单视图中显示一个字段,不代表这个字段属于你当前开发的模型。比如你在合作伙伴表单上插入customer_source,这个字段本身定义在合作伙伴模型上,任何模块都能在视图里引用它。视图继承只是界面层的事,不产生字段所有权。

有一种需求是:字段要显示,但只能由特定用户组修改。我经常在继承视图里配合groups属性使用:

<field name="customer_source" groups="sales_team.group_sale_salesman"/>

这样普通用户看得到字段但不可见可编辑,销售团队成员才能看到完整字段。这种受限可见性完全是视图层控制,比在模型层做权限判断要直观得多。

5. 模块继承开发中必须避开的几个坑(第一讲版)

教程讲思路是线性的,但实际开发会遇到各种意外。这一节我专门整理第一讲阶段最常踩的坑,每个都是我或我身边的同事真实碰到过的。

5.1 视图继承报错"Element cannot be located"的完整排查链路

这是视图继承里最高频的报错,几乎每个写继承的人都会遇到。完整的报错信息长这样:

odoo.tools.convert.ParseError: while parsing res.partner.form.inherit.customer_source Element cannot be located in parent view: field[attribute='name'] with value 'city'

别慌,这个信息很明确:你的XPath表达式在target视图里没找到对应的元素。排查按以下顺序来:

第一步,确认inherit_id里ref指向的视图编号是否正确。打开开发者模式的"视图"菜单,搜索目标模型,看它的表单视图外部ID是什么。如果你继承的是base.view_partner_form,但实际视图的external ID根本不是这个,必然报错。

第二步,确认目标字段真的存在于你继承的那个视图文件里。注意,同一个字段可能在模型里存在,但不在这个特定视图的架构里。比如city这个字段可能只存在于base.view_partner_form里,而res.partner的树视图里根本没有。这时候你如果用一个树视图作为inherit_id,再去定位city字段,自然定位不到。

第三步,检查你的XPath表达式是否写得太泛或太具体。//field[@name='city']这种写法在表单视图里通常没问题,但如果字段在可编辑列表视图的多行区域内,可能需要更精确的定位路径。

最后,检查你的模块在升级时有没有报其他错误。视图继承是相互依赖的——如果多个模块同时修改同一个视图,其中一个定位失败,后面的都会失败。排查时把所有自定义模块的日志打开,找到最早报错的那个。

5.2 视图外部ID冲突与继承顺序问题

你能继承标准视图,别人也能。这是Odoo多模块协作的正常形态,但也带来一个隐患:多个模块往同一个位置插入内容,顺序不可控。

举个例子,A模块在city字段后加一个字段,B模块也在city字段后加一个字段。最终哪个在前,取决于两个模块的安装顺序和加载顺序。如果你是B模块的维护者,你没法保证用户在装B模块之前一定装了A模块。

解决思路有两个。第一,不要刻意依赖其他自定义模块的加载顺序;第二,如果你的字段必须显示在某个确定的相对位置,优先选择一个标准视图里稳定存在的字段作为锚点,而不是依赖另一个自定义模块插入的字段。

id冲突也是新手碰得多的。视图的id在模块内部要唯一,但更重要的是跨模块引用时的可预期性。我给你一个建议:所有视图继承的id都加上模块名前缀,比如我们模块里的view_partner_form_inherit就够清晰,但如果你公司有多个项目组同时开发,最好写成partner_customer_source.view_partner_form_inherit这种带模块名的形式。避免未来合并代码时出现external ID撞车。

5.3 升级模块但看不到效果时的检查顺序

这个问题几乎每周都有人在社区里问:"为什么我加了字段,更新模块后界面没变化?"

我的检查顺序是:

第一步,确认是否真的在Odoo里执行了升级。在应用菜单里找到你的模块,点击"升级",而不是只点击"应用"。修改Python代码,仅重新启动服务是不够的;修改XML视图,必须升级模块才会重新解析。如果你只重启了服务,旧的视图定义还留在数据库里。

第二步,看是否有报错。升级模块后立即查看日志。视图继承相关的错误通常会在升级时被捕获。如果你忽略了报错,界面自然不会有变化。

第三步,确认你在正确的视图下查看。我见过有人改了销售订单表单视图,却去合作伙伴列表界面找新字段,找了半天没找到。字段在哪个模型上,就要去哪个模型的视图里看。

第四步,考虑浏览器缓存。Odoo前端的assets会做缓存,特别是视图相关的静态资源。多按几下Ctrl+Shift+R强刷,再进界面看。如果这步之前一切正常,很可能是缓存导致的——这种情况我碰到的次数可比想象中多得多。

5.4 方法重写时super()用错导致的连锁问题

前面讲了super()的顺序问题,但还有一个更隐蔽的坑:别把super()的结果随意修改后返回。

某些扩展方法,比如创建或写入记录(create()、write()),返回值的类型和语义有严格约定。你在扩展时往往需要对记录集做额外操作,但绝对要遵循原方法的返回约定。我曾经看到一个同事这样写:

def write(self, vals): if 'customer_source' in vals: vals['customer_source'] = 'other' return super().write(vals)

表面上逻辑没问题,但他在某些条件下直接return True,跳过了super().write(),导致字段值根本没写入数据库。这个Bug因为每个条件分支都走了不同的返回路径,排查了整整一下午。

更安全的做法是:在任何情况下,都让super()完整执行一次,再在返回值基础上做微调。如果确实需要提前结束,一定要清醒地知道你在放弃什么。

另外,不要在create()或write()里用self.field = value这样的ORM自动更新语法,这个在重写方法中很容易触发二次写入甚至递归。需要改值就去改vals字典。这个细节值得贴在你的IDE注释里。

5.5 多模块协作时的继承冲突预防

当一个系统里跑着好几个二开模块时,视图继承的冲突概率直线上升。我处理过的最典型一个场景是:两个模块同时继承销售订单的表单视图,各自在不同位置加了字段,结果安装顺序一变,有个字段跑到完全不合理的区域里。

预防有四个原则,建议你在团队里推广:

一个模块只继承一个视图的一个位置,不要贪多。聚合需求到同一个模块里处理,比分散在N个模块里更容易维护。记录锚点字段的选择,尽量选那些标准视图里长期稳定存在的字段,而不是其他模块的私有字段。最后,定期做全局视图审查,开发模式下打开每个视图,检查arch结构是否符合预期。

这几个原则执行到位,所谓继承冲突基本就能扼杀在摇篮里。

最后再分享一点我个人体会。第一讲的内容学完,你能做的已经不少了,但别急着把所有继承技术都塞进脑子里。我在带新人时发现,把模型扩展继承和视图继承吃透的人,后续学习报表继承、菜单继承、数据继承时都非常快。Odoo官方文档很长,很多人翻两页就把自己劝退了,说实话没必要——上手就用,遇到问题再查,是这门技术最有效的打开方式。下一篇我会讲计算字段和约束算法在继承场景里的应用,那种"字段值自动从关联模型算出来"的需求,很多电商和分销项目都会碰到,到时候我们继续用第一讲搭好的模块往里加内容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 1:02:05

基于MP2642GR-Z与PIC18F45K40的2S磷酸铁锂主动均衡方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:01:28

用Vue实现聊天机器人:消息状态管理与交互细节全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:01:00

临床小讲课PPT高效制作:用WorkBuddy快速生成主动脉夹层专业课件

1. 被主任点名的那一刻&#xff0c;我脑子里只有十五分钟周三下午四点十七分&#xff0c;科室群里弹出一条消息&#xff1a;“明天早交班后安排一个小讲课&#xff0c;讲一下主动脉夹层&#xff0c;谁方便准备一下&#xff1f;”紧接着主任补了一句&#xff1a;“小张你来吧&am…

作者头像 李华
网站建设 2026/10/16 0:57:58

Python零依赖手写贪吃蛇:150行代码吃透游戏循环与碰撞检测

1. 为什么我劝你用150行代码手写一遍贪吃蛇很多人学Python学到某个阶段会突然卡住&#xff1a;语法都懂&#xff0c;循环、函数、类也能写&#xff0c;但就是不知道拿这些东西能做出什么。看教程的时候觉得“哦&#xff0c;原来是这样”&#xff0c;关掉教程自己动手&#xff0…

作者头像 李华
网站建设 2026/10/16 0:55:21

Z-EVES形式化验证入门:从Z规格到定理证明的四层工作流

简介&#xff1a;本资源为形式化方法领域核心工具Z-EVES的完整安装与入门套件&#xff0c;面向软件工程、安全关键系统开发人员及高校形式化验证课程学习者&#xff0c;解决Z语言规格编写、自动推理、模型检查与交互式验证等实践难题。压缩包共5个文件&#xff0c;含2个Windows…

作者头像 李华
网站建设 2026/10/16 0:54:17

煤炭数据采集精进指南:从爬虫脚本到工程化系统

最近帮某大宗商品研究团队重构了煤炭数据采集流程&#xff0c;爬取金投网上的煤炭价格、库存、运价指数等内容。这个项目的难点不在“能不能爬到”&#xff0c;而在“怎么长期稳定地爬到”&#xff0c;以及“爬下来的数据能不能直接用”。如果你只是临时抓一次数据做分析&#…

作者头像 李华