Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Python——命名规范

Python命名规范(Name Convention)
Reference: https://blog.csdn.net/real_myth/article/details/68927665


命名:

module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.


应该避免的名称

  • 单字符名称, 除了计数器和迭代器.
  • 包/模块名中的连字符(-)
  • 双下划线开头并结尾的名称(Python保留, 例如__init__)

命名约定

  • 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
  • 用单下划线(_)开头表示模块变量或函数是protected的(使用import * from时不会包含).
  • 用双下划线(__)开头的实例变量或方法表示类内私有.
  • 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
  • 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.

引号

  • 自然语言使用双引号(想表达人为意思的,比如log,错误,提示等)
  • 机器标识使用单引号(比如dict的key等)
  • 正则表达式使用原生的双引号 r”…”
  • docstring使用三双引号

Python之父Guido推荐的规范

Type Public Internal
Modules lower_with_under _lower_with_under
Packages lower_with_under
Classes CapWords _CapWords
Exceptions CapWords
Functions lower_with_under() _lower_with_under()
Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER
gobal/Class Variables lower_with_under _lower_with_under
Instance Variables lower_with_under _lower_with_under (protected) or __lower_with_under (private)
Method Names lower_with_under() _lower_with_under() (protected) or __lower_with_under() (private)
Function/Method Parameters lower_with_under
Local Variables lower_with_under

Pyhton——defaultdict的用法


整体说明

  • defaultdict 是 Python 标准库 collections 模块中的一个工具,继承自普通字典 dict
  • defaultdict 的核心特点是:访问不存在的键时,会自动创建该键并赋予默认值
    • 无需手动判断 key in dict 或使用 dict.get(key, 默认值)
  • defaultdict 的核心是 “自动处理不存在的键” ,适合以下场景:
    • 统计计数(defaultdict(int))
    • 分组聚合(defaultdict(list)/set)
    • 避免频繁判断 key in dict 或 dict.get()

defaultdict 基本用法

  • 初始化:指定“默认值工厂函数”:defaultdict 的构造函数接收一个 可调用对象(工厂函数) ,当访问不存在的键时,会调用该函数生成默认值
  • 常见的工厂函数可以是:
    • 内置类型:int(默认 0)、list(默认空列表)、set(默认空集合)、str(默认空字符串)
    • 自定义函数:返回固定值或动态生成的值
  • 可以用于解决普通字典的痛点:访问不存在的键会抛出 KeyError
    1
    2
    3
    4
    5
    6
    7
    8
    # 普通字典:访问不存在的键报错
    d = {}
    print(d["foo"]) # KeyError: 'foo'

    # defaultdict:自动创建键并赋予默认值
    from collections import defaultdict
    dd = defaultdict(int) # 默认值为 0
    print(dd["foo"]) # 输出 0,且 dd 现在是 {'foo': 0}

defaultdict 使用示例

  • 统计元素出现次数(用 int 作工厂)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from collections import defaultdict

    words = ["apple", "banana", "apple", "orange", "banana", "apple"]
    count = defaultdict(int) # 键不存在时默认值为 0

    for word in words:
    count[word] += 1 # 无需判断键是否存在,直接累加

    print(dict(count)) # 转为普通字典:{'apple': 3, 'banana': 2, 'orange': 1}
  • 分组(用 list 作工厂):将元素按某个规则分组,值为列表(自动创建空列表,避免手动 append 时报错):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from collections import defaultdict

    students = [("Alice", "Math"), ("Bob", "English"), ("Alice", "Physics"), ("Bob", "Math")]
    group = defaultdict(list) # 键不存在时默认值为 []

    for name, subject in students:
    group[name].append(subject) # 直接向列表添加元素

    print(dict(group))
    # 输出:{'Alice': ['Math', 'Physics'], 'Bob': ['English', 'Math']}
  • 去重分组(用 set 作工厂):值为集合 set,自动去重:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from collections import defaultdict

    data = [("a", 1), ("b", 2), ("a", 1), ("b", 3)]
    group = defaultdict(set) # 键不存在时默认值为 set()

    for key, val in data:
    group[key].add(val) # 集合自动去重

    print(dict(group)) # 输出:{'a': {1}, 'b': {2, 3}}
  • 自定义默认值(用自定义函数作工厂):如果需要非内置的默认值(如 None、固定字符串、动态值),可传入自定义函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from collections import defaultdict

    # 自定义工厂函数:返回固定值 "unknown"
    def default_val():
    return "unknown"

    dd = defaultdict(default_val)
    print(dd["foo"]) # 输出 "unknown",dd 变为 {'foo': 'unknown'}

    # 简化:用 lambda 表达式(适合简单默认值)
    dd2 = defaultdict(lambda: None) # 默认值为 None
    print(dd2["bar"]) # 输出 None

    dd3 = defaultdict(lambda: [1, 2, 3]) # 默认值为 [1,2,3]
    print(dd3["baz"]) # 输出 [1,2,3]

