Hexo

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

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 未被创建)

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()
1…328329330…352
San Ye

San Ye

Stay Hungry. Stay Foolish.

704 posts
53 tags
© 2026 San Ye
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4