cover

走马

陈粒

1. 类的创建#

# 普通创建
# 常规方式创建数据类
class Student:
# 手动写__init__,支持默认值
def __init__(self, name: str, age: int = 18, score: float = 0.0):
self.name = name
self.age = age
self.score = score
# 手动写__repr__(打印时友好显示)
def __repr__(self):
return f"Student(name='{self.name}', age={self.age}, score={self.score})"
# 手动写__eq__(支持相等判断)
def __eq__(self, other):
if not isinstance(other, Student):
return False
return (self.name == other.name and
self.age == other.age and
self.score == other.score)
# 使用
s1 = Student("小明", 18, 90.5)
s2 = Student("小明", 18, 90.5)
print(s1) # Student(name='小明', age=18, score=90.5)
print(s1 == s2) # True
# 使用dataclass创建数据类
from dataclasses import dataclass
# 仅需定义属性和类型提示,自动生成__init__、__repr__、__eq__等
@dataclass
class Student:
name: str
age: int = 18 # 支持默认值
score: float = 0.0
# 使用(效果和上面完全一致)
s1 = Student("小明", 18, 90.5)
s2 = Student("小明", 18, 90.5)
print(s1) # Student(name='小明', age=18, score=90.5)
print(s1 == s2) # True
# @dataclass的常用参数
# @dataclass(frozen=True) # 冻结属性,不可修改
# @dataclass(order=True) # 支持排序
# @dataclass(kw_only=True) # 仅支持关键字参数
@dataclass(kw_only=True)
class Person:
name: str
age: int = 20
city: str = "Beijing"
# ✅ 正确:使用关键字参数
p1 = Person(name="Alice", age=25, city="Shanghai")
p2 = Person(name="Bob", age=30)
# ❌ 错误:不能使用位置参数
# p3 = Person("Charlie", 35, "Guangzhou") # 会报错:TypeError

2 .类中属性注解#

2.1 可变与不可变对象#

  1. 可变对象:列表、字典、集合、对象等,这些对象的值可以被修改
  2. 不可变对象:整型、浮点型、布尔型、字符串、元组、 frozenset等,这些对象的值一旦创建就不能被修改
# 不可变对象
a: int = 1
print(id(a))
a = 2
print(id(a)) # 地址发生变化,也就是说赋值给新的变量,对象地址发生了变化
# 可变对象
b: list = [1, 2, 3]
print(id(b))
b.append(4)
print(id(b)) # 地址没有发生变化,只是对象的值发生了变化
b = [1, 2, 3, 4] # 重新赋值,对象地址发生了变化
print(id(b))

2.1.2 类型注解#

1. 语法:#
  1. field和default_factory 当我们想要给dataclass的属性一个默认值时,可以直接赋值。但是,如果默认值是可变的(比如列表、字典、numpy数组等),我们不能直接赋值,因为这样会导致所有实例共享同一个可变对象。 这时我们就需要使用field和default_factory。 default_factory是一个函数,它返回一个默认值。每次创建实例时,都会调用这个函数来生成一个新的对象。 例如,对于列表,我们通常写成:my_list: list = field(default_factory=list) 对于数字、字符串等不可变对象,可以直接赋值。
  2. Optional和Union Optional表示这个类型可以是某个类型或者None。例如,x: Optional[int] 表示x可以是int或者None。 Union表示这个类型可以是多个类型中的一个。例如,x: Union[int, str] 表示x可以是int或者str。 在Python 3.10+中,可以使用 | 运算符代替Union,例如 int | str。
  3. Callable Callable表示一个可调用对象,比如函数。你可以指定参数和返回值的类型,例如 Callable[[int, int], int] 表示接受两个整数参数并返回一个整数的函数。 在dataclass中,如果你需要存储一个函数,可以使用Callable。
2 快速决策指南#
  1. 这个字段必须有值吗?
  • 是 → 直接定义类型,不要Optional
  • 否 → 用Optional[类型] = None
  1. 这个字段的默认值是可变对象吗?
  • 是 → 用field(default_factory=…)
  • 否 → 直接赋值默认值
  1. 这个字段可以是多种类型之一吗?
  • 是 → 用Union[类型1, 类型2] 或 类型1 | 类型2
  • 否 → 用单一类型
  1. 这个字段需要存储函数吗?
  • 是 → 用Callable
  • 否 → 不用