特别注意 dict.fromkeys()

  • dict.fromkeys() 也能设置默认值,但会为所有键共享同一个可变对象(如列表),容易踩坑:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 普通 dict.fromkeys() 的坑:所有键共享同一个列表
    d = dict.fromkeys(["a", "b"], [])
    d["a"].append(1)
    print(d) # {'a': [1], 'b': [1]}(意外修改了 b 的值)

    # defaultdict 无此问题:每个键的列表是独立的
    dd = defaultdict(list)
    dd["a"].append(1)
    print(dd) # {'a': [1]}(b 未被创建)

Pyhton——原生random模块的使用


整体说明

  • 随机种子(seed)是初始化随机数生成器的状态,相同种子产生完全相同的随机序列(可复现性)
    • 不设置种子时,使用系统时间(time.time())作为种子,每次运行结果不同
    • 种子可以是整数、字符串、字节等(内部会转换为整数)
  • 随机状态(state)本质是生成器的内部状态(包含所有必要信息,确保后续随机数可预测)
    • random.getstate():保存当前状态(返回一个元组)
    • random.setstate(state):恢复到之前保存的状态
  • 全局 random 模块在所有地方共享一个状态,不是线程安全的(多线程会竞争状态)
    • 建议多使用 局部 random.Random 实例而不是全局 random 模块

