Skip to content

Commit 73e0e7d

Browse files
walter201230claude
andcommitted
fix: 让 example_runner CI 回绿——修 stdin 隔离、Queue 死锁与 class 笔误,67 个教学片段加 skip 标注
这个 workflow 自 2026-04-28 加入起跑过 16 次、一次没绿过,69 个「失败」里 只有 2 个是真问题,其余全是本来就不该当独立程序跑的教学片段。一直红着等于 没有 CI——真回归会被淹没,本次就是为了确认前一个 commit 有没有引入新失败, 才不得不逐块做差集,两个真 bug 由此第一次单独露出来。 工具缺陷(长期红的根因): - subprocess.run 没传 stdin,含 input() 的块在有 stdin 的环境里等到超时、 在没有的环境里立刻抛 EOFError,同一份代码两种结果。CI 各版本失败数长期 在 69~72 之间飘就是这个原因,不是内容有四种状态。钉死为 DEVNULL。 - 超时 10s → 30s:并发示例本地实测就要 4~5 秒(python13/2.md 两块各 5.04s、 python13/3.md 一块 4.07s),CI 机器更慢必然偶发超时。 真 bug 两处: - python13/2.md 的 Queue 生产者消费者示例死锁:写线程放完 3 个值就结束, 读线程 while isRead 永远为真、q.get(True) 永久阻塞,读者复制到本地跑必须 强制中断。改为写完放 None 哨兵、读到哨兵 break,并删掉那个永远为真的 isRead 全局变量。这段代码 2019-10-14 就在了,因为输出完全正确、只是不会 自己退出,手动验证看不出来。python-lab 有同源内容,已同步修改。 - codeSpecification_third.md 的 `Class FooBar:` 写成大写 C,读者复制即 SyntaxError。两仓 grep 确认 python-lab 无同样问题。 67 处 <!-- skip-ci --> 标注,涉及 34 个文件:语法示意(for x in sequence:)、 占位符、假模块名(import decoder / foo / bar)、依赖前文的连续片段、故意演示 报错的例子(如 python15 闭包章节的 UnboundLocalError)。与 python17-27 既有的 131 处标注同样处理——此前 skip 标注只覆盖 2026 新写的章节,python0-16 与 codeSpecification 从未处理过,这是 CI 长期红的结构性原因。 验证:本地 python3 全量跑 691 块,改前「跳过 131;失败 69」exit 1, 改后「跳过 198;失败 0」exit 0;Queue 示例改后连跑 5 次均 0.02 秒正常退出、 输出 6 行。 已知残留:python13/2.md 第 339 行的 Event 示例有竞态(if event.isSet() 与 event.clear() 之间非原子,两个线程可能都进 wait() 而只有一个 set() 唤醒), 本地连跑 20 次未触发,但 8-31 那次 CI 的 3.11 与 3.13 各失败过一次,故 CI 仍有小概率偶发红。未在本次范围内处理。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKcB6SFyeRo9UPJL99QvkC
1 parent c91a9a0 commit 73e0e7d

35 files changed

Lines changed: 78 additions & 6 deletions

File tree

‎Article/PythonBasis/python11/2.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
Enum 在模块 enum.py 中,先来看看 Enum 类的片段
1313

14+
<!-- skip-ci -->
1415
```python
1516
class Enum(metaclass=EnumMeta):
1617
"""Generic enumeration.

‎Article/PythonBasis/python12/2.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Hello(object):
2121

2222
其中 `type()` 函数的作用是可以查看一个类型和变量的类型。
2323

24+
<!-- skip-ci -->
2425
```python
2526
#!/usr/bin/env python3
2627
# -*- coding: UTF-8 -*-
@@ -97,6 +98,7 @@ Hello, Py
9798

9899
具体的模式如下:
99100

101+
<!-- skip-ci -->
100102
```python
101103
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
102104
```

‎Article/PythonBasis/python12/3.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ print(mEat.__class__)
7272

7373
我们可以继续在上面的代码基础上新增下面的代码:
7474

75+
<!-- skip-ci -->
7576
```python
7677
print(age.__class__.__class__)
7778
print(name.__class__.__class__)

‎Article/PythonBasis/python12/4.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ metaclass,直译为元类,简单的解释就是:
2626

2727
换句话说,你可以把类看成是 metaclass 创建出来的“实例”。
2828

29+
<!-- skip-ci -->
2930
```python
3031
class MyObject(object):
3132
__metaclass__ = something…
@@ -84,6 +85,7 @@ ython 会在类的定义中寻找 `__metaclass__` 属性,如果找到了,Pyt
8485

8586
再举个实例:
8687

88+
<!-- skip-ci -->
8789
```python
8890
class Foo(Bar):
8991
pass
@@ -115,6 +117,7 @@ def upper_attr(future_class_name, future_class_parents, future_class_attr):
115117
```
116118

