视频加载失败

Python 笔记

4193 字
21 分钟
Python 笔记

一、基础语法#

编码声明#

Python3 源文件默认就是 UTF-8 编码,下面这行常见于兼容 Python2 的遗留习惯,现在通常可以省略:

# encoding: utf-8

空函数体的两种写法#

函数体为空时,pass 是唯一合法的”什么都不做”语句;...(Ellipsis)效果等价,常见于类型标注 stub 文件:

def main() -> None:
pass
def stub() -> None:
...

多重赋值#

一行内可以同时给多个变量赋值:

name, age, decimal_number = "张三", 18, 15

二、字符串格式化(f-string)#

f-string(格式化字符串字面量)是 Python 3.6(PEP 498)引入的字符串写法,在字符串前加 f/F 前缀,大括号 {} 里可以直接嵌入表达式。

完整替换字段结构总览#

大括号内完整的语法是:

{expression [=] [!conversion] [:format_spec]}

四个部分各司其职:expression 决定”取什么值”,= 决定”要不要连同源码一起打印出来”(自文档化),!conversion 决定”取值时走 str()/repr()/ascii() 哪一个”,:format_spec 决定”取到的值怎么排版”。

1. 表达式部分:能写什么#

{} 里不只能放变量名,任何合法的 Python 表达式都行——下标、方法调用、算术、函数调用、三元表达式等等:

d = {'name': 'Alice', 'age': 30}
lst = [10, 20, 30]
class Cat:
def meow(self):
return "喵"
cat = Cat()
n = 7
print(f'{d["name"]}') # Alice (字典下标)
print(f'{lst[1]}') # 20 (列表下标)
print(f'{cat.meow()}') # 喵 (方法调用)
print(f'{n * 2 + 1}') # 15 (算术表达式)
print(f'{"偶" if n % 2 == 0 else "奇"}') # 奇 (三元表达式)
print(f'{len(lst)}') # 3 (函数调用)

2. format spec(格式说明符)语法#

f-string 的格式说明符完整结构是:

[[fill]align][sign][#][0][width][grouping_option][.precision][type]

所有代码均已实测验证。

1. fill(填充字符)#

任意单个字符(除 {}、换行符外),用于填充到 width 指定的宽度。必须与 align 同时出现。

pi = 3.1415926535893238462643383279
print(f'{pi:我<21.10f}') # 用"我"填充、左对齐、总宽度 21、保留 10 位小数

2. align(对齐方式)#

符号含义
<左对齐(默认,字符串的默认行为)
>右对齐(数字类型的默认行为)
^居中对齐
=符号在最左、数字在最右,中间用填充字符填满(仅对数字有效)
x = 42
print(f'|{x:<10}|') # |42 |
print(f'|{x:>10}|') # | 42|
print(f'|{x:^10}|') # | 42 |
print(f'|{x:=10}|') # | 42| (正数没符号,效果同 >)
print(f'|{-x:*=10}|') # |-*******42| ← 【已修正】宽度10 - 符号1位 - 数字2位 = 7个星号,原笔记误写为8个

= 对齐不能用于字符串,会直接报错:

f'{"abc":=10}'
# ValueError: '=' alignment not allowed in string format specifier

= 对齐配合 sign 使用时,符号永远贴最左,填充字符插在符号和数字之间:

print(f'{-42:=+10}') # - 42
print(f'{42:=+10}') # + 42

3. sign(符号)#

符号含义
+正数显示 +,负数显示 -
-正数不显示符号,负数显示 -(默认)
(空格)正数前留空格,负数显示 -
a, b = 3.14, -3.14
print(f'{a:+}') # +3.14
print(f'{b:+}') # -3.14
print(f'{a:-}') # 3.14
print(f'{a: }') # 3.14
print(f'{b: }') # -3.14

4. #(备用形式 / alternate form)#

对整数(b/o/x/X)添加前缀 0b/0o/0x/0X;对浮点数强制显示小数点。

n = 42
print(f'{n:#b}') # 0b101010
print(f'{n:#o}') # 0o52
print(f'{n:#x}') # 0x2a
print(f'{n:#X}') # 0X2A
print(f'{3.0:#.0f}') # 3. (带小数点)

5. 0(零填充)#

等价于 fill="0"align="="

print(f'{42:0>5}') # 00042 (fill=0, align=>)
print(f'{42:05}') # 00042 (等价写法)
print(f'{-42:05}') # -0042 (注意 0 在 = 模式)

关于 0 与显式 fill/align 的关系:【已修正】原笔记称”0fill 不能同时出现”,这个说法不准确。实际上二者可以共存,只是一旦显式写了 fillalign0 的零填充效果就会被覆盖:

print(f'{42:*<010}') # 42******** ← 不报错,但0没有起到零填充作用,填充的是 *
print(f'{42:010}') # 0000000042 ← 不写 fill/align 时,0 才真正生效

6. width(最小宽度)#

整数,指定输出的最小总宽度(包括符号、前缀、小数点、分组符在内)。

print(f'{42:10}') # ' 42'

7. grouping_option(千位分隔)#

符号含义
,逗号分隔
_下划线分隔
n = 1234567890
print(f'{n:,}') # 1,234,567,890
print(f'{n:_}') # 1_234_567_890
print(f'{3.14159:,.2f}') # 3.14 (小整数位不够不加逗号)
print(f'{1234567.89:,.2f}') # 1,234,567.89

对 b/o/x 类型用 _ 每 4 位分隔:

print(f'{255:_b}') # 1111_1111
print(f'{255:#_b}') # 0b1111_1111
print(f'{4095:_x}') # fff ← 【已修正】4095 的十六进制只有3位(fff),不足4位不会分组;原笔记的 'f_f_f' 是错的
print(f'{0xFFFFFFF:_x}') # fff_ffff ← 换一个够长的数才能看到分组效果

8. . precision(精度)#

  • fFeE:小数点后保留的位数。
  • gG:有效数字的总位数。
  • 对字符串 s:最大字符数(截断)。
pi = 3.141592653589793
print(f'{pi:.2f}') # 3.14
print(f'{pi:.6f}') # 3.141593
print(f'{pi:.4e}') # 3.1416e+00
print(f'{pi:.4g}') # 3.142
print(f'{"hello":.3s}') # hel

9. type(类型码)#

字符串:

类型含义
s字符串格式(默认)

整数:

类型含义
d十进制(默认)
b二进制
o八进制
x十六进制(小写字母)
X十六进制(大写字母)
n数字(同 d,但用本地化分隔)
c字符(将整数转成 Unicode 字符)
n = 255
print(f'{n:d}') # 255
print(f'{n:b}') # 11111111
print(f'{n:o}') # 377
print(f'{n:x}') # ff
print(f'{n:X}') # FF
print(f'{n:c}') # ÿ

浮点数:

类型含义
f / F定点表示法(Fnan/inf 输出为大写)
e / E科学计数法,底数 eE
g / G通用格式:根据值的大小自动选 fe,去掉尾部无意义的零
n数字(同 g,但用本地化分隔)
%百分比:将数值乘以 100 再以 f 格式显示,末尾带 %
pi = 3.141592653589793
big = 6.02214076e23
small = 1.602176634e-19
print(f'{pi:f}') # 3.141593 (默认6位)
print(f'{pi:e}') # 3.141593e+00
print(f'{pi:E}') # 3.141593E+00
print(f'{pi:g}') # 3.14159
print(f'{pi:G}') # 3.14159
print(f'{big:e}') # 6.022141e+23
print(f'{big:g}') # 6.02214e+23
print(f'{big:.2e}') # 6.02e+23
print(f'{small:e}') # 1.602177e-19
print(f'{small:g}') # 1.60218e-19
ratio = 0.25
print(f'{ratio:%}') # 25.000000%
print(f'{ratio:.0%}') # 25%
print(f'{ratio:.1%}') # 25.0%
# nan / inf
print(f'{float("inf"):f}') # inf
print(f'{float("inf"):F}') # INF
print(f'{float("nan"):f}') # nan
print(f'{float("nan"):F}') # NAN

10. 旁注:Rust 里的 ?(不是 Python 语法)#

Rust 的格式说明符结构长得和 Python 很像,但 <type> 部分多一个 Python 没有的类型码:?,对应 Debug trait —— 用来打印一个值的调试信息,结构化输出。这是 Rust 的语法,Python f-string 里没有 ? 这个类型码,写在这里只是为了避免两边语法混淆。

#[derive(Debug)]
struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: 4 };
println!("{:?}", p); // Point { x: 3, y: 4 }
println!("{:#?}", p); // 加 # 变成多行美化版:
// Point {
// x: 3,
// y: 4,
// }
let n: i32 = 255;
println!("{:x?}", n); // ff —— debug 输出里整数用小写十六进制
println!("{:X?}", n); // FF —— 大写十六进制
// ? 照样可以和 fill/align/width 组合
println!("{:*^15?}", n); // ******255******