random 模块示例

  • 以下是一个原生的 Python random 模块使用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    import random

    print("=" * 50)
    print("随机种子的可复现性演示")
    print("=" * 50)

    print("\n【不设置种子(全局随机)】")
    random.seed() # 重置为默认状态(系统时间种子)
    print(f"第一次随机整数序列: {[random.randint(1, 100) for _ in range(3)]}")
    print(f"第二次随机整数序列: {[random.randint(1, 100) for _ in range(3)]}")

    print("\n【设置固定种子】")
    random.seed(42) # 固定种子,注意 seed 是一个函数,不要写成 random.seed = 42,这样会导致 seed 失效
    print(f"种子42-序列: {[random.randint(1, 100) for _ in range(3)]}") # 结果为固定值,跨平台跨进程可复现

    random.seed(42) # 固定种子,重新设置相同种子,会重置随机序列到初始状态
    print(f"种子42-序列: {[random.randint(1, 100) for _ in range(3)]}") # 结果复现上面的结果

    print("\n" + "=" * 50)
    print("随机状态的保存与恢复")
    print("=" * 50)

    random.seed(100) # 先设置种子,确保初始状态一致
    print(f"\n初始状态-随机数序列: {[random.randint(1, 100) for _ in range(3)]}")

    state = random.getstate() # 保存当前随机状态(返回一个不可变对象,包含完整的随机生成器状态)
    print(f"状态改变后-随机数序列: {[random.randint(1, 100) for _ in range(3)]}") # 继续生成几个随机数

    random.setstate(state)# 恢复之前保存的状态,从当时状态开始继续除数,就像是 seed(100) 后已经输出过多个 随机数一样
    print(f"恢复状态后-随机数序列: {[random.randint(1, 100) for _ in range(3)]}") # 继续生成几个随机数,得到的结论是跟上面一致的

    print("\n" + "=" * 50)
    print("全局随机 vs 局部随机实例")
    print("=" * 50)

    # 全局 random:所有模块共享同一个状态
    print("\n【全局随机(共享状态)】")
    random.seed(200)
    print(f"全局-随机数1: {random.uniform(0, 10)}")
    print(f"全局-随机数2: {random.uniform(0, 10)}")

    # 局部 Random 实例:独立状态,不影响全局
    local_rand1 = random.Random(300) # 局部实例1,种子300
    local_rand2 = random.Random(300) # 局部实例2,种子300
    local_rand3 = random.Random(400) # 局部实例2,种子400

    print("\n【局部随机实例(独立状态)】")
    print(f"局部1-随机数(seed=300): {local_rand1.randint(1, 10)}")
    print(f"局部2-随机数(seed=300): {local_rand2.randint(1, 10)}")
    print(f"局部3-随机数(seed=400): {local_rand2.randint(1, 10)}")

    # ==================================================
    # 随机种子的可复现性演示
    # ==================================================
    #
    # 【不设置种子(全局随机)】
    # 第一次随机整数序列: [100, 29, 44]
    # 第二次随机整数序列: [16, 34, 5]
    #
    # 【设置固定种子】
    # 种子42-序列: [82, 15, 4]
    # 种子42-序列: [82, 15, 4]
    #
    # ==================================================
    # 随机状态的保存与恢复
    # ==================================================
    #
    # 初始状态-随机数序列: [19, 59, 59]
    # 状态改变后-随机数序列: [99, 23, 91]
    # 恢复状态后-随机数序列: [99, 23, 91]
    #
    # ==================================================
    # 全局随机 vs 局部随机实例
    # ==================================================
    #
    # 【全局随机(共享状态)】
    # 全局-随机数1: 0.4560930208539393
    # 全局-随机数2: 2.0344697486239927
    #
    # 【局部随机实例(独立状态)】
    # 局部1-随机数(seed=300): 10
    # 局部2-随机数(seed=300): 10
    # 局部3-随机数(seed=400): 6

附录:特别说明

  • random.seed 是一个函数而不是一个数字或字符串,需要使用 random.seed(1) 而不是 random.seed=1 来设置种子
    • 这里容易理解错误
  • 所有设置了固定种子的部分(如 seed=42、local_rand1 = random.Random(300)),每次运行结果完全一致
  • 全局随机在未设置种子时,每次运行结果不同;但恢复状态后,会延续之前的序列
  • 局部实例的操作不会影响全局或其他局部实例的状态,实现了完全隔离
  • random.shuffle(input) 是个 in-place 修改方法,会直接修改 input 对象的值

附录:random 隔离实用技巧

  • 临时固定状态:保存原始状态 -> 设置临时种子 -> 执行操作 -> 恢复原始状态(避免污染全局)
  • 独立随机流:为不同任务/模块分配独立种子(比如 task_seed = base_seed + task_id),确保并行任务结果可复现
  • 局部状态持久化:保存局部实例的状态(如 local_rand.getstate()),后续可在其他地方恢复继续使用
  • 禁止全局污染:封装局部实例,强制代码使用独立随机生成器(避免误改全局状态)

Pyhton——有限迭代器转无限迭代器


整体说明

  • 本文记录一个非常有用的特别函数,可用于将有限的迭代器包装变成无限的迭代器(不停重复)

Python 生成器回顾

  • 斐波那契数列生成器定义和使用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 以后调用这个函数不能再简单的理解为函数调用,要理解为创建生成器
    def fibonacci_generator(n):
    a, b = 0, 1
    count = 0
    while count < n:
    yield a # 返回当前值,并暂停函数执行
    a, b = b, a + b
    count += 1

    # 使用生成器
    fib_gen = fibonacci_generator(10) # 创建生成器对象
    for num in fib_gen: # for 循环访问 fib_gen即可,不需要再访问原始函数,原始函数调用一次足以
    print(num, end=" ")
    print("\n")

    # 0 1 1 2 3 5 8 13 21 34

