尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从 Textual 中“偷“代码:四个可独立复用的纯 Python 工具模块实战指南

从 Textual 中“偷“代码:四个可独立复用的纯 Python 工具模块实战指南 从 Textual 中偷代码四个可独立复用的纯 Python 工具模块实战指南【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual开源代码的魅力不仅在于被使用更在于被偷——被另一个项目复制、修改并继续演化。Textual 作者 Will McGugan 在 2022 年的一篇 DevLog 中公开邀请开发者从他的项目里拿走代码只要尊重许可证必要时给出署名复制开源代码并不会让原作者失去任何东西反而让代码在传播中获得更大价值。本文以这篇 DevLog 为线索结合当前仓库源码深入讲解 Textual 中四个可以原样搬走或略作修改即可提取的通用模块——迭代工具_loop.py、LRU 缓存cache.py、颜色处理color.py与终端几何geometry.py并给出它们的内部实现、真实调用场景与测试佐证让你能直接在自己的 Python 项目中复用。需要先声明的是这里说的偷是开源许可证明确允许的复用而不是盗版。开源代码本身就给了你使用它的显式许可。原 DevLog 同样对此强调过。为什么 Textual 适合被薅代码Textual 是一个用纯 Python 编写的终端界面框架其中既有面向终端渲染的顶层 API也有大量与终端 UI 无关、可以独立成库的基础设施。从 src/textual 的模块结构可以看到像_loop.py、cache.py、color.py、geometry.py这类模块几乎不依赖 Textual 的 DOM 与渲染体系引用关系非常干净非常适合整体提取。从源码视角看这些模块还具备两个额外优势久经考验geometry 模块被 Textual 的布局引擎与合成器compositor大量使用属于项目中最古老、测试覆盖最充分的部分有测试背书仓库中 tests/test_loop.py、tests/test_cache.py 等测试文件直接验证了这些模块的行为搬走代码时可以把测试一起搬走。Loop 工具函数摆脱first/last判断的样板代码遍历一个可迭代对象时几乎每个人都会遇到需要知道当前元素是不是第一个/最后一个的场景。通常的写法是维护一个计数器或者用enumerate比较长度既不优雅又容易出错。Textual 在 src/textual/_loop.py 中提供了四个惰性生成器函数把这种判断封装成了极简 API。四个生成器的完整实现# src/textual/_loop.py节选共 4 个函数 def loop_first(values: Iterable[T]) - Iterable[tuple[bool, T]]: Iterate and generate a tuple with a flag for first value. iter_values iter(values) try: value next(iter_values) except StopIteration: return yield True, value for value in iter_values: yield False, value def loop_last(values: Iterable[T]) - Iterable[tuple[bool, T]]: Iterate and generate a tuple with a flag for last value. iter_values iter(values) try: previous_value next(iter_values) except StopIteration: return for value in iter_values: yield False, previous_value previous_value value yield True, previous_value def loop_first_last(values: Iterable[T]) - Iterable[tuple[bool, bool, T]]: Iterate and generate a tuple with a flag for first and last value. ... def loop_from_index( values: Sequence[T], index: int, direction: Literal[-1, 1] 1, wrap: bool True, ) - Iterable[tuple[int, T]]: Iterate over values in a sequence from a given starting index, potentially wrapping... ...各函数语义如下函数产出用途loop_first(values)(bool, item)第一个元素标记为Trueloop_last(values)(bool, item)最后一个元素标记为Trueloop_first_last(values)(bool, bool, item)同时给出 first/last 两个标志位loop_from_index(values, index, direction1, wrapTrue)(index, item)从指定下标开始循环遍历支持方向与回绕几个值得注意的实现细节所有函数都是惰性生成器对空的可迭代对象会直接结束而不产出任何元素loop_last通过多看一个元素的方式提前预读从而在消费到最后一个元素时才标记True因此可以正确处理无限或超长迭代器无需预先知道长度loop_from_index内部对direction做了断言只能是-1或1wrapTrue时用取模实现首尾回绕wrapFalse时越界即停注意其语义是先走一步再产出起始下标本身会最后一个被产出见源码 docstring。实战用法原 DevLog 给出的例子DevLog 中展示的场景是在逐行输出终端内容时需要在每一行之间插入换行、但最后一行不能换行for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)): yield move_to(x, y) yield from line if not last: yield new_line如果不用loop_last这段逻辑通常要写成记录 index循环结束前判断是否index len(lines) - 1或者先取所有行再切片拼接——而loop_last一行就表达清楚了。Textual 内部实际使用这些函数的地方src/textual/_compositor.py 在合成渲染行render_strips时用loop_last判断最后一行避免在末尾多输出换行符src/textual/_wrap.py 在按制表符折叠文本行时用loop_last区分段间连接符src/textual/content.py 用loop_last拼接被切分的多行内容src/textual/_widget_navigation.py 用loop_from_index实现焦点在候选控件间的循环移动当前焦点向后/向前找下一个可聚焦控件src/textual/drivers/linux_driver.py 在处理 select 事件掩码时也使用了loop_last。测试方面tests/test_loop.py 覆盖了空迭代、单元素、多元素以及 first/last 标志位的正确性例如assert list(loop_first([])) [] iterable loop_first([apples, oranges, pears, lemons]) # - [(True, apples), (False, oranges), (False, pears), (False, lemons)]LRUCache能控制生命周期的容器型缓存Python 标准库的functools.lru_cache是一行注释就能让代码快几个数量级的神器但它有几个坑全局单例缓存装饰器持有的是一个模块级全局缓存生命周期不可控缓存会保存每次调用涉及的对象引用。用在实例方法上时等于在应用存活期间一直持有self的引用阻止垃圾回收。Textual 的 src/textual/cache.py 提供了容器实现的 LRU 缓存算法与标准库装饰器本质相同但以 dict 风格对象存在缓存的生命周期完全由你掌控。注意原 DevLog 中该模块名为_cache.py当前仓库中已更名为公开模块cache.py并导出__all__ [LRUCache, FIFOCache]这本身也说明官方将其视为可对外复用的稳定 API。快速上手原 DevLog 的 REPL 示例按当前仓库修正导入路径 from textual.cache import LRUCache cache LRUCache(maxsize3) cache[foo] 1 cache[bar] 2 cache[baz] 3 dict(cache) {foo: 1, bar: 2, baz: 3} cache[egg] 4 dict(cache) {bar: 2, baz: 3, egg: 4}它用起来像字典直到达到maxsize上限之后新元素会把最久未使用least recently used的元素挤出去——上面示例中foo是最早写入的在egg写入后被淘汰。实现原理双向链表 哈希表从 src/textual/cache.py 的源码看LRUCache采用了与functools.lru_cache相同的结构内部维护一张key - [PREV, NEXT, KEY, VALUE]的哈希表其中PREV/NEXT是指向链表中前驱和后继条目的引用从而构成一个循环双向链表set新 key 插入链表头部缓存已满时从链表尾部淘汰最久未使用的条目并del self._cache[last[2]]get/__getitem__命中时把该条目移动到链表头部最近使用并累加hits未命中累加misses并返回默认值或抛KeyError__slots__优化了内存占用属性包括_maxsize、_cache、_full、_head以及统计用的hits/misses。完整 API 一览方法/属性行为LRUCache(maxsize)构造maxsize为容量上限cache[key] value/set(key, value)写入满时淘汰最久未使用项cache[key]/get(key, defaultNone)读取未命中时__getitem__抛KeyErrorget返回默认值discard(key)按 key 移除条目不存在则静默返回grow(maxsize)将上限提升为max(maxsize, 当前上限)clear()清空缓存并重置链表keys()/len()/key in cache/bool(cache)字典风格接口hits/misses命中/未命中计数便于调优容量maxsize属性可读可写运行中动态调整容量同模块还提供了更轻量的 FIFOCache同样受maxsize约束但满时淘汰最先加入的条目。它的开销比 LRUCache 更低但不会维护工作集适合容量不大、查询频率不高的场景如 src/textual/content.py 中的使用。Textual 用它缓存什么src/textual/css/stylesheet.py用LRUCache(64)缓存 CSS 规则集解析结果、用LRUCache(1024 * 4)缓存样式解析结果src/textual/dom.py用LRUCache(1024)缓存query_one的选择器查询结果原 DevLog 提到 DataTable 场景终端渲染成本太高无法一次性渲染全部行于是只渲染屏幕上当前可见的行用 LRU 缓存保证滚动时只渲染新暴露的行同时把很久未显示的行淘汰以节省内存。源码 docstring 里有一条重要提醒标准库lru_cache是 C 实现更快。所以最佳实践是——高频且快速的小操作用lru_cache缓存较慢的重操作、需要显式控制生命周期时用LRUCache此时缓存自身的开销只占总处理时间的一小部分。tests/test_cache.py 对 LRU 淘汰顺序、grow、discard、命中率统计等行为都有系统化验证可以直接作为搬走代码后的回归测试。Color无 C 依赖的纯 Python 颜色工具Textual 的 Color 类可以解析多种 HTML/CSS 格式的颜色并支持一系列直观的操作符和方法来操纵颜色。它的实现只有纯 Python基于标准库colorsys做色彩空间转换没有 C 依赖非常适合提取为独立模块。支持的解析格式从源码顶部的正则RE_COLOR与命名颜色表src/textual/_color_constants.py可以确认Color.parse支持十六进制#RGB、#RGBA、#RRGGBB、#RRGGBBAA函数形式rgb(r,g,b)、rgba(r,g,b,a)、hsl(h,s%,l%)、hsla(h,s%,l%,a)CSS 命名颜色如lime、red以及 ANSI 颜色表解析失败时会抛出ColorParseError并携带相近颜色的拼写建议见 src/textual/color.py。原 DevLog 的 REPL 示例完整保留 from textual.color import Color color Color.parse(lime) color Color(0, 255, 0, a1.0) color.darken(0.8) Color(0, 45, 0, a1.0) color Color.parse(red).with_alpha(0.1) Color(25, 229, 0, a1.0) color Color.parse(#12a30a) color Color(18, 163, 10, a1.0) color.css rgb(18,163,10) color.hex #12A30A color.monochrome Color(121, 121, 121, a1.0) color.monochrome.hex #797979 color.hsl HSL(h0.3246187363834423, s0.8843930635838151, l0.33921568627450976)从中可以看出它的设计哲学用自然的数学方式操作颜色。darken(0.8)把亮度压暗、做颜色混合带 alpha 的红色叠加到绿色上得到偏绿的混合色、monochrome转为灰度、css/hex输出不同格式的字符串、hsl暴露 HSL 分量。数据结构与配套类型Color本身是一个NamedTuple字段为r0–255、g0–255、b0–255、a0.0–1.0另有ansi与auto两个扩展字段同模块还定义了 HSL、HSV、Lab三个色彩空间元组类型方便在不同表示间转换提供Color.parse、Color.automatic自动黑白对比色、from_rich_color与 Rich 颜色互转等工厂方法其中from_rich_color还套了lru_cache(maxsize1024)做转换加速。PyPI 上确实有非常优秀的颜色库可供选择但如果需要的是一个精简、无 C 扩展、API 直白的小型颜色模块Textual 的这份实现是现成的答案。仓库中 tests/css/test_parse.py、tests/css/test_inheritance.py 以及 tests/animations/test_disabling_animations.py 等测试都对Color.parse与颜色运算结果做了断言可佐证其行为。Geometry终端 2D 几何的基石geometry模块是原 DevLog 作者最喜欢的模块。它包含一组负责存储与操作二维几何的类支撑着 Textual 的布局引擎与合成器是项目中最古老、测试最充分的代码之一。模块入口在 src/textual/geometry.py约 1500 行。四大核心类类含义典型字段Offset二维坐标点x、yRegion由坐标与尺寸定义的矩形区域x、y、width、heightSpacing区域四周的附加空间margin/padding 语义top、right、bottom、leftSize区域的宽高width、height这些类都实现了丰富的运算符重载加、减、乘、比较、相交、包含判断等。例如Offset支持、-、*、取负并提供is_origin、clamped、transpose等便捷属性src/textual/geometry.py模块顶层的clamp(value, minimum, maximum)函数还被color.py引用用于把数值限制在范围内且不要求 min/max 顺序正确。充满 Unicode 艺术的详尽 docstring这个模块的 docstring 非常细致甚至用 ASCII/Unicode 图来解释方法行为。原 DevLog 引用的是Region.split的切分示意图——该方法用cut_x和cut_y两条切割线把一个矩形区域切成 4 个子区域cut_x ↓ ┌────────┐ ┌───┐ │ │ │ │ │ 0 │ │ 1 │ │ │ │ │ cut_y → └────────┘ └───┘ ┌────────┐ ┌───┐ │ 2 │ │ 3 │ └────────┘ └───┘对应的源码在 src/textual/geometry.pycut_x/cut_y支持负数从区域右/下边界反向计算返回(0, 1, 2, 3)四个Region。这种先画图、再写代码的文档方式让几何逻辑的意图一目了然——把它搬进自己的项目时这些 docstring 本身就是最好的使用手册。在 Textual 中的地位从源码结构可以推断Offset、Region、Spacing、Size被布局解析src/textual/css/scalar.py 等 CSS 子系统、组件布局src/textual/layout.py与合成器src/textual/_compositor.py大量引用tests/css/test_parse.py、tests/css/test_styles.py 中也直接导入了Spacing进行断言。如果你的项目需要处理终端/网格/2D 场景下的矩形运算、碰撞检测或区域切分这个模块几乎可以开箱即用。结语整个 Textual 都欢迎你去偷除了上面四个模块Textual 仓库里还有大量纯 Python 编写的可提取部件CSS 解析器、渲染器renderer、布局引擎与合成引擎compositing engine。正如原 DevLog 所说带着我的祝福去偷吧。在实际复用之前请留意两件事许可证与署名Textual 使用 MIT 许可证见仓库根目录 LICENSE复用代码时请遵守其条款——通常需要保留版权声明并给出署名依赖边界color.py内部引用了 Rich 的颜色与终端主题类型from rich.color import ...geometry.py也使用了rich.repr做漂亮的 repr 输出整体提取时要么一并依赖 Rich要么按需裁剪这些引用。而_loop.py与cache.py则完全零外部依赖仅用到标准库typing是可以一行不改直接搬进任何项目的首选。如果你正在写自己的 Python 库或工具不妨先打开 src/textual 翻一翻这里有一套久经真实终端应用检验、附带测试和详尽文档的纯 Python 基础设施等着被你偷走。【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表