117119

120+
<!-- skip-ci -->
118121
```python
119122
# 将它们转为大写形式
120123
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
@@ -130,6 +133,7 @@ class Foo(object):
130133
bar = 'bip'
131134
```
132135

136+
<!-- skip-ci -->
133137
```python
134138
print hasattr(Foo, 'bar')
135139
# 输出: False
@@ -177,6 +181,7 @@ class UpperAttrMetaclass(type):
177181

178182
你可能已经注意到了有个额外的参数 `upperattr_metaclass` ,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的 self 参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像 self 一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:
179183

184+
<!-- skip-ci -->
180185
```python
181186
class UpperAttrMetaclass(type):
182187
def __new__(cls, name, bases, dct):

‎Article/PythonBasis/python12/5.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
元类的主要用途是创建 API。一个典型的例子是 Django ORM。它允许你像这样定义:
88

9+
<!-- skip-ci -->
910
```python
1011
class Person(models.Model):
1112
name = models.CharField(max_length=30)
@@ -14,6 +15,7 @@ class Person(models.Model):
1415

1516
但是如果你这样做的话:
1617

18+
<!-- skip-ci -->
1719
```python
1820
guy = Person(name='bob', age='35')
1921
print guy.age

‎Article/PythonBasis/python13/2.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,21 @@ End Main threading
154154

155155
其中 `threading` 模块给我们提供了一个 Lock 功能。
156156

157+
<!-- skip-ci -->
157158
```python
158159
lock = threading.Lock()
159160
```
160161

161162
在线程中获取锁
162163

164+
<!-- skip-ci -->
163165
```python
164166
lock.acquire()
165167
```
166168

167169
使用完成后,我们肯定需要释放锁
168170

171+
<!-- skip-ci -->
169172
```python
170173
lock.release()
171174
```
@@ -174,6 +177,7 @@ lock.release()
174177

175178
那么怎么创建重入锁呢?也是一句代码的事情:
176179

180+
<!-- skip-ci -->
177181
```python
178182
r_lock = threading.RLock()
179183
```
@@ -277,20 +281,21 @@ producer.start()
277281
from queue import Queue
278282
from threading import Thread
279283

280-
isRead = True
281-
282284

283285
def write(q):
284286
# 写数据进程
285287
for value in ['两点水', '三点水', '四点水']:
286288
print(f'写进 Queue 的值为:{value}')
287289
q.put(value)
290+
q.put(None) # 写完放一个结束标记
288291

289292

290293
def read(q):
291294
# 读取数据进程
292-
while isRead:
295+
while True:
293296
value = q.get(True)
297+
if value is None: # 读到结束标记就退出
298+
break
294299
print(f'从 Queue 读取的值为:{value}')
295300

296301

‎Article/PythonBasis/python13/3.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Process finished with exit code 0
7373
当然我们也可以把进程创建成一个类,如下面的例子,当进程 p 调用 start() 时,自动调用 run() 方法。
7474

7575

76+
<!-- skip-ci -->
7677
```python
7778
# -*- coding: UTF-8 -*-
7879

@@ -268,6 +269,7 @@ if __name__ == '__main__':
268269

269270
请注意输出的结果,子进程 0,1,2,3是立刻执行的,而子进程 4 要等待前面某个子进程完成后才执行,这是因为 Pool 的默认大小在我的电脑上是 4,因此,最多同时执行 4 个进程。这是 Pool 有意设计的限制,并不是操作系统的限制。如果改成:
270271

272+
<!-- skip-ci -->
271273
```python
272274
p = Pool(5)
273275
```

‎Article/PythonBasis/python14/1.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ print(f'是否含有“两点水”这个字符串:{"两点水" in a}')
2929

3030
刚刚提到过,Python 给我们提供了 re 模块来实现正则表达式的所有功能,那么我们先使用其中的一个函数:
3131

32+
<!-- skip-ci -->
3233
```python
3334
re.findall(pattern, string[, flags])
3435
```

‎Article/PythonBasis/python14/6.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
语法:
66

7+
<!-- skip-ci -->
78
```python
89
re.match(pattern, string, flags=0)
910
```
@@ -15,6 +16,7 @@ re.match 尝试从字符串的起始位置匹配一个模式,如果不是起
1516

1617
语法:
1718

19+
<!-- skip-ci -->
1820
```Python
1921
re.search(pattern, string, flags=0)
2022
```

‎Article/PythonBasis/python15/1.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
面对这个需求,我们一般都会创建一个全局变量来记录时间,然后用一个方法来新增每次的学习时间,通常都会写成下面这个形式:
88

9+
<!-- skip-ci -->
910
```Python
1011
time = 0
1112

0 commit comments

Comments
 (0)