代码外简单解释

  • 下面的程序定义了一个函数 infinite_iterator,可以对任意有限迭代器(如列表)无限次循环遍历
  • 每遍历一轮,先输出 “this”,再依次输出迭代器的所有元素
  • 整个过程是无限循环,除非手动终止
  • 代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from itertools import repeat

    def infinite_iterator(finite_iterator):
    """
    实现对有限迭代器的无限循环遍历
    每次循环开始时,先打印 "this",然后依次输出迭代器的所有元素

    参数:
    finite_iterator: 任意可迭代对象(如列表、元组等)

    实现原理:
    1. 使用 itertools.repeat(finite_iterator) 实现对同一个迭代器对象的无限重复
    2. 外层 for 循环每次进入时,先打印 "this"
    3. 使用 yield from finite_iterator,将 finite_iterator 的所有元素依次生成,finite_iterator 被访问完以后,才会进入下一个 yield(即下个 for 迭代)
    4. 这样,每次完整遍历 finite_iterator 后,又会重新开始新一轮遍历,整个过程无限循环
    注意:
    如果 finite_iterator 是可迭代但不可重复遍历的(如生成器),第二轮开始时将不再有元素
    """
    for _ in repeat(finite_iterator): # 无限循环
    print("this") # 每轮遍历前打印
    yield from finite_iterator # 依次输出所有元素

    # 创建生成器,循环遍历 [1, 2, 4]
    iter_x = infinite_iterator([1, 2, 4])

    # 无限输出 "this, 1, 2, 4, this, 1, 2, 4, this, ...",直到手动终止
    for i in iter_x:
    print(i)

Python——contextlib包的使用


整体说明

  • contextlib 模块是 Python 标准库的一部分,提供了一系列简化上下文管理器实现的实用工具

@contextmanager 装饰器

  • @contextmanager 允许将生成器函数转换为上下文管理器,无需显式定义类

  • @contextmanager 核心逻辑通过 yield 分割为“进入上下文”和“退出上下文”两部分

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from contextlib import contextmanager

    @contextmanager
    def file_manager(file_path, mode):
    file = open(file_path, mode)
    yield file
    file.close()

    with file_manager('test.txt', 'w') as f:
    f.write('Hello, contextlib!')
    • 在这个例子中,yield 之前的代码 file = open(file_path, mode) 用于获取资源,yield 之后的代码 file.close() 用于释放资源
    • 注:yield 之后的语句在第二次调用函数时被调用(第一次调用函数返回 yield 的结果)

closing类

  • 对于只提供 close() 方法但未实现上下文协议的对象,closing 可确保其 close() 被调用

    1
    2
    3
    4
    5
    from contextlib import closing
    import urllib.request

    with closing(urllib.request.urlopen('https://www.example.com')) as response:
    html = response.read()
    • 上述代码等价于手动使用try-finally块来调用close()方法,但closing类使代码更加简洁

suppress上下文管理器

  • 在需要忽略某些非关键异常时,suppress可替代繁琐的try-except

    1
    2
    3
    4
    5
    6
    from contextlib import suppress

    with suppress(FileNotFoundError):
    with open('nonexistent_file.txt', 'r') as f:
    content = f.read()
    print("程序继续执行,不抛出异常")
  • suppress还可以同时抑制多个异常,如with suppress(FileNotFoundError, PermissionError):


nullcontext上下文管理器

  • 当代码需要根据条件决定是否使用上下文管理器时,nullcontext提供“空上下文”,避免重复代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from contextlib import nullcontext

    def process_data(data, use_file=True):
    context = open('output.txt', 'w') if use_file else nullcontext()
    with context as f:
    if f:
    f.write(str(data))
    else:
    print(f"直接输出:{data}")

    process_data("临时数据", use_file=False)
    process_data("重要数据", use_file=True)

ExitStack类

  • contextlib 支持通过 with 语句嵌套或使用 ExitStack 批量管理多个上下文,尤其适合动态生成的资源列表

    1
    2
    3
    4
    5
    6
    7
    from contextlib import ExitStack

    file_names = ('file1.txt', 'file2.txt', 'file3.txt')
    with ExitStack() as stack:
    files = [stack.enter_context(open(name, 'w')) for name in file_names]
    for f in files:
    f.write('示例内容')
    • 在这个例子中,ExitStack 会自动管理多个文件的上下文,确保在退出 with 块时所有文件都被正确关闭。