综合示例:#
from typing import List, Optional, Union, Callable
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int = 20
city: str = "Beijing"
# 列表默认值是一个空列表
hobbies: List[str] = field(default_factory=list)
# 数字默认值是0
score: int = 0
# 字符串默认值是空字符串
address: str = ""
# 这个字段可以是int、float、str、bool、None中的任意类型,默认值是None
optional_field: Optional[Union[int, float, str, bool]] = None
# 这个字段可以是一个函数, 默认值是一个匿名函数,参数类型是str和int,返回值类型是str
read_book: Callable[[str,int], str] = lambda title, pages: f"Reading {title} for {pages} pages."
# 实例化
p1 = Person("Alice", 25, "Shanghai", hobbies=["reading", "swimming"], score=90, address="123 Main St")
p2 = Person("Bob", 30, hobbies=["running", "hiking"], optional_field=True)

3. 类中方法#

3.1. 创建方法:#

class Student:
def __init__(self, name: str, age: int = 18, score: float = 0.0):
self.name = name
self.age = age
self.score = score
def say_hello(self):
print(f"Hello, my name is {self.name}!")
def say_goodbye(self):
print(f"Goodbye, {self.name}!")
# 使用
s1 = Student("小明", 18, 90.5)
s1.say_hello() # Hello, my name is 小明!
s1.say_goodbye() # Goodbye, 小明!

3.2. 类属性与方法的命名规则:#

3.2.1 以单个下划线开头:#
  • 含义:表示”这是一个受保护的(protected)成员”,提示其他程序员”这个变量/方法主要供内部使用”。
  • 特点: Python 不会阻止你访问它,这只是一种约定 from module import * 不会导入以单下划线开头的名称 在交互式环境中,单下划线变量通常不显示在dir()输出中(某些IDE会过滤) 2.2 以双下划线开头和结尾:
  • 含义:特殊方法(魔术方法),它们不是由你直接调用的,而是在特定场景下由 Python 解释器自动触发执行
  • 特点: 不应该自己发明这样的名称 这些是Python语言的核心方法 通常用于操作符重载、上下文管理等

4. 魔术方法#

  1. 构造与初始化相关的魔术方法:

这类方法用于控制对象的创建、初始化和销毁过程。 |方法 | 触发时机 | 作用| | --- | --- | ---| |new | 创建实例时(比__init__早) | 控制实例的创建过程(如单例)| |init | 实例创建后初始化时 | 给实例赋值初始属性| |del | 实例被垃圾回收时 | 释放资源(不建议使用)|

  1. 字符串表示相关(提升可读性)的魔术方法:

这类方法控制对象被打印 / 展示时的格式,让输出更易读。 |方法 | 触发时机 | 作用 | --- | --- | --- str | 调用print()时 | 返回对象的字符串表示 repr | 调用repr()时 | 返回对象的可读字符串表示 strrepr 的区别:

  • str 一般用于用户友好输出,repr 一般用于开发者调试输出
  • str 应该返回简短、易读的字符串,repr 应该返回完整、准确的字符串 关键规则:如果只定义一个,优先实现__repr__(因为__str__默认调用__repr__)。
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
# 面向用户的展示格式
def __str__(self):
return f"《{self.title}》- 作者:{self.author}"
# 面向开发者的调试格式(可通过该字符串还原对象)
def __repr__(self):
return f"Book(title='{self.title}', author='{self.author}')"
# 测试
b = Book("Python编程", "张三")
print(b) # 触发__str__ → 《Python编程》- 作者:张三
print(repr(b)) # 触发__repr__ → Book(title='Python编程', author='张三')
  1. 魔术方法的必要性:
  • 魔术方法本质是重写(overriding) 还是实现(implementing)? 绝大多数情况是 “重写(Override)”,少数是 “实现(Implement)”。因为Python 中所有自定义类默认继承自最顶层的object类(可以理解为 “所有类的祖宗”),object类已经为常见的魔术方法提供了默认实现, 但这些默认实现往往 “没用” 或 “不符合具体的需求”。

  • 3.1重写和不重写的区别:

# 情况1:不重写__str__(用object的默认实现)
class Person:
def __init__(self, name):
self.name = name
p = Person("张三")
print(p) # 输出:<__main__.Person object at 0x7f8a1b2c3d40>(毫无意义)
#想输出也只能print(p.name) # 输出:张三
# 情况2:重写__str__(替换默认行为)
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"我是{self.name}" # 自定义逻辑
p = Person("张三")
print(p) # 输出:我是张三(符合你的需求)
  • 3.2自定义add方法 vs 魔术方法__add__:核心区别是 “触发方式” 和 “集成性”
# 方式1:自定义普通add方法
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# 普通方法:必须手动调用
def add(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
# 只能手动调用add方法
p3 = p1.add(p2)
# 用+号会报错:TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
# p3 = p1 + p2 # 报错!
# 方式2:魔术方法__add__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# 魔术方法:解释器自动触发
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
# 支持原生+号,符合Python习惯
p4 = p1 + p2 # 等价于p1.__add__(p2),但你不用手动写
print(p4.x, p4.y) # 输出:4 6

也就是说,自定义add方法需手动调用,而__add__会被 Python 解释器自动触发,支持+号等原生语法,和 Python 生态的集成性更好。

5 继承#

继承允许我们创建新的类(子类)基于现有的类(父类)。子类继承了父类的所有属性和方法,并允许我们扩展或重写这些属性和方法。继承关系 establ

  1. 继承关系的语法:
# 基本继承示例
class Animal: # 父类(基类)
def __init__(self, name):
self.name = name
def speak(self):
return "动物叫"
class Dog(Animal): # 子类(派生类)
def speak(self):
return "汪汪!"
class Cat(Animal): # 子类
def speak(self):
return "喵喵!"
# 使用
dog = Dog("旺财")
cat = Cat("咪咪")
print(dog.name) # 旺财(继承自Animal)
print(dog.speak()) # 汪汪!(重写了speak方法)
print(cat.speak()) # 喵喵!
  1. 后续: 目前暂时没有涉及多余的内容,后续会继续补充。

6 装饰器#

  1. 概念:

装饰器(Decorator)是 Python 用于修改其他函数的功能的一种方式。它是一种函数,它接受一个函数作为输入,并返回一个修改后的函数。

  1. 后续 目前涉及的内容说实话还是不清楚什么时候该用装饰器,以及该用什么类型的装饰器,后续慢慢补充吧。

7 类之间数据共享和独立#

我目前用的是单列模式,创建一个类,然后实例化一个对象,对象之间数据共享。 单列模式:单例模式(Singleton Pattern) 是一种设计模式,确保一个类在整个应用程序中只有一个实例,并提供全局访问点来获取这个实例。

from dataclasses import field, dataclass
@dataclass
class GlobalParams:
name: str = "张三"
age: int = 18
class Test1 :
def __init__(self):
self.gp = GlobalParams() # 实例化一个对象
class Test2 :
def __init__(self):
self.gp = GlobalParams() # 实例化一个对象,也就是说这两个test类的gp对象是两个不同的对象,数据是独立的

如果需要在不同的test类中想要得到的是个同一的GlobalParams对象,包括值一样,则需要通过单列模式来实现数据互通。

from dataclasses import field, dataclass
from typing import Optional
@dataclass
class GlobalParams:
name: str = "张三"
age: int = 18
_global_params_instance: Optional[GlobalParams] = None
def get_global_params() -> GlobalParams:
"""获取GlobalParams单例实例
所有地方都应该通过此函数获取GlobalParams,
而不是直接实例化 GlobalParams()
"""
global _global_params_instance
if _global_params_instance is None:
_global_params_instance = GlobalParams()
return _global_params_instance
class Test1 :
def __init__(self):
self.gp = get_global_params() # 实例化一个对象
class Test2 :
def __init__(self):
self.gp = get_global_params() # 实例化一个对象,因为get_global_params()返回的是同一个对象,所以两个test类的gp对象是同一个对象,数据是共享的

文章目录

✨️ 复制成功,转载请标注本文地址