Skip to content

Commit 5688614

Browse files
📝 更新Python文档,新增字符串格式化中的双花括号转义与__dict____slots__的详细说明
- 在文档中新增了双花括号转义的用法,展示了如何在字符串模板中保留字面花括号,提升了内容的实用性。 - 增加了关于`__dict__`和`__slots__`的详细解释,阐述了它们在属性存储中的作用及内存优化的技巧。 - 引入了`__match_args__`的介绍,说明其在模式匹配中的应用,增强了文档的全面性与参考价值。
1 parent e894c2a commit 5688614

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

docs/docs/选择编程语言/Python/20类与对象.mdx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,203 @@ print(nike_clothes.__class__) # <class '__main__.NikeClothes'>
784784
print(nike_clothes.__class__.__bases__) # (<class '__main__.Clothes'>,)
785785
```
786786

787+
### \_\_dict\_\_\_\_slots\_\_
788+
789+
默认情况下,Python把各个实例的属性存储在一个名为`__dict__`的字典中。这个字典的键是属性名,值是属性值。
790+
791+
每当属性被增加或删除时,`__dict__`都会被更新。
792+
793+
```python showLineNumbers
794+
class Clothes(object):
795+
def __init__(self, color="green"):
796+
self.color = color
797+
798+
# 标准方式增加属性
799+
my_clothes = Clothes()
800+
print(my_clothes.__dict__) # {'color': 'green'}
801+
802+
# 动态增加属性
803+
my_clothes.size = "L"
804+
print(my_clothes.__dict__) # {'color': 'green', 'size': 'L'}
805+
806+
# 删除属性
807+
del my_clothes.color
808+
print(my_clothes.__dict__) # {'size': 'L'}
809+
```
810+
811+
不过字典占用的空间较大,如果实例的属性较多,可能会占用较多的内存。
812+
813+
如果定义一个名为`__slots__`的类属性,以序列的形式存储属性名称,那么Python将使用`__slots__`指定的数据模型存储实例属性,而不会使用`__dict__`。此时你虽然可以继续定义`__dict__ = {}`,但是它已经不会再动态的添加属性了。
814+
815+
`__slots__`空间占用更小的代价是失去了动态添加属性和弱引用的能力。下面的例子中,你不能添加 `x``y` 之外的属性。
816+
817+
:::note
818+
普通引用会增加对象的引用计数,导致对象无法被垃圾回收。
819+
820+
弱引用(weak reference)是一种不增加对象引用计数的引用方式。当对象的所有强引用都被删除后,即使存在弱引用,对象也会被垃圾回收。
821+
822+
弱引用不会阻止对象的销毁,但可以在对象被销毁前访问它。
823+
:::
824+
825+
`__slots__`必须在定义类时声明,之后再添加或修改均无效。属性名称可以存储在一个元组或列表中,不过我喜欢使用元组,因为这可以明确表明`__slots__`无法修改。
826+
827+
```python showLineNumbers
828+
# 继承自object,所以默认有__weakref__属性、__dict__属性
829+
class P1:
830+
pass
831+
832+
# 定义了__slots__,所以没有__weakref__属性、__dict__属性
833+
# 但是多了__slots__属性和'x', 'y'两个定义的属性
834+
class P2:
835+
__slots__ = ('x', 'y')
836+
print(set(dir(P1())) - set(dir(P2())))
837+
# {'__dict__', '__weakref__'}
838+
print(set(dir(P2())) - set(dir(P1())))
839+
# {'__slots__', 'y', 'x'}
840+
841+
p = P2()
842+
843+
p.x = 10
844+
p.y = 20
845+
print(p.__slots__) # ('x', 'y')
846+
847+
# p.size = 'L'
848+
# AttributeError: 'P2' object has no attribute 'size' and no __dict__ for setting new attributes
849+
```
850+
851+
:::tip
852+
想把该类的实例作为弱引用的目标,则必须把`'__weakref__'`添加到`__slots__`中。
853+
854+
想把该类的实例继续动态添加属性,则必须把`'__dict__'`添加到`__slots__`中。该实例的非`__slots__`中的属性会存储在`__dict__`中,而不是`__slots__`中。但是这样会导致`__slots__`失去意义。
855+
856+
```python showLineNumbers
857+
# 定义了__slots__,所以必须把__weakref__添加到__slots__中
858+
class Pixel2:
859+
__slots__ = ('x', 'y','__weakref__')
860+
861+
# 没有定义__slots__,所以默认就有__weakref__属性
862+
class Pixel3:
863+
pass
864+
865+
p = Pixel()
866+
print("__weakref__" in dir(p)) # False
867+
p2 = Pixel2()
868+
print("__weakref__" in dir(p2)) # True
869+
p3= Pixel3()
870+
print("__weakref__" in dir(p3)) # True
871+
872+
class Parent:
873+
__slots__ = ('x', 'y',"__dict__")
874+
875+
p = Parent()
876+
p.xx = 1 # 非__slots__中的属性会存储在__dict__中
877+
p.x = 1 # __slots__中的属性会存储在__slots__中
878+
print(p.__dict__) # {'xx': 1}
879+
print(p.__slots__) # ('x', 'y', '__dict__')
880+
```
881+
:::
882+
883+
当子类继承父类时,子类会继承父类的`__slots__`,但是会有一些奇特的表现。
884+
885+
```python showLineNumbers
886+
class P:
887+
__slots__ = ("x")
888+
889+
class C(P):
890+
pass
891+
892+
class X:
893+
pass
894+
895+
# 子类和标准类X,取对称差集发现,子类C本该失去的'__weakref__'和'__dict__'又回来了
896+
# 同时依然继承了父类的'__slots__'属性和创建的'x'属性
897+
print(set(dir(C)) ^ set(dir(X)))
898+
# {'__slots__', 'x'}
899+
c = C()
900+
901+
c.x = 1
902+
c.y = 2
903+
# 这个表现等价于在 __slots__ 中添加了 '__weakref__'和'__dict__'
904+
print(c.__dict__) # {'y': 2}
905+
906+
"""
907+
为了确保子类的实例也没有`__dict__`属性,必须在子类中再次声明`__slots__`属性。
908+
909+
如果在子类中声明`__slots__= ()`(一个空元组)​,则子类的实例将没有`__dict__`属性,而且只接受基类的`__slots__`属性列出的属性名称。如果子类需要额外属性,则在子类的__slots__属性中列出来。
910+
"""
911+
class C2(P):
912+
__slots__= ()
913+
914+
class C3(P):
915+
__slots__= ("y",)
916+
```
917+
918+
:::tip
919+
如果使用得当,则类属性`__slots__`能显著节省内存(60%左右),不过有几个问题需要注意。
920+
921+
- 每个子类都要重新声明`__slots__`属性,以防止子类的实例有`__dict__`属性。
922+
- 实例只能拥有`__slots__`列出的属性,除非把`'__dict__'`加入`__slots__`中(但是这样做就失去了节省内存的功效)​。
923+
-`__slots__`的类不能使用`@cached_property`装饰器,除非把`'__dict__'`加入`__slots__`中。
924+
- 如果不把`'__weakref__'`加入`__slots__`中,那么实例就不能作为弱引用的目标。
925+
:::
926+
927+
### \_\_match_args\_\_
928+
929+
`__match_args__` 是 Python 3.10+ 引入的类属性,用于在模式匹配(match-case)中定义位置参数的顺序。它允许类指定在类模式匹配时,如何将位置参数映射到类的属性。
930+
931+
在模式匹配中,可以使用位置参数或关键字参数来匹配类的属性:
932+
933+
```python showLineNumbers
934+
class Point:
935+
__match_args__ = ('x', 'y') # 定义位置参数的顺序
936+
937+
def __init__(self, x, y):
938+
self.x = x
939+
self.y = y
940+
941+
def process_point(p):
942+
match p:
943+
# 没有定义__match_args__,只能使用关键字参数匹配
944+
# 不能使用位置参数匹配
945+
case Point(x=0, y=_):
946+
return "y轴上的点"
947+
# 定义之后可以使用位置参数匹配,更简洁
948+
case Point(_,0):
949+
return "x轴上的点"
950+
case _:
951+
return f"点({p.x}, {p.y})"
952+
953+
print(process_point(Point(0, 5))) # y轴上的点
954+
print(process_point(Point(3, 0))) # x轴上的点
955+
print(process_point(Point(2, 3))) # 点(2, 3)
956+
```
957+
`__match_args__` 用于定义在模式匹配中使用位置参数时的顺序,关键字参数匹配总是可用,无需 `__match_kwargs__`
958+
959+
可以自定义 `__match_args__` 来控制哪些属性可以通过位置参数匹配。`@dataclass` 会自动生成 `__match_args__`,除非使用 `kw_only=True`
960+
961+
```python showLineNumbers
962+
class Point:
963+
__match_args__ = ('flag',) # 定义位置参数的顺序,注意,位置参数必须在默认参数之前。
964+
965+
def __init__(self, flag, x, y):
966+
self.x = x
967+
self.y = y
968+
self.flag = flag
969+
970+
# 使用关键字参数匹配(总是可用)
971+
def process_point(p):
972+
match p:
973+
# 定义__match_args__,只有__match_args__设置的关键字才能使用位置参数匹配
974+
case Point(99,x=0, y=0):
975+
return "彩蛋"
976+
# 异常,因为我们只设置了1个参数可以位置参数匹配,这里传入了3个位置参数,所以报错
977+
case Point(100, 0, 0):
978+
return "异常"
979+
print(process_point(Point(99,x = 0, y = 0))) # 彩蛋
980+
print(process_point(Point(99, 0, 0))) # 彩蛋
981+
982+
print(process_point(Point(100, 0, 0))) # TypeError: Point() accepts 1 positional sub-pattern (3 given)
983+
```
787984

788985
## 重载类型转换
789986

docs/docs/选择编程语言/Python/2字符串.mdx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,59 @@ print('{:%Y-%m-%d %H:%M:%S}'.format(d)) # 2008-12-03 12:15:58
307307
# # 2008-12-03 是Python3.0.0的发布日期。
308308
```
309309