Python——np.nan, None的判断和比较

Python值的判断与比较: np.nan, None


None

1
2
3
4
5
6
7
8
type(None)
# Output: <type 'NoneType'>

None is None
# Output: True

None == None
# Output: True

np.nan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type(np.nan)
# Output: <type 'float'>

np.nan == np.nan
# Output: False

np.nan is np.nan
# Output: True

np.nan != np.nan
# Output: True

np.nan > np.nan
# Output: False

np.nan < np.nan
# Output: False

np.nan 与 None

1
2
3
4
5
6
7
8
None == np.nan
# Output: False

None != np.nan
# Output: True

None is np.nan
# Output: False

与数字的比较

1
2
3
4
5
6
7
8
np.nan > 10
# Output: False

np.nan < 10
# Output: False

np.nan == 10
# Output: False

总结

  • 【Python2和Python3表现相同】
    np.nan 只有在np.nan != np.nan或者np.nan is np.nan时为True , 其他情况下和数字比较(包括和自身)都为False
  • 【Python2和Python3表现相异】
    Python3与Python2在直接使用np.nan时表现正常,但是当涉及到DataFrame的NaN时表现不同

特殊情况

DataFrame中的NaN与数字比较时会出现有时候为True有时候为False的情况

  • 这种情况出现在Python3中,当NaN 与数字比较时
    • 此时对于列属性类型为数值型,那么返回False
    • 否则返回True
  • Python2中NaN和数字的就是np.nan和数字比较的结果,都为False

    Python2与Python3比较

  • 代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Python3:
import pandas as pd
df = pd.DataFrame([['a',2,3], ['a',3,4], ['a',8,9]], index=['a', 'b', 'c'])
df = df.reindex(['a', 'b', 'c', 'd', 'e', 'f'])
print(df)
# Output:
0 1 2
a a 2.0 3.0
b a 3.0 4.0
c a 8.0 9.0
d NaN NaN NaN
e NaN NaN NaN
f NaN NaN NaN

print(df > 5)
# Output:
0 1 2
a True False False
b True False False
c True True True
d True False False
e True False False
f True False False