关于 #? 的多行版本:加 # 不只是简单换成多行,缩进是按嵌套层级递增的,每深一层多缩进 4 个空格,嵌套的 struct、Vec 等都会展开:

#[derive(Debug)]
struct Address { city: String, zip: String }
#[derive(Debug)]
struct Person { name: String, age: u32, address: Address, tags: Vec<String> }
let p = Person {
name: "Alice".to_string(),
age: 30,
address: Address { city: "Tokyo".to_string(), zip: "100-0001".to_string() },
tags: vec!["admin".to_string(), "vip".to_string()],
};
println!("{:#?}", p);
// Person {
// name: "Alice",
// age: 30,
// address: Address {
// city: "Tokyo", ← 内层 struct 多缩进4格
// zip: "100-0001",
// },
// tags: [ ← Vec 也会被展开成多行
// "admin",
// "vip",
// ],
// }

对照 Python:没有 ? 类型码,最接近的等价物是 f-string 里的 !r 转换符(conversion),效果同样是”调用 repr()”:

class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Point(x={self.x}, y={self.y})'
p = Point(3, 4)
print(f'{p!r}') # Point(x=3, y=4)
print(f'{p!r:>20}') # !r 之后仍可以接 format spec

关键区别:Rust 的 ?type 字段的一部分,属于 format_spec 语法之内;Python 的 !r 是独立于 format_spec 的”转换符”,写在 ! 后面、: 前面,两者语法位置不一样,不能简单类比成”Python 也有个 ?”。

11. 动态 / 嵌套格式说明符#

widthprecision 都不必写死,可以用 {} 从变量里动态传入,实际写代码时很常用:

width, prec = 10, 3
val = 3.14159
print(f'{val:{width}.{prec}f}') # 3.142
print(f'{val:>{width}}') # 3.14159

这在需要根据数据动态对齐(比如表格列宽随内容变化)时特别方便,比写死一个固定宽度灵活得多。

12. 常用组合示例#

name, score = "Alice", 95.6789
# 左对齐 + 右对齐制表效果
print(f'{name:<10}{score:>8.2f}') # Alice 95.68
# 带符号 + 千位分隔 + 精度
print(f'{1234567.89123:+,.2f}') # +1,234,567.89
# 百分比 + 宽度 + 填充
print(f'{0.1234:*^10.1%}') # **12.3%***
# 居中 + 零填充(不能直接用 0,要显式写 fill+align)
print(f'{42:*^10}') # ****42****
# 整数十六进制 + 前缀 + 零填充
print(f'{255:#010x}') # 0x000000ff
# 字符串截断 + 填充
print(f'{"hello world":*>15.5s}') # **********hello

13. 自定义类型的 __format__#

任何对象都可以通过实现 __format__(self, format_spec) 来支持格式说明符:

class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __format__(self, spec):
if spec == 'polar':
import math
r = math.hypot(self.x, self.y)
theta = math.atan2(self.y, self.x)
return f'(r={r:.2f}, θ={theta:.2f})'
return f'({self.x}, {self.y})'
p = Point(3, 4)
print(f'{p}') # (3, 4)
print(f'{p:polar}') # (r=5.00, θ=0.93)

3. 转换符:!s / !r / !a#

写在表达式后面、冒号前面,决定取值时走哪个转换函数:

符号等价于含义
!sstr()默认行为,不写 ! 时就是这个
!rrepr()调试用的表示形式
!aascii()repr(),但把非 ASCII 字符转义
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f'({self.x}, {self.y})'
def __repr__(self):
return f'Point(x={self.x!r}, y={self.y!r})'
p = Point(3, 4)
print(f'{p!s}') # (3, 4) 走 __str__
print(f'{p!r}') # Point(x=3, y=4) 走 __repr__
print(f'{p}') # (3, 4) 不写 ! 时默认同 !s
s = "café"
print(f'{s!a}') # 'caf\xe9' 非 ASCII 字符被转义
print(f'{s}') # café 默认不转义

对照 Rust:Rust 没有”转换符”这种语法,而是靠两个独立的 trait 分工——Display(配合 {})和 Debug(配合 {:?},见另一篇笔记的旁注)。大致能对上 Python 这三个转换符里的两个:

Python语法Rust语法
str()!s(或不写,默认)Display trait{}
repr()!rDebug trait{:?}
ascii()!a没有对应的 trait

ascii() 是 Python 独有的,Rust 标准库没有内建等价物(想转义非 ASCII 字符得自己调用类似 str::escape_default() 这样的方法,不是靠 trait + 格式说明符)。