310+
#### 双花括号转义
311+
312+
在字符串格式化中,双花括号 `{{``}}` 用于转义,表示字面上的花括号字符。这在定义模板字符串时非常有用,特别是大模型提示词等场景,可以先定义模板,稍后再插入参数。
313+
314+
```python showLineNumbers
315+
# 定义模板字符串,使用双花括号表示字面上的花括号
316+
template = "你是一个{{role}}助手,请用{{tone}}的语气回答:{{question}}"
317+
318+
# 稍后通过 format() 方法插入参数
319+
prompt = template.format(role="专业", tone="友好", question="什么是Python?")
320+
print(prompt)
321+
# 输出:你是一个专业助手,请用友好的语气回答:什么是Python?
322+
323+
# 也可以使用关键字参数
324+
prompt2 = template.format(
325+
role="教学",
326+
tone="耐心",
327+
question="如何学习编程?"
328+
)
329+
print(prompt2)
330+
# 输出:你是一个教学助手,请用耐心的语气回答:如何学习编程?
331+
332+
# 如果模板中需要包含字面上的花括号,使用双花括号转义
333+
template_with_braces = "变量名:{{name}},值:{{{{value}}}}"
334+
result = template_with_braces.format(name="x", value=42)
335+
print(result)
336+
# 输出:变量名:x,值:{value}
337+
```
338+
:::tip
339+
f-string 也支持双花括号转义,但需要注意:**f-string 是立即求值的**,在定义时就会执行表达式。如果需要在模板中保留花括号供后续格式化,应该使用普通字符串配合 `.format()` 方法。
340+
341+
```python showLineNumbers
342+
# f-string 中使用双花括号转义
343+
name = "Python"
344+
# 在 f-string 中,双花括号会被转义为单个花括号
345+
text = f"语言名称:{{name}}"
346+
print(text)
347+
# 输出:语言名称:{name}
348+
349+
# 注意:f-string 会立即求值,所以这样写会直接替换变量
350+
text2 = f"语言名称:{name}"
351+
print(text2)
352+
# 输出:语言名称:Python
353+
354+
# 如果需要延迟格式化,使用普通字符串模板
355+
template = "语言名称:{{name}}"
356+
# 稍后再格式化
357+
result = template.format(name="Python")
358+
print(result)
359+
# 输出:语言名称:Python
360+
```
361+
:::
362+
310363
### Unicode 和转义字符
311364

312365
Python3 的字符就是`Unicode`字符。

0 commit comments

Comments
 (0)