print(df.values)
# Output:
[['a' 2.0 3.0]
['a' 3.0 4.0]
['a' 8.0 9.0]
[nan nan nan]
[nan nan nan]
[nan nan nan]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Python2:
import pandas as pd
df = pd.DataFrame([['a',2,3], ['a',3,4], ['a',8,9]], index=['a', 'b', 'c'])
df = df.reindex(['a', 'b', 'c', 'd', 'e', 'f'])
print(df)
# Output:
0 1 2
a a 2.0 3.0
b a 3.0 4.0
c a 8.0 9.0
d NaN NaN NaN
e NaN NaN NaN
f NaN NaN NaN

print(df > 5)
# Output:
0 1 2
a True False False
b True False False
c True True True
d False False False
e False False False
f False False False

print(df.values)
# Output:
[['a' 2.0 3.0]
['a' 3.0 4.0]
['a' 8.0 9.0]
[nan nan nan]
[nan nan nan]
[nan nan nan]]

Python——@dataclass注解的使用


整体说明

  • @dataclass 是 Python 3.7 引入的一个装饰器,属于 dataclasses 模块,用于简化类的定义
  • @dataclass 在初始化对象后,还会自动生成常用方法如 __init__、__repr__、__eq__ 等

@dataclass 用法示例

  • 综合示例:汇总 @dataclass 各种用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    from dataclasses import dataclass, field
    from typing import Union, Dict, List, Callable, Optional
    from easydict import EasyDict as edict

    # 下面的语句中的参数是可选的,常常用 @dataclass 即可
    @dataclass(
    init=True, # 自动生成 __init__
    repr=True, # 自动生成 __repr__
    eq=True, # 自动生成 __eq__
    order=True, # 自动生成比较方法(<、<=、>、>=)
    unsafe_hash=False, # 不自动生成 __hash__
    frozen=False # 允许属性修改, 若设置为 True 则不允许修改属性,报错dataclasses.FrozenInstanceError: cannot assign to field 'xx'
    )
    class DemoDataClass:
    # 类型注解 + 必须参数(@dataclass注解过的类,字段的类型注解是必须需要的)
    # 字段的类型注解仅做类型检查(如 mypy、Pyright)看的,实际上仍然可以赋值为任意类型
    name: str

    # 类型注解 + 默认值
    age: int = 18

    # 可能是 字典{str:edict} 或 列表[str],默认值取 None
    tools: Union[Dict[str, edict], List[str]] = None

    # 可以是 int,也可以是 None
    children: Optional[int] = None

    # 类型注解 + 默认工厂函数
    tags: list = field(default_factory=lambda: ["python", "dataclass"])

    # 不参与 __init__,表示该字段不会作为参数传递给构造函数,无法通过参数传入
    # # 后续可在 __post_init__ 函数中自己定义或修改
    # # 字段仍然是对象属性,可以被访问和修改,且仍然可以被 __repr__、__eq__ 等方法使用(除非通过 repr=False 或 compare=False 排除)
    # # 通常用于那些需要在对象创建后由其他逻辑赋值的字段,比如自增主键、计算得出的值
    id: int = field(init=False, default=0)

    # 排除字段(不会参与比较和展示)
    secret: str = field(default="hidden", repr=False, compare=False)

    # 排除字段(不会参与比较和展示)
    temp_data: dict = field(default_factory=dict, repr=False, compare=False)

    # 函数字段定义
    print_fn: Callable = lambda x: print(x)

    # 用函数的方式定义字段,实际访问时必须按照字段语法访问
    @property
    def age_name(self):
    return "%s_%s" % (self.age, self.name) # 可使用类的字段访问

    # 自定义初始化逻辑,在自动生成的 __init__ 函数执行后会被调用
    def __post_init__(self):
    self.id = hash(self.name + str(self.age)) # 仅初始化一次,以后再修改 name 或 age 不会引起 self.id 的变化(除非显示调用.__post_init__())

    # 使用示例
    demo1 = DemoDataClass(name="Alice")
    # demo1 = DemoDataClass(name="Alice", id=100) # 报错:TypeError: DemoDataClass.__init__() got an unexpected keyword argument 'id'
    demo2 = DemoDataClass(name="Bob", age=25, tags=["dev"], temp_data={"x": 1})

    print(demo1) # DemoDataClass(name='Alice', age=18, tools=None, children=None, tags=['python', 'dataclass'], id=1490295676558033675, print_fn=<function DemoDataClass.<lambda> at 0x10e504a60>)
    print(demo2) # DemoDataClass(name='Bob', age=25, tools=None, children=None, tags=['dev'], id=6412555795401615714, print_fn=<function DemoDataClass.<lambda> at 0x10e504a60>)
    print(demo1 == demo2) # False;比较结果
    print(demo1.tags) # ['python', 'dataclass'];默认工厂
    print(demo1.secret) # hidden
    print(demo1.age_name) # 18_Alice;须按照字段方式读取
    # print(demo1.age_name()) # 报错 TypeError: 'str' object is not callable
    demo1.tools = 100 # 请记住:字段类型仅作为声明检查,并不会直接报错
    print(demo1) # DemoDataClass(name='Alice', age=18, tools=100, children=None, tags=['python', 'dataclass'], id=1490295676558033675, print_fn=<function DemoDataClass.<lambda> at 0x10e504a60>)
    demo1.print_fn("print me") # print me
  • 示例说明

    • 类型注解:所有字段都需要类型注解,@dataclass 会自动识别,没有给出类型的字段会报错
    • 默认值:通过直接赋值或 field(default=...) 指定
    • 默认工厂:如 default_factory 用于可变类型(如列表、字典)
    • init=False:字段不参与初始化,只在类内部赋值,__init__ 内不可读
    • repr=False, compare=False:字段不参与对象展示和比较
    • __post_init__:用于自定义初始化逻辑
    • order=True:自动生成排序方法
    • frozen=True:设置后对象不可变(本例未启用)

附录:在 dataclass 中使用 field 和不使用 field 的区别

  • 使用 field 的字段
    • 通过 field() 传递更多参数来控制字段行为
    • 可以精细控制字段的初始化、展示、比较、哈希、默认值生成等行为,适合复杂需求
    • 常用参数包括
      • default:指定默认值(同直接赋值)
      • default_factory:指定一个工厂函数,用于生成默认值(常用于可变类型,如列表、字典)
      • init:是否包含在 __init__ 方法参数中(True/False)
      • repr:是否包含在 __repr__ 方法中(True/False)
      • compare:是否用于比较(__eq__、__lt__ 等)
      • hash:是否用于哈希
      • metadata:可存储任意元数据
  • 不使用 field 的字段
    • 直接用类型注解和可选的默认值定义字段
    • 只能简单指定类型和默认值,适合简单场景
    • 默认动作:
      • 默认会参与 __init__ 方法(即可以通过构造函数传参)
      • 默认会参与 __repr__、__eq__、__hash__ 等自动生成的方法
      • 默认不可用 default_factory,只能用普通的默认值(如字符串、数字、布尔等不可变类型)
  • TLDR:
    • 简单字段可直接定义,不用 field
    • 需要更细致控制(如可变类型、参与方法、元数据等)时,应使用 field()

Python——位运算与逻辑运算和C++有什么不同

参考链接: https://blog.csdn.net/weixin_39129504/article/details/85958295


位运算(与C++相同)

按位与 &

按位或 |

按位异或 ^

按位取反 ~

移位运算 >>,<<,<<=和>>=


逻辑运算(与C++不同)


逻辑与

  • Python: and
  • C++: &&

逻辑或

  • Python: or
  • C++: ||

逻辑非

  • Python: not
  • C++: !

Python——函数返回值是copy还是引用

Python中有些函数是直接操作当前对象的,有些函数是操作副本的


操作当前对象的

  • list.sort(), 返回空
  • random.shuffle(my_list), 返回空
  • func(inplace=True), 这里Pandas库中的其他方法几乎均适用

返回copy的

  • 除了numpy的reshape()外目前默认都为
  • sorted(), 内置函数,返回新对象,不论接受什么参数返回的都是列表
  • np.ndarray.reshape() 返回新对象,但是新对象除了shape属性外,数据属性是和原始对象共享的
    • np.ndarray存储着数据和一个shape属性
    • 我们可通过修改shape属性而不是创建新对象来修改当前对象的shape object.shape = 3,4
    • 使用reshape时可以理解为创建了一个新对象,但是共享了数据,两个ndarray对象有相同的数据引用
    • numpy包没有array类,只有ndarray类,array是一个函数,用于构造ndarray,也可以用ndarray函数构建,但是不推荐,测试ndarray函数发现用法很奇怪

Python——使用pip管理包

使用 pip 管理 Python 包的常见用法如下:


管理包(安装、升级、卸载和查看等)

  • 安装最新版本:

    1
    pip install package_name
  • 安装指定版本:

    1
    pip install package_name==1.2.3
  • 从本地文件安装:

    1
    pip install /path/to/package.whl
  • 从 GitHub 安装:

    1
    pip install git+https://github.com/user/repo.git
  • 升级到最新版本:

    1
    pip install --upgrade package_name
  • 卸载包:

    1
    pip uninstall package_name
  • 查看某个包的详细信息:

    1
    pip show package_name
  • 列出所有已安装的包:

    1
    pip list

搜索包

  • 搜索包:

    1
    pip search package_name
    • 这个命令已经弃用,现在需要访问官网才行 https://pypi.org/
  • 搜索包版本:

    1
    pip index versions package_name

检查包更新

  • 检查哪些包需要更新:
    1
    pip list --outdated

配置镜像源

  • 临时使用镜像源:

    1
    pip install package_name -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 永久配置镜像源:

    1
    pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

其他常用命令

  • 查看帮助:

    1
    pip --help
  • 查看版本:

    1
    pip --version

用pip批量导出或安装环境配置

  • 可以使用 pip freeze 命令导出当前 Python 环境的配置

  • 下面的pip freeze 命令将在当前目录下创建一个名为 requirements.txt 的文件,并写入当前环境中已安装的所有包及其版本信息 :

    1
    pip freeze > requirements.txt
  • 下面的命令可以在任何地方根据 requirements.txt 中列出的包及其版本信息重新安装所有必需的包,从而重建相同的环境(其中-r参数用于指定依赖性的文件名):

    1
    pip install -r requirements.txt

从源码安装包

  • 以通过源码安装transformers为例子
  • clone仓库 :git clone https://github.com/huggingface/transformers.git
  • 进入目录 :cd transformers
  • 安装依赖(准备项) :确保你的Python环境中已经安装了setuptools等必要的依赖。如果没有安装,可以先使用pip install setuptools进行安装
  • 安装(推荐方式) :pip install .
    • 注意:这种方式会将transformers安装到你的Python环境中,但如果后续你对源码进行了修改,需要重新执行安装命令才能使修改生效
    • 其他安装方式(可编辑安装) :pip install -e .命令,该命令会将克隆的仓库链接到你的Python库路径,这样Python不仅会在正常的库路径中搜索库,也会在你克隆到的文件夹中进行查找,方便修改后生效
  • 安装完成后,对于还未发行的版本,版本名称可能是带有后缀.dev等的(依赖可能识别不了)

附录:安装带 -e 的含义

  • 是否使用 -e 在安装方式和后续开发行为上有本质区别
  • 具体来说 pip install . 和 pip install -e . 都是用来安装当前目录下的 Python 包的命令,但安装本质不同

pip install . (标准安装)

  • 将当前目录(. 代表当前目录)下的 Python 包进行复制安装(静态)
  • 安装过程 :
    • 1)pip 会查找当前目录中的 setup.py 或 pyproject.toml 文件
    • 2)根据配置,将包的源代码复制到 Python 环境的 site-packages 目录下
    • 3)同时安装包的依赖项
  • 安装后,你在项目源码中做的任何修改,不会自动反映到已安装的包中,即不可编辑
    • 如果你修改了源代码,必须重新运行 pip install . 来更新已安装的包,才能让改动生效
  • 当你准备发布一个稳定版本时,通常使用这种方式