真正的差异不在”有几个”,而在是否自动兜底:Python 里任何对象都天生带 str()repr()object 基类给了默认实现,__str__ 没重写时甚至会自动退回用 __repr__

class OnlyRepr:
def __repr__(self):
return "OnlyRepr(x=5)"
# 没写 __str__
o = OnlyRepr()
print(str(o)) # OnlyRepr(x=5) 自动退回 __repr__,不报错
print(f'{o}') # OnlyRepr(x=5) 同样能用

怎么精确判断一个类有没有自定义 __str__object.__str__ 的默认实现就是”退回去调 __repr__”,所以直接拿类型的 __str__object.__str__is 比对——相等说明没自定义(str() 会走 __repr__),不等说明类自己重写了 __str__

a = None
print(type(a).__str__ is object.__str__) # True None 没自定义 __str__,str() 回退到 __repr__
class WithStr:
def __str__(self):
return "x"
print(type(WithStr()).__str__ is object.__str__) # False 自己定义了 __str__

Rust 里 DisplayDebug 是完全独立的两个 trait,互不兜底——只 #[derive(Debug)]、没实现 Display 的类型,用 {} 直接编译报错

#[derive(Debug)]
struct OnlyDebug { x: i32 }
let o = OnlyDebug { x: 5 };
println!("{:?}", o); // OnlyDebug { x: 5 } 没问题
println!("{}", o); // 编译错误:
// error[E0277]: `OnlyDebug` doesn't implement `std::fmt::Display`

也就是说 Python 的宽松是”总有兜底可用”,Rust 的严格是”没实现就不让你用”——这个差异比”三个 vs 两个”本身更值得记住。

4. 自文档化:=(Python 3.8+)#

在表达式后面加一个 =,会把”源码文本 = 值”一起打印出来,调试时很好用:

x = 42
y = 3.14159
print(f'{x=}') # x=42
print(f'{x + 1=}') # x + 1=43 源码文本原样保留,包括表达式本身
print(f'{y=:.2f}') # y=3.14 可以和 format spec 一起用
print(f'{x=!r}') # x=42 也可以和 !conversion 一起用

再展开讲两个容易忽略但很关键的细节:

① 不写 !conversion 时,= 默认走的是 repr(),不是 str() 这一点和不带 = 的普通插值刚好相反——普通 {x} 默认 !sstr()),但 {x=} 默认 !rrepr())。用那个 Point 类(__str____repr__ 故意写成不一样)就能看出区别:

p = Point(3, 4)
print(f'{p}') # (3, 4) ← 无=:走 str()
print(f'{p=}') # p=Point(x=3, y=4) ← 有=且不写!:走 repr(),不是 str()
print(f'{p!s}') # (3, 4) ← 对比:显式!s
print(f'{p!r}') # Point(x=3, y=4) ← 对比:显式!r,和上面的p=一致

之所以默认选 repr(),是因为这个特性本来就是给调试用的——repr() 通常携带更多信息(类名、字段名等),比面向用户展示的 str() 更适合排查问题。

= 前后的空白会被原样保留,因为它的实现方式是直接截取大括号里 = 前面那段源码文本,而不是重新把表达式格式化打印一遍:

x = 42
print(repr(f'{x=}')) # 'x=42' 无空白
print(repr(f'{x =}')) # 'x =42' = 前面的空格被保留
print(repr(f'{x= }')) # 'x= 42' = 后面的空格被保留(出现在值前面)
print(repr(f'{x = }')) # 'x = 42' 两边空格都保留
lst = [1, 2, 3]
print(repr(f'{sum(lst) + 1=}')) # 'sum(lst) + 1=7' 连表达式内部的空格也是原样照抄

这也意味着 f'{x=}'x 这段文字打印出来的不是”Python 重新序列化的表达式”,而是货真价实的原始源码字符——所以想让输出好看,得自己在写代码时把空格加在合适的位置。

注意 = 出现的位置在表达式之后、!conversion:format_spec 之前,对应整体结构。

5. 转义字面大括号:{{ }}#

想在输出里出现真正的 { } 字符,写两个:

x = 42
print(f'{{这是字面大括号}}{x} 是变量')
# {这是字面大括号} 而 42 是变量

6. 多行 / 三引号 f-string#

三引号形式的 f-string 可以跨行,用法和普通三引号字符串一样:

name = "小明"
x = 42
msg = f"""
你好, {name}!
今天是 {x} 号。
"""
print(msg)