pip install -e . (可编辑安装 / 开发安装)

  • -e 是 --editable 的缩写,进行的是可编辑安装或开发安装(动态链接)
  • 安装过程 :
    • 1)pip 同样查找 setup.py 或 pyproject.toml
    • 2)它不会复制源代码,而是在 site-packages 目录下创建一个指向你当前项目目录的符号链接(symlink) 或特殊的 .pth 文件
    • 3)这样,Python 解释器在导入这个包时,会直接从你的项目源码目录加载模块
  • 你在项目源码中做的任何修改,立即生效 ,不需要重新安装,即是可编辑的
    • 非常适合在开发过程中使用,可以快速测试代码变更
  • 包的依赖项仍然会被安装到环境中
  • 包的“安装位置”就是开发目录,删除开发目录会导致包“消失”

附录:安装包的用法

常规用法

  • 使用普通命令足以

    1
    pip install package==1.1.0
  • 若已经安装有包(不管是升级还是降级均可),想要强制重新安装,使用:

    1
    pip install package==1.1.0 --force-reinstall
  • 已经有包的情况下,也可以使用:

    1
    2
    pip3 uninstall package
    pip3 install package==1.1.0

推荐用法

  • 升级包时使用 --upgrade 参数,确保升级操作能够执行

    1
    pip install --upgrade package==1.1.0
    • 若没有 --upgrade 参数可能会默认不安装,因为认为当前包已经符合版本(高于目标版本)
  • 降级包时加一个强制参数

    1
    pip install package==1.1.0 --force-reinstall
1…363738…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4