7. Python 3.12+(PEP 701)解除的限制#

3.11 及更早版本里,f-string 表达式部分有三条限制:不能用和外层相同的引号、不能出现反斜杠、不能跨行/带注释(哪怕是三引号形式)。PEP 701 在 3.12 把这些限制全部取消了:

# 引号复用:3.11会直接 SyntaxError,3.12+ 没问题
songs = ['Take me back to Eden', 'Alkaline']
print(f"playlist: {", ".join(songs)}")
# playlist: Take me back to Eden, Alkaline
# 反斜杠:3.11 报错 "f-string expression portion cannot include a backslash"
items = ['a', 'b', 'c']
print(f'{"\n".join(items)}')
# a
# b
# c
# 表达式内可以带注释了(多行场景下)
x = 42
result = f'''{
x # 这是一个注释
+ 1
}'''
print(result) # 43

副作用是嵌套 f-string 现在可以随便嵌,不再需要每层换一种引号:

inner = "world"
print(f'{f"hello {inner}"}') # hello world
# 官方文档里那个夸张的例子:嵌套6层照样跑
print(f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"'') # 2

8. 例外:顶层裸冒号仍然要加括号#

3.12 放开了很多限制,但有一条没变:如果表达式顶层(不在任何 []/{}/() 内部)直接出现一个裸 :,会和”开始写 format_spec”的那个冒号混淆,所以还是得加括号。最典型的例子是 lambda

# 不加括号:报错
f'{lambda: 5}'
# SyntaxError: f-string: lambda expressions are not allowed without parentheses
# 加括号:没问题
print(f'{(lambda: 5)()}') # 5

补充一句:切片 lst[1:3] 和字典字面量 {"a": 1} 里的冒号不受影响,因为它们已经被 []/{} 包住,不算”顶层”,直接写就行,不用额外加括号。

9. f-string / .format() / %:怎么选#

三种写法效果相同,但 f-string 直接引用当前作用域的变量,可读性和执行效率都更好,新代码基本没有理由不用 f-string:

name, age = "Bob", 25
print(f'{name} is {age}') # f-string:变量直接写在字符串里
print('{} is {}'.format(name, age)) # .format():占位符 + 传参
print('%s is %d' % (name, age)) # % 格式化:C 风格,Python 里已经算过时写法

.format() 在需要模板字符串本身是变量、或者要复用同一份模板多次填值的场景里还有点用;% 格式化基本只在维护老代码时才会遇到。


三、and / or 逻辑运算技巧#

用 and 替代简单 if#

and 是短路求值:左边为假时不会计算右边。可以用来在条件为真时触发一个副作用(如 print):

5 > 10 and print("5 is greater than 10") # 左边 False,print 不执行
5 < 10 and print("5 is less than 10") # 左边 True,print 执行

用 or 提供默认值#

or 会返回第一个真值,如果所有值都是假值,则返回最后一个假值。常用于给变量设默认值,或判断某个条件是否不成立:

value = None
value = value or "default" # value 是 None(假值),取 "default"
print(value) # default

四、程序入口#

__name__ 是模块的内置变量,当文件被直接运行时其值为 "__main__";被其他文件 import 时则是模块名。用这个判断可以让代码既能直接执行,也能作为模块被导入而不触发主逻辑:

if __name__ == "__main__":
main()

五、从 PNG 生成 exe 图标#

Windows 的 .ico 文件本质是多分辨率图标的集合,sizes 里列出想要打包进去的每个尺寸,可以从一张高清 PNG 一次性生成完整的 icon group:

from PIL import Image
logo = Image.open('logo.png')
icon = Image.open('icon.png')
# 256、128 从 logo.png 生成
logo_sizes = [(256, 256), (128, 128)]
logo_frames = [logo.resize(s, Image.LANCZOS) for s in logo_sizes]
# 其余从 icon.png 生成
icon_sizes = [(64, 64), (32, 32), (16, 16)]
icon_frames = [icon.resize(s, Image.LANCZOS) for s in icon_sizes]
logo_frames[0].save(
'logo.ico',
format='ICO',
sizes=logo_sizes + icon_sizes,
append_images=logo_frames[1:] + icon_frames
)
Python 笔记
https://cialo.site/posts/coding/py-notes/
作者
洛璃
发布于
2026-07-24
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
洛璃
初春的离去,晚樱的谢幕
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
38
分类
12
标签
166
总字数
160,464
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.6
文章许可
CC BY-NC-SA 4.0
文章目录