from dataclasses import dataclass
from mimetypes import inited
from pdb import post_mortem
from unittest import skipIf
from dataclasses import asdict
from dataclasses import field
"""创建类创建和使用类"""
class Dog:
def __init__(self,name,age):
"""初始化属性name 和 age"""
self.names = name
self.ages = age
def sit(self):
print(f"{self.names} is now sitting")
def roll_over(self):
print(f"{self.names} is rolled over")
# 使用类创建实例
my_dog =Dog('willie',6)
print(f"My Dog's name is {my_dog.names},{my_dog.ages} years old")
my_dog.sit()
my_dog.roll_over()
# 创建多实例
your_dog =Dog('habagou',3)
print(f"your Dog's name is {your_dog.names},{your_dog.ages} years old")
your_dog.sit()
your_dog.roll_over()
# 创建Car类
class Car:
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year =year
def get_info(self):
car_info = f"{self.year} {self.model} {self.make}"
return car_info.title()
# 使用car类
my_car = Car('audi','a4',2025)
print(my_car.get_info())
# ---------------------------------------------------------------
# 给属性指定默认值
# ---------------------------------------------------------------
# 创建Car类
class Car:
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year =year
# 给属性指定默认值
self.odometer_reading =0
def get_info(self):
car_info = f"{self.year} {self.model} {self.make}"
return car_info.title()
def read_odometer(self):
print(f"本车已行驶{self.odometer_reading}千米")
# 使用car类
my_car = Car('audi','a4',2025)
# print(my_car.get_info())
my_car.read_odometer()
# --------------------------------------------------------------
# 修改属性的值
# --------------------------------------------------------------
# 1.直接修改属性的值
my_car.odometer_reading =23
my_car.read_odometer()
# --------------------------------------------------------------
# 2.通过方法修改属性的值
# 创建Car类
class Car:
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year =year
# 给属性指定默认值
self.odometer_reading =0
def get_info(self):
car_info = f"{self.year} {self.model} {self.make}"
return car_info.title()
# 通过方法来修改属性的值
def read_odometer(self,mileage):
self.odometer_reading =mileage
print(f"本车已行驶{self.odometer_reading}千米")
# 使用car类
my_car = Car('audi','a4',2025)
my_car.read_odometer(35)
# ---------------------------------------------------------------
# 3.通过方法让属性的值递增
# 创建Car类
class Car:
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year =year
# 给属性指定默认值
self.odometer_reading =0
def get_info(self):
car_info = f"{self.year} {self.model} {self.make}"
return car_info.title()
def read_odometer(self):
print(f"本车已行驶{self.odometer_reading}千米")
# 通过方法来修改属性的值
def update_odometer(self,mileage):
self.odometer_reading =mileage
print(f"本车已行驶{self.odometer_reading}千米")
def increment_odometer(self,miles):
self.odometer_reading += miles
# 使用类
my_car_1 = Car('changcheng','hafo',2026)
print(my_car_1.get_info())
my_car_1.update_odometer(23_500)
my_car_1.increment_odometer(50)
my_car_1.read_odometer()
# ---------------------------------------------------------
# 关于类的实例化的问题
# 类的实例化是否可以不赋值参数,取决于类中__init__方法的定义
# ---------------------------------------------------------
# 1.如果 __init__ 方法有默认参数
class Person:
name:str
age:int
def __init__(self,name = 'Mayer',age =0):
self.name = name
self.age = age
# 类的实例化
# 实例化可以不赋值
person_1 = Person()
print(f"类实例化可以不赋值:{person_1},------------------name:{person_1.name},-----------age:{person_1.age}")
# 实例化仅赋1个参数值
person_2 = Person("JieHao")
print(f"类实例化赋1个参数值:{person_2},------------------name:{person_2.name},-----------age:{person_2.age}")
# 实例化赋值
person_3 = Person("Zhongmin",40)
print(f"类实例化赋1个参数值:{person_3},------------------name:{person_3.name},-----------age:{person_3.age}")
# --------------------------------------------------------------
# 2.如果 __init__ 方法没有默认参数
class Person2:
name:str
age:int
city:str
def __init__(self,name,age,city):
self.name = name
self.age =age
self.city = city
# TypeError: Person2.__init__() missing 3 required positional arguments: 'name', 'age', and 'city'
# person_4 =Person2()
# print(f"init方法没有默认参数赋值的情况下:{person_4.city}")
# 类中没有定义init方法
"""
定义__init__方法的主要目的是在创建实例时进行初始化,例如设置实例属性的初始值。这样可以确保每个实例在创建后都有一组一致的属性。如果没有__init__方法,实例的属性将需要在外部代码中设置,这可能导致错误或遗漏。
如果一个类没有定义__init__方法,那么它在实例化时不会报错,因为Python会使用默认的__init__方法.
在Python中,如果你没有为一个类定义__init__方法,那么该类会继承自基类object的__init__方法。这个默认的__init__方法不接受任何参数(除了self),并且不执行任何操作。
当你创建一个类的实例时,Python会自动调用该类的__init__方法(如果存在)。如果不存在,则调用父类(直到object)的__init__方法。由于object的__init__方法不要求任何参数(除了self),所以你可以不带任何参数实例化这个类。
# ----------------------------------------------------------
class MyClass:
pass
# 实例化, 这不会报错,因为调用的是默认的__init__方法
obj = MyClass()
# 可以动态地为实例添加属性
obj.name ="MayerHaHaHaHaHaHa"
print(f"动态地为实例添加属性:{obj.name}")
# ----------------------------------------------------------
# 自定义了__init__方法,要求两个参数
class MyClassWithInit:
def __init__(self, a, b):
self.a = a
self.b = b
# 必须传递两个参数,否则会报错
# obj = MyClassWithInit() # 报错:缺少两个参数
# 没有定义__init__方法的类
class MyClassWithoutInit:
pass
# 可以无参数实例化
obj = MyClassWithoutInit() # 正确
# -------------------------------------------------------
# 继承中的__init__方法
class Parent:
def __init__(self,x):
self.x = x
class Child(Parent):
pass
# TypeError: Parent.__init__() missing 1 required positional argument: 'x'
# c = Child()
# 由于Child没有定义__init__,它会使用Parent的__init__
# 因此实例化Child时需要传入Parent的__init__所需的参数
c = Child(2000)
print(c.x)
# -------------------------------------------------------
# 特殊情况:__init__方法只接受self
# 如果你定义了一个只接受self的__init__方法,那么实例化时也不需要传递额外参数。
class MyClass:
def __init__(self):
self.initialized = True
obj = MyClass()
print(f"init方法仅接受self参数:{obj.initialized}")
"""
# 继承自 object 的默认行为
# 这个类没有定义 __init__
class SimpleClass:
pass
# 实际上相当于:
class SimpleClass:
def __init__(self):
pass # 什么都不做
# ------------------------------------------------------------------
class Person:
pass
# 实例化时,Python 执行以下步骤:
person = Person()
# 1. 创建对象(分配内存)
# 2. 如果 Person 没有 __init__,就调用父类 object 的 __init__
# 3. 返回这个对象
# -----------------------------------------------------------------
class EmptyClass:
pass
# 创建实例
obj = EmptyClass()
# 可以动态添加属性
obj.name = "Alice" # 运行时添加
obj.age = 25
print(obj.name) # Alice
print(obj.age) # 25
# 也可以动态添加方法
def say_hello(self):
return f"Hello, I'm {self.name}"
# 但这不是绑定方法,只是普通函数
print(say_hello(obj)) # Hello, I'm Alice
# -------------------------------------------------------------------
# ---------------------------------------------------------------------
# 类的继承
# ---------------------------------------------------------------------
# 当一个类继承另一个类时,将自动获得后者所有的属性和方法,原有的类成为父类,而新类成为子类,子类不仅可以继承父类所有的属性和方法,还可以定义自己的属性和方法
# 基本语法:
"""
class ParentClass:
# 父类的定义
pass
class ChildClass(ParentClass):
# 子类的定义,继承自ParentClass
pass
"""
# 使用继承
"""
继承属性和方法:子类会自动拥有父类的所有属性和方法(除了私有属性和方法,即以双下划线__开头的属性或方法,但实际上私有方法也可以通过特殊方式访问,但一般不推荐)。
方法重写:如果子类需要修改父类的方法,可以在子类中定义同名方法,这样就会覆盖父类的方法。
调用父类方法:在子类中,可以使用super()函数来调用父类的方法,这在重写方法但还需要父类功能时非常有用。
添加新属性和方法:子类可以添加新的属性和方法。
多继承:Python支持多继承,即一个子类可以继承多个父类。语法为class ChildClass(Parent1, Parent2, ...)。在多继承中,方法解析顺序(MRO)按照C3线性化算法来确定。
"""
# ---------------------------------------------------------------------
# 基本语法示例
# 示例1:
# 父类
class ParentClass:
def __init__(self,name):
self.cname = name
def display(self):
print(f"父类方法,名称:{self.cname}")
# 子类
class ChildClass(ParentClass):
def __init__(self,name,age):
# 调用父类的构造函数
super().__init__(name)
self.caage = age
def display(self):
super().display()
print(f"子类方法,年龄:{self.caage}")
my_class = ChildClass('张三','30')
print(my_class.cname)
print(my_class.caage)
my_class.display()
# ----------------------------------------------------------------------
# 子类在继承父类时候是否必须在子类的 构造函数中显性调用父类的构造函数的情形
# ---------------------------------------------------------------------
"""
当子类继承父类时,并不强制要求在子类的构造函数中显式调用父类的构造函数。但是,如果子类需要初始化父类的属性,或者父类的构造函数中包含必要的初始化代码,那么通常需要在子类的构造函数中调用父类的构造函数。
Python提供了一种自动调用父类构造函数的机制,即使用super()函数。但是,即使不显式调用,子类也会继承父类的所有属性和方法。不过,如果父类的构造函数没有被调用,那么父类中在构造函数里初始化的属性可能就不会在子类实例中存在。
1.如果父类有构造函数,而子类没有定义构造函数,那么创建子类对象时会自动调用父类的构造函数。
2.如果子类定义了构造函数,但没有显式调用父类的构造函数,那么父类的构造函数不会被执行。这意味着父类中在构造函数里初始化的属性可能不会在子类实例中。
3.如果子类定义了构造函数,并且需要调用父类的构造函数,应该使用super().__init__()(在Python 3中)来调用。
"""
#示例1: 子类没有构造函数,自动调用父类构造函数
class Parent:
def __init__(self):
self.parent_attr = 'parent attribute'
def parent_method(self):
return "From parent"
class Child(Parent):
pass
child =Child()
# parent attribute
print(f"子类没有构造方法,自动调用父类构造函数:{child.parent_attr}")
# From parent
print(f"子类没有构造方法,自动调用父类构造函数:{child.parent_method()}")
# 示例2:子类有构造函数,但没有调用父类构造函数
class Parent:
def __init__(self):
self.parent_attr = 'parent attribute'
def parent_method(self):
return "From parent"
class Child(Parent):
def __init__(self):
self.child_attr ='child attribute'
child_2 =Child()
# 示例2结果:child attribute
print(f"示例2结果:{child_2.child_attr}")
# 示例2结果:From parent
print(f"示例2结果:{child_2.parent_method()}")
"""报错:AttributeError: 'Child' object has no attribute 'parent_attr'"""
# print(f"示例2结果:{child_2.parent_attr}")
# 示范3:子类有构造函数,并调用父类构造函数。当子类定义了自己的 __init__ 方法时,必须显式调用父类的构造函数才能继承父类的属性。
class Parent:
def __init__(self,name):
self.pname = name
self.parent_attr = "From parent attribute"
class Child(Parent):
def __init__(self,cname):
super().__init__(cname)
self.child_attr = "From child attribute"
my_child = Child("mayer")
print(f"子类有构造函数,调用父类构造函数:{my_child.child_attr}")
print(f"子类有构造函数,调用父类构造函数:{my_child.parent_attr}")
# --------------------------------------------------------------------------------
# 类装饰器@dataclass
# --------------------------------------------------------------------------------
# 传统类如示例1——3所示
# 类装饰器@dataclass示例1:
from dataclasses import dataclass
@dataclass
class Parent:
name:str
age:int=10
parent_1 = Parent('Mayer',30)
print(f"fulei:{parent_1.name},{parent_1.age}")
# -------------------------------------------------------
# name: str
"""
name:字段名
: :类型注解分隔符
str:类型提示(type hint)
"""
# age: int = 0
"""
这行代码包含四个部分:
age:字段名
: :类型注解分隔符
int:类型提示
= 0:默认值
"""
# 示例1:
from dataclasses import dataclass
@dataclass
class DataclassParent:
name: str # 类级别的字段定义
age: int = 0 # 带默认值的字段定义
# 字段在类定义时就已经存在
# -------------------------------------------------------
# 类装饰器示例2,使用dataclass自动合并
from dataclasses import dataclass
@dataclass
class Parent:
name:str
age:int = 0
@dataclass
class Child(Parent):
# 自动合并父类字段
grade:str = 'A'
# 等同于自动生成了以下代码:
# def __init__(self, name: str, age: int = 0, grade: str = "A"):
# self.name = name
# self.age = age
# self.grade = grade
child = Child('Mayer',35,'A+')
print(f"child:{child},---------{type(Child)}")
# 字段合并顺序
# ----------------------------------------------------------
from dataclasses import dataclass,fields
@dataclass
class Parent:
name:str
age:int = 0
@dataclass
class Child(Parent):
grade: str = 'A'
# 查看自动生成的字段
"""
fields(Parent) 是一个 元数据查询函数,用于获取类的字段定义信息.fields(Parent) 是一个函数调用,通常用于获取类 Parent 的字段信息
返回一个 字段对象的元组,每个字段对象包含字段的完整元数据。每个 Field 对象描述了数据类中的一个字段。
每个 Field 对象包含以下属性(常见):
1. name:字段名称(字符串)。
2. type:字段的类型注解(可能为空)。
3. default:字段的默认值(如果没有默认值,则是 dataclasses.MISSING)。
4. default_factory:用于生成默认值的工厂函数(如果没有,则是 dataclasses.MISSING)。
5. 还有其他属性,如 init、repr、hash 等,取决于数据类的定义。
-------------------------------------
常用属性详解
------------------------------------
属性 描述 示例
name 字段名称 'name', 'age'
type 类型注解 str, int, List[str]
default 默认值 Anonymous, 18
default_factory 默认值工厂函数 lambda: []
init 是否包含在 __init__ 中 True/False
repr 是否包含在 __repr__ 中 True/False
compare 是否用于比较方法 True/False
hash 是否用于哈希计算 True/False
metadata 用户定义的元数据 {'min': 0, 'max': 100}
MISSING 特殊标记(表示无默认值) 检查 f.default is f.MISSING
---------------------------------------------------------
"""
print(f"父类字段:{[f.name for f in fields(Parent)]},-------{fields(Parent)}")
print(f"子类字段:{[f.name for f in fields(Child)]},-------{fields(Child)}")
# 查看字段顺序和默认值
for f in fields(Child):
print(f"字段: {f.name}, 类型: {f.type}, 默认值: {f.default}")
# --------------------------------------------------------------------------------------------
# 自动继承规则
# --------------------------------------------------------------------------------------------
# 规则1:字段继承顺序
@dataclass
class A:
a:str
b:int = 1
@dataclass
class B(A):
c:float = 2.0
# 生成的__init__参数顺序:a, b, c
obj = B(a="test", b=3, c=4.0)
# 规则2:默认值继承
# ------------------------------------------------
@dataclass
class Base:
x:int = 10
y:int = 20
@dataclass
class Derived(Base):
z:int = 30
# 可以使用所有默认值
d = Derived()
print(f"d:{d.x}-----{d.y}--------{d.z}")
# 也可以覆盖部分
d2 = Derived(x=5)
print(f"d:{d2.x}-----{d2.y}--------{d2.z}")
# 规则 3:字段类型继承
# ------------------------------------------------
@dataclass
class Person:
name: str
age: int
@dataclass
class Employee(Person):
salary: float
# 必须提供所有父类字段
emp = Employee(name="Alice", age=30, salary=50000.0)
# ---------------------------------------------------
# 多层继承
# --------------------------------------------------
@dataclass
class Grandparent:
grand_parent:str ='grand'
@dataclass
class Parent(Grandparent):
parent_value:str ='parent'
@dataclass
class Child(Parent):
child_value:float = 1.0
c = Child()
print(f"多层继承:{c}")
# 覆盖父类默认值
# --------------------------------------------------
@dataclass
class Base:
value:int = 20
@dataclass
class Derived(Base):
value:int = 25
d =Derived()
print(f"覆盖父类默认值后:{d.value}")
d_2 = Derived(value=30)
print(f"在实例时候覆盖父类默认值:{d_2.value}")
# --------------------------------------------------
# 场景 3:混合有/无默认值的字段
@dataclass
class Base:
name:str
age:int = 0
@dataclass
class Child(Base):
salary:float =30000
c =Child('Mayer')
print(c)
# 注意事项
# 注意1:不能与显式__init__共存
# -----------------------------------
@dataclass
class Parent:
x: int = 1
@dataclass
class Child(Parent):
y: int = 2
# ❌ 这会覆盖自动生成的 __init__
def __init__(self, y):
self.y = y # 会丢失父类的字段
# ------------------------------------
# 注意 2:继承链中所有类都应是 @dataclass
# ❌ 不推荐:混合使用
class RegularParent:
def __init__(self, x):
self.x = x
@dataclass
class DataclassChild(RegularParent): # 这可能导致问题
y: int = 0
# --------------------------------------------------------------------------
# 使用InitVar进行复杂问题的初始化
# --------------------------------------------------------------------------
# 我们定义了一个 InitVar 类型的字段 init_value,它在 __post_init__ 方法中被使用,用来设置 processed_name 属性。但是,init_value 本身不会成为实例的属性。
"""
__init__ 方法中的 InitVar 是一种特殊的类型注解,它用于指定一个参数在 __init__ 方法中使用,但不会成为实例的属性。也就是说,这个参数的值只在初始化过程中使用,而不会被保存为实例的变量。
InitVar 通常与 dataclasses 模块一起使用。在 dataclass 中,我们可以使用 InitVar 来定义这样的参数
注意:在子类中,如果父类有 InitVar 字段,那么子类会自动继承这个 InitVar 字段,并且在生成 __init__ 方法时,会包含这个参数。
同样,在子类的 __post_init__ 方法中也需要接收这个参数,并且如果子类没有重写 __post_init__,那么它会调用父类的 __post_init__。
如果子类重写了 __post_init__,那么必须确保正确处理父类的 InitVar 参数,通常是通过调用父类的 __post_init__ 方法。
另外,如果子类也是 @dataclass,那么它也会自动生成包含父类 InitVar 字段的 __init__ 方法
在 dataclass 中,使用 InitVar 注解的字段不会被添加到生成的 __init__ 方法的参数列表之外,也就是说,它不会成为实例的属性。
我们可以在 __post_init__ 方法中使用这些 InitVar 字段。__post_init__ 方法在 __init__ 之后被调用,并且接收所有 InitVar 字段作为参数。
如果我们想保存 InitVar 字段的值,我们需要在 __post_init__ 中手动将其赋值给实例的某个属性(如上例中的 _internal)。
类型提示必需:必须指定类型,如 InitVar[str]
__post_init__参数必需:如果定义了InitVar,必须在__post_init__中接收
不可访问:实例创建后无法直接访问InitVar的值
顺序无关:可以放在普通字段之前或之后
"""
# ------------------------------------------------------------------
# 关于InitVar不会成为实例字段,但创建实例对象时,仍然需要体现
# ------------------------------------------------------------------
from dataclasses import dataclass, InitVar
@dataclass
class Example:
x: int
y: InitVar[int] # 这是一个InitVar参数,不会成为实例字段
z: int = None # 这是一个普通的实例字段,有默认值
def __post_init__(self, y):
# 在__post_init__中,我们可以使用InitVar参数y
self.z = self.x + y
# 创建对象时,必须提供InitVar参数y
obj = Example(x=5, y=10)
print(obj) # 输出:Example(x=5, z=15)
print(obj.z) # 输出:15
# 注意:obj没有y属性,因为y是InitVar
# -------------------------------------------------------------------
@dataclass
class Example2:
x: int
y: InitVar[int] = 5 # 有默认值
z: int = None
def __post_init__(self, y):
self.z = self.x + y
obj2 = Example2(x=10) # 使用默认值y=5
print(obj2.z) # 输出15
# ----------------------------------------------------------------
from dataclasses import dataclass, InitVar, field
@dataclass
class User:
name: str
age: int
# InitVar 参数:在初始化时提供,但不存储为实例字段
# InitVar[str] 声明的字段不会成为实例字段。它是一个初始化专用变量,只存在于 __init__ 方法中,不会成为实例的属性
# 在dataclass中,InitVar 用于声明一个仅在初始化时使用的字段,该字段不会被存储为实例属性。它通常用于在初始化过程中计算其他字段的值。
password: InitVar[str]
# 注意:password_hash 不是 InitVar,它会是实例字段
"""
password_hash: str
声明一个名为 password_hash 的字段类型注解为 str(字符串类型)
= field(...) 使用 dataclasses.field() 函数配置字段的特殊行为
init=False
关键部分:告诉dataclass不要在自动生成的 __init__ 方法中包含此参数
意味着创建对象实例时,不能直接传递 password_hash 值
password_hash 会成为实例字段,但它不会在 __init__ 方法中被初始化
"""
password_hash: str = field(init=False) # 不在 __init__ 中初始化
def __post_init__(self, password: str):
# 使用 password 来计算 password_hash,但 password 本身不存储
self.password_hash = self._hash_password(password)
def _hash_password(self, pwd: str) -> str:
# 模拟密码哈希
return f"hashed_{pwd}"
# ✅ 创建对象时必须提供 password 参数
user = User(name="Alice", age=30, password="secret123")
print(user)
# 输出:User(name='Alice', age=30, password_hash='hashed_secret123')
print(user.name) # ✅ Alice
print(user.age) # ✅ 30
print(user.password_hash) # ✅ hashed_secret123
# print(user.password) # ❌ AttributeError: 'User' object has no attribute 'password'
# --------------------------------------------------------------------------------------
from dataclasses import dataclass,InitVar
@dataclass
class Parent:
name:str
age:int
# 声明一个InitVar类型的参数,它不会成为实例字段
init_value:InitVar[str] ='defalut'
def __post_init__(self, init_value: str):
# 在 __post_init__ 中我们可以使用这个 InitVar 参数
# 例如,我们可以用它来计算或设置其他字段
self.processed_name = self.name + init_value
@dataclass
class ChildData(Parent):
grade:float =0.0
# 创建实例时,需要提供init_var_a参数,但它不会成为实例属性
child_3 = ChildData('ZHANGSAN',20,"!!!!",98.0)
# 结果:ChildData(name='ZHANGSAN', age=20, grade=98.0)
print(child_3)
#结果: ZHANGSAN!!!!
print(child_3.processed_name)
# -------------------------------------------------------------
# 不会成为实例属性
@dataclass
class Example:
regular_field: str # 会成为实例属性
init_only: InitVar[str] # 不会成为实例属性
def __post_init__(self, init_only):
print(f"初始化时使用: {init_only}")
e = Example("hello", "world")
print(e.regular_field) # 输出: hello
# print(e.init_only) # 错误!没有这个属性
# --------------------------------------------------------------
# 传递到 __post_init__
@dataclass
class Circle:
radius: float
calculate_area: InitVar[bool] = True
area: float = 0.0
def __post_init__(self, calculate_area):
if calculate_area:
self.area = 3.14159 * self.radius ** 2
c = Circle(5.0)
print(c.area) # 输出: 78.53975
# -------------------------------------------------------------------
# 使用InitVar进行复杂问题的初始化:示例2:
from dataclasses import dataclass,InitVar
@dataclass
class Parent:
name:str
age:int
init_value_Parent:InitVar[str] = 'defalut'
def __post_init__(self, init_value_Parent: str):
self.processed = self.age +len(init_value_Parent)
@dataclass
class Child(Parent):
salary:int = 0
init_value_child:InitVar[str] = '0'
def __post_init__(self, init_value_Parent:str,init_value_child: str):
# 必须调用父类的 __post_init__,因为我们在父类中使用了init_value_Parent
super().__post_init__(init_value_Parent)
self.save = self.salary+len(init_value_child)
child_3 = Child('zhangsan',30,"%%%%",100000,"hhh")
print(f"子类对象:{child_3}\n过程变量processed:{child_3.processed}\n过程变量save:{child_3.save}\ninit_value_Parent:{child_3.init_value_Parent}\ninit_value_child:{child_3.init_value_child}")
"""
在这个例子中,类 A 有一个 InitVar 字段 init_var_a,类 B 继承自 A 并新增了一个 InitVar 字段 init_var_b。注意,在 B 的 __post_init__ 方法中,我们接收两个 InitVar 参数,并且调用了父类的 __post_init__ 方法。
总结:
InitVar 用于声明初始化时使用的变量,但不会成为实例字段。
在 __post_init__ 方法中可以使用这些变量。
如果父类有 InitVar 字段,子类在生成 __init__ 时会包含这些字段,并且子类的 __post_init__ 方法需要接收这些字段,并正确处理(通常要传递给父类的 __post_init__)。
InitVar 字段不会出现在实例的 __dict__ 中,也不会出现在 dataclasses.fields 返回的字段列表中(但会出现在 __init__ 方法的参数中)。
注意:InitVar 是 dataclasses 模块中一个特殊的类型,它只在使用 @dataclass 装饰器时才有效。它告诉 dataclass 这个字段只用于初始化,不作为实例字段存储
"""
# ---------------------------------------------------------------------------------------------
# 使用InitVar进行复杂问题的初始化:示例3:
# InitVar字段(仅初始化时使用)
from dataclasses import dataclass,InitVar,fields
@dataclass
class LifecycleDemo:
# 常规字段(会成为实例属性)
final_value:int
# InitVar字段(仅初始化期间存在)
raw_data:InitVar[str]
multiplier:InitVar[int] = 2
def __post_init__(self, raw_data: str, multiplier: int):
print(f"__post_init__阶段 - raw_data:{raw_data}")
print(f"__post_init__阶段 - raw_data:{multiplier}")
# 处理原始数据,生成最终值
self.processed = len(raw_data) * multiplier
self.final_value = self.processed
# InitVar参数在这里被使用后就消失了
# 查看字段信息
for field in fields(LifecycleDemo):
print(f" 类的属性:{field.name} :类的类型type= {field.type} ,init_only ={field.init}")
# 创建实例
lcd = LifecycleDemo(final_value = 30, raw_data = "哈哈哈哈哈,我是raw-data",multiplier=100000000000000000000)
print(f"\n实例属性:{lcd.final_value},是否有raw_data属性?:{hasattr(obj,'raw_data')}\n是否有multiplier属性?:{hasattr(obj,'multiplier')}")
# -------------------------------------------------------------------------------------------
# 应用场景1.计算派生字段:计算派生字段
from dataclasses import dataclass,InitVar
from datetime import date,datetime
from typing import Optional
@dataclass
class UserProfile:
username:str
# 用于计算年龄
birth_date:InitVar[int]
"""
在Python中,Optional[int] 表示一个类型,它可以是 int 或者 None。这通常用于类型注解,表明变量可以是一个整数,也可以是空值(None)。
具体到 age: Optional[int] = None 这一行,它做了以下几件事:
声明一个变量 age。
使用类型注解指定 age 的类型为 Optional[int],即允许整数或None。
将 age 初始化为 None。
age: Optional[int] = None
age:变量名
Optional[int]:类型注解,表示 age 可以是 int 或 None
= None:初始化值为 None
# 以下两种写法是等价的
age: Optional[int] = None
age: Union[int, None] = None
"""
age: Optional[int]= None
create_at : date = None
def __post_init__(self, birth_date: int):
current_year = datetime.now().year
self.age = current_year - birth_date
self.create_at = datetime.today()
user = UserProfile("Mayer",1990)
print(f"用户名:{user.username},年龄:{user.age},创建时间:{user.create_at}")
# -------------------------------------------------------------------
# -------------------------------------------------------------------
# __post_init__方法中出现的在类中没有定义的变量该怎么理解?
# __post_init__方法中定义的变量通常是实例变量,即使它们在类属性中没有定义(即没有在dataclass的字段中声明),这些变量在实例化后会被添加到实例中,因此实例可以调用它们
# 可以访问:在 __post_init__ 中动态创建的变量是完全的实例属性,实例可以正常访问、修改、删除它们。
# 与 dataclass 字段的区别:
# 不在类属性中定义,所以不会被 @dataclass 装饰器处理
# 不会出现在自动生成的 __repr__、__eq__ 等方法中
# 不能通过构造函数参数传入
from dataclasses import dataclass
@dataclass
class Example:
x: int
y: int
def __post_init__(self):
# 定义一个在类属性中没有的变量
self.z = self.x + self.y
# 实例化
obj = Example(1, 2)
print(obj.x, obj.y) # 输出: 1 2
print(obj.z) # 输出: 3,因为z在__post_init__中被定义
# 注意:z不会出现在__repr__中
print(repr(obj)) # 输出: Example(x=1, y=2)
# 但是,实例可以调用z,因为它已经被绑定到实例上
print(obj.z) # 3
# 另外,这样的属性可以被修改
obj.z = 10
print(obj.z) # 输出: 10
# ---------------------------------------------
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
def __post_init__(self):
# 定义类属性中没有的变量
self.is_adult = self.age >= 18
self.greeting = f"Hello, I'm {self.name}!"
self.birth_year = 2024 - self.age # 假设当前是2024年
# 使用
p = Person("Alice", 25)
print(p.is_adult) # True - 可以访问
print(p.greeting) # "Hello, I'm Alice!" - 可以访问
print(p.birth_year) # 1999 - 可以访问
# ------------------------------------------------------------
# ---------------------------------------------------------------------------------
# 继承中的InitVar
# 简单继承
from dataclasses import dataclass,InitVar
@dataclass
class Base:
base_value:int
config:InitVar[dict] =None
def __post_init__(self, config: dict):
if config:
print(f"Base配置:{config}")
self.base_value_squared = self.base_value ** 2
@dataclass
class MyDerived(Base):
derived_value:str =""
extra_config:InitVar[str] =""
def __post_init__(self, config: dict,extra_config: str):
# # 必须调用父类的__post_init__,并传递所有父类需要的InitVar
super().__post_init__(config)
if extra_config:
self.derived_value = f"{self.derived_value}_{extra_config}"
"""
class Parent:
def __init__(self, a, b):
self.a = a
self.b = b
class Child1(Parent):
pass
# 子类Child1没有定义自己的__init__,所以需要提供父类的两个参数
c1 = Child1(1, 2) # 正确
# c1 = Child1(1) # 错误,需要两个参数
class Child2(Parent):
def __init__(self, a, b, c):
super().__init__(a, b) # 调用父类初始化,传递a和b
self.c = c
# 子类Child2有自己的__init__,并且需要三个参数,其中两个用于父类,一个用于自己
c2 = Child2(1, 2, 3) # 正确
class Child3(Parent):
def __init__(self, c):
# 注意:这里没有调用父类的__init__,所以父类的属性a和b不会被初始化
self.c = c
c3 = Child3(3) # 这样虽然可以实例化,但是父类的属性a和b没有初始化,所以c3.a和c3.b会不存在
class Child4(Parent):
def __init__(self, a, b, c=0):
# 子类可以有自己的默认参数,但父类所需的参数必须传递
super().__init__(a, b)
self.c = c
# 实例化Child4时,必须提供a和b,c可以省略(有默认值)
c4_1 = Child4(1, 2) # c默认为0
c4_2 = Child4(1, 2, 3) # 提供c的值
"""
obj = MyDerived(
base_value= 5,
derived_value= "test",
config={'mode':'debug'},
extra_config="extra"
)
print(f"base_value_squared:{obj.base_value_squared}")
print(f"derived_value:{obj.derived_value}")
print(fields(MyDerived))
print(obj.__dict__)
# ------------------------------------------------------------------
# 多层继承
from dataclasses import dataclass,InitVar
@dataclass
class Level1:
value1:int
init1:InitVar[str] = ""
def __post_init__(self, init1: str):
pass
@dataclass
class Level2(Level1):
value2:str = ""
init2:InitVar[int] =0
def __post_init__(self,init1, init2: int):
super().__post_init__(init1)
self.value2 = f"{self.value2}_{init2}"
@dataclass
class Level3(Level2):
value3 :float =0.0
init3:InitVar[bool] = False
def __post_init__(self,init1, init2: int,init3):
super().__post_init__(init1,init2)
if init3:
self.value3 *=1.1
obj2 =Level3(
value1= 10,
init1= "level1",
value2="test",
init2 = 10,
value3=100.0,
init3=True
)
print(f"{fields(obj2)}:{'-' * 5}{obj2.value1}{'-' * 5}{obj2.value2}{'-' * 5}{obj2.value3}")
# --------------------------------------------------------------------------------------------------
"""实例方法与类方法"""
# ---------------------------------------------------------------------------------------------------
"""
1.定义方式与第一个参数:
实例方法:第一个参数通常命名为 self,代表实例对象本身。在定义时不需要使用装饰器。
类方法:第一个参数通常命名为 cls,代表类本身。需要使用 @classmethod 装饰器。
Class Myclass:
def instance_method(self):
printf(f"实例方法,self:{self}")
@classmethod
def class_method(cls):
printf(f"类方法,cls:{cls}")
2.调用方式
实例方法:必须通过实例对象调用,或者通过类调用并手动传入实例。
类方法:既可以通过类直接调用,也可以通过实例调用(Python 会自动将类传入)。
obj = MyClass()
obj.instance_method() # 通过实例调用实例方法
MyClass.instance_method(obj) # 通过类调用实例方法,需显式传实例
MyClass.class_method() # 通过类调用类方法
obj.class_method() # 通过实例调用类方法(实际传入的是类)
3.访问权限
实例方法:可以访问实例属性(self.xxx)和类属性,也可以调用其他实例方法、类方法、静态方法。
类方法:只能访问类属性(cls.xxx),不能直接访问实例属性(因为没有 self)。可以调用其他类方法和静态方法,但不能直接调用实例方法(除非传入一个实例)。
Class Demo:
class_attr = 0
def __init__(self):
self.instance_attr = 100
def instance_method(self):
print(self.instance_attr) # ✅ 可以访问实例属性
print(self.class_attr) # ✅ 可以访问类属性
@classmethod
def class_method(cls):
print(cls.class_attr) # ✅ 可以访问类属性
# print(cls.instance_attr) # ❌ 报错,没有实例属性
特性 实例方法 类 方法 静态方法
装饰器 无 @classmethod @staticmethod
第一个参数 self(实例) cls(类) 无特殊参数
访问实例属性 ✅ 可以 ❌ 不可以 ❌ 不可以
访问类属性 ✅ 可以 ✅ 可以 ✅ 可以
调用方式 实例调用 类或实例调用 类或实例调用
"""
# 类方法
class Student:
school = "第一中学" # 类属性
students_count = 0
def __init__(self, name):
self.sname = name
Student.students_count += 1
# 类方法
@classmethod
def get_school_name(cls):
return cls.school
# 工厂方法
@classmethod
def create_student(cls, name):
"""类方法可以作为工厂方法"""
return cls(name)
@classmethod
def get_student_count(cls):
return cls.students_count
# 使用:可以通过类或实例调用
print(Student.get_school_name()) # 第一中学
# 作为工厂方法
stu = Student.create_student("小红")
print(stu.sname) # 小红
# 通过实例调用类方法
print(stu.get_school_name())
# 实例方法
# -------------------------------------------------------------
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
# 实例方法
def get_info(self):
return f"学生:{self.name},分数:{self.score}"
def update_score(self, new_score):
self.score = new_score
# 使用:必须通过实例调用
stu = Student("小明", 90)
print(stu.get_info()) # 学生:小明,分数:90
stu.update_score(95)
# 注意:此处是使用了类直接调用了实例方法,当传入了实例stu
print(f"类调用实例方法:{Student.get_info(stu)}")
# @staticmethod装饰器
# 将方法定义为静态方法,不需要实例或类作为第一个参数
# 类似于普通函数,但为了组织方便放在类中
# 不能访问实例属性和类属性
# 类名.方法或者实例.方法调用。不能像普通函数直接调用
# ----------------------------------------------------------------
class MathUtils:
@staticmethod
def add(x, y):
return x + y
@staticmethod
def is_even(num):
return num % 2 == 0
# 使用方式
print(MathUtils.add(5, 3)) # 通过类调用:8
math = MathUtils()
print(math.is_even(10)) # 通过实例调用:True
class Person:
species = "Homo sapiens" # 类属性
population = 0
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age
Person.population += 1
# 实例方法:操作实例数据
def introduce(self):
return f"我是{self.name},{self.age}岁"
def birthday(self):
self.age += 1
print(f"{self.name} 过生日啦!现在{self.age}岁")
# 类方法:操作类数据或创建实例
@classmethod
def get_species(cls):
return cls.species
@classmethod
def create_anonymous(cls, age):
"""工厂方法:创建匿名Person"""
return cls("Anonymous", age)
@classmethod
def get_population(cls):
return cls.population
# 静态方法:工具函数
@staticmethod
def is_adult(age):
return age >= 18
# ----------------------------------------------------------------
# 场景1
class Person:
species = "Homo sapiens" # 类属性
population = 0
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age
Person.population += 1
# 实例方法:操作实例数据
def introduce(self):
return f"我是{self.name},{self.age}岁"
def birthday(self):
self.age += 1
print(f"{self.name} 过生日啦!现在{self.age}岁")
# 类方法:操作类数据或创建实例
@classmethod
def get_species(cls):
return cls.species
@classmethod
def create_anonymous(cls, age):
"""工厂方法:创建匿名Person"""
return cls("Anonymous", age)
@classmethod
def get_population(cls):
return cls.population
# 静态方法:工具函数
@staticmethod
def is_adult(age):
return age >= 18
# 测试
# 实例方法
p1 = Person("张三", 25)
print(p1.introduce()) # 我是张三,25岁
p1.birthday() # 张三 过生日啦!现在26岁
# 类方法
print(Person.get_species()) # Homo sapiens
p2 = Person.create_anonymous(30)
print(p2.introduce()) # 我是Anonymous,30岁
print(Person.get_population()) # 2
# 静态方法
print(Person.is_adult(16)) # False
print(Person.is_adult(20)) # True
# 测试
# 实例方法
p1 = Person("张三", 25)
print(p1.introduce()) # 我是张三,25岁
p1.birthday() # 张三 过生日啦!现在26岁
# 类方法
print(Person.get_species()) # Homo sapiens
p2 = Person.create_anonymous(30)
print(p2.introduce()) # 我是Anonymous,30岁
print(Person.get_population()) # 2
# 静态方法
print(Person.is_adult(16)) # False
print(Person.is_adult(20)) # True
# --------------------------------------------------------------------
# 类装饰器@classmethod
# 第一个参数是类本身(通常命名为 cls)
# 可以访问和修改类属性
# 常用于工厂方法或替代构造函数
# --------------------------------------------------------------------
class Person:
count = 0 # 类属性
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def get_count(cls):
return cls.count
@classmethod
def from_string(cls, data_str):
"""工厂方法:从字符串创建实例"""
name = data_str.split(',')[0]
return cls(name) # 等价于 Person(name)
# 使用
p1 = Person("Alice")
p2 = Person("Bob")
print(Person.get_count()) # 2
p3 = Person.from_string("Charlie,25")
print(p3.name) # Charlie
#@property - 属性方法
# -------------------------------------------------------------------
# 作用
# 将方法转换为只读属性
# 可以添加 setter/deleter 实现数据验证
# 提供优雅的接口封装
# -------------------------------------------------------------------
"""
class C:
def __init__(self):
self._x = None
@property
def x(self):
'I'm the 'x' property.'
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
# --------------------------------------------
语法:property(fget=None, fset=None, fdel=None, doc=None)
参数 作用 触发时机
fget getter 方法 读取属性时:obj.x
fset setter 方法 赋值时:obj.x = value
fdel deleter 方法 删除时:del obj.x
doc 文档字符串 help(Class.x)
# --------------------------------------------
"""
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
"""作为属性访问"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""属性赋值时的验证"""
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
@property
def fahrenheit(self):
"""只读属性"""
return self._celsius * 9 / 5 + 32
@celsius.deleter
def celsius(self):
"""删除属性时的操作"""
print("删除温度")
del self._celsius
# 使用
temp = Temperature(25)
print(temp.celsius) # 25(像属性一样,不加括号)
temp.celsius = 30 # 使用 setter
print(temp.fahrenheit) # 86.0
del temp.celsius
# temp.fahrenheit = 100 # 错误!只读属性
#-------------------------------------------------------------
# @abstractmethod - 抽象方法
# 声明抽象方法,子类必须实现
# 防止实例化包含抽象方法的类
# 定义接口规范
# -------------------------------------------------------------
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def make_sound(self):
"""子类必须实现这个方法"""
pass
@abstractmethod
def move(self):
pass
def normal_method(self):
return "普通方法不需要实现"
class Dog(Animal):
def make_sound(self):
return "汪汪"
def move(self):
return "跑"
# animal = Animal("test") # 错误!不能实例化抽象类
dog = Dog("旺财")
print(dog.make_sound()) # 汪汪
print(dog.move()) # 跑
# -----------------------------------------------------------
#@functools.wraps - 保留元信息
# 作用
# 保留被装饰函数的元数据(name, doc, __module__等)
# 解决装饰器导致的函数信息丢失问题
# 对调试和文档很重要
# -----------------------------------------------------------
from functools import wraps
def my_decorator(func):
# 不使用 @wraps 的坏例子
def bad_wrapper(*args, **kwargs):
"""坏包装器的文档"""
return func(*args, **kwargs)
return bad_wrapper
def good_decorator(func):
@wraps(func) # 保留原函数信息
def good_wrapper(*args, **kwargs):
"""好包装器的文档"""
return func(*args, **kwargs)
return good_wrapper
@my_decorator
def add_bad(a, b):
"""计算两个数的和"""
return a + b
@good_decorator
def add_good(a, b):
"""计算两个数的和"""
return a + b
print(add_bad.__name__) # bad_wrapper (丢失)
print(add_bad.__doc__) # 坏包装器的文档 (错误)
print(add_good.__name__) # add_good (保留)
print(add_good.__doc__) # 计算两个数的和 (保留)
# ----------------------------------------------------------------
# @dataclass-数据类
# 作用
# 这是唯一会自动生成内置方法的装饰器!
# 自动为你添加以下方法(除非你自己显式定义):
# __init__(构造函数)
# __repr__(字符串表示)
# __eq__(相等比较)
# __hash__(可选)
# __lt__、__le__ 等比较方法(如果指定 order=True)
# 减少样板代码
# 适合存储数据的类
# ----------------------------------------------------------------
"""
格式化转换标志:
!r 表示使用 repr() 而不是 str() 来转换变量。
# !s - 使用 str() (默认)
f"{obj!s}" # 等同于 str(obj)
# !r - 使用 repr()
f"{obj!r}" # 等同于 repr(obj)
# !a - 使用 ascii() (只保留 ASCII 字符)
f"{obj!a}" # 等同于 ascii(obj)
# ----------------------------------------
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("张三", 20)
# 使用 !r(repr 表示)
print(f"{p.name!r}") # 输出: '张三' (带引号)
# 不使用 !r(str 表示)
print(f"{p.name}") # 输出: 张三 (不带引号)
# ----------------------------------------
# 示例
name = "中文"
print(f"{name!s}") # 中文
print(f"{name!r}") # '中文'
print(f"{name!a}") # '\u4e2d\u6587'
# 场景1:字符串包含特殊字符
p1 = Person("张\t三", 20)
print(f"{p1.name!r}") # '张\t三' - 清晰看到转义符
print(f"{p1.name}") # 张 三 - 实际显示为制表符
# 场景2:空字符串
p2 = Person("", 20)
print(f"{p2.name!r}") # '' - 明确是空字符串
print(f"{p2.name}") # (空白) - 看不出是什么
# 场景3:数字和字符串的区别
p3 = Person("20", 20) # name 是字符串 "20",age 是整数 20
print(f"name={p3.name!r}, age={p3.age!r}") # name='20', age=20
print(f"name={p3.name}, age={p3.age}") # name=20, age=20 - 看不出类型差异
# ----------------------------------------------------------
字段确实是对象,Python中一切都是对象
可变 vs 不可变:
不可变对象(str, int, tuple等):赋值时创建新对象
可变对象(list, dict, set等):修改时影响原对象
类变量定义在类中但方法外,会被所有实例共享
最佳实践:可变对象的默认值应该在 __init__ 中创建,而不是定义为类变量
"""
"""不使用 dataclass(传统写法):"""
class Person:
def __init__(self, name: str, age: int, email: str = ""):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r}, email={self.email!r})"
def __eq__(self, other):
if not isinstance(other, Person):
return False
return (self.name, self.age, self.email) == (other.name, other.age, other.email)
# -----------------------------------------------------------
@dataclass
class Person:
name:str
age:str
email:str = ''
p1 =Person('zhangsan','16')
p2=Person('lisi','26')
p1.email='995711416@qq.com'
print(p1.email)
print(p2.email)
print(id(p1.email))
print(id(p2.email))
# -------------------------------------------------------------
class Student:
email: str = '' # 不可变对象(字符串)
scores: list = [] # 可变对象(列表)
def __init__(self, name):
self.name = name
# 创建两个实例
s1 = Student('小明')
s2 = Student('小红')
# 测试 email(不可变)
s1.email = 'xiaoming@email.com' # 创建新对象,不影响类变量
print(s1.email) # xiaoming@email.com
print(s2.email) # ''(空字符串)
print(Student.email) # ''(类变量未改变)
# 测试 scores(可变)
s1.scores.append(90) # 修改同一个列表对象
s2.scores.append(85)
print(s1.scores) # [90, 85] - 共享了!
print(s2.scores) # [90, 85] - 共享了!
print(Student.scores) # [90, 85] - 类变量被修改了
# -----------------------------------------------------
class Test:
s: str = '' # 不可变对象
l: list = [] # 可变对象
t1 = Test()
t2 = Test()
# 字符串(不可变)
print(id(t1.s)) # 同一个id
print(id(t2.s)) # 同一个id
t1.s = 'hello' # 这是赋值操作,创建新对象
print(id(t1.s)) # 新的id(不同了)
print(id(t2.s)) # 旧id(未变)
# 列表(可变)
print(id(t1.l)) # 同一个id
print(id(t2.l)) # 同一个id
t1.l.append(1) # 修改操作,不创建新对象
print(id(t1.l)) # id 没变(还是同一个对象)
print(id(t2.l)) # id 没变(还是同一个对象)
# ----------------------------------------------------------------------------------
class Student:
def __init__(self):
# 在 __init__ 中创建新的可变对象
self.email = '' # 每个实例独立的字符串
self.scores = [] # 每个实例独立的列表
self.tags = set() # 每个实例独立的集合
self.info = {} # 每个实例独立的字典
# 现在每个实例都有独立的列表
s1 = Student()
s2 = Student()
s1.scores.append(90)
s2.scores.append(85)
print(s1.scores) # [90]
print(s2.scores) # [85] - 独立!
# field(default_factory=list)
# field(default_factory=list) 是解决 Python 中可变默认参数陷阱的标准做法
# ----------------------------------------------------------------
# 错误示范
"""Python 在定义类时(不是创建实例时)只计算一次默认值。所以 [] 这个列表只创建一次,所有实例共享它。"""
class WrongStudent:
def __init__(self, name, scores=[]): # 危险的默认参数!
self.name = name
self.scores = scores
# 创建两个学生
s1 = WrongStudent("张三")
s2 = WrongStudent("李四")
s1.scores.append(100)
print(s1.scores) # [100]
print(s2.scores) # [100] ← 问题!李四也被影响了
# 原因:s1 和 s2 共享了同一个列表对象
print(id(s1.scores)) # 140234567890
print(id(s2.scores)) # 140234567890 相同的id
# 正确示范如下
# -------------------------------------------------------------------
"""
default_factory=list
等价于:
def __init__(self, name, scores=None):
self.name = name
if scores is None:
self.scores = list() # 调用 list() 创建新列表
else:
self.scores = scores
"""
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
scores: list = field(default_factory=list) # 每次创建实例时调用 list()
s1 = Student("张三")
s2 = Student("李四")
s1.scores.append(100)
print(s1.scores) # [100]
print(s2.scores) # [] ← 独立,不受影响
print(id(s1.scores)) # 140234567890
print(id(s2.scores)) # 140234567999 ← 不同的id
#
from dataclasses import dataclass, field
@dataclass
class Employee:
name: str = field(metadata={
"description": "员工姓名",
"max_length": 50,
"required": True
})
age: int = field(metadata={
"description": "年龄",
"min": 18,
"max": 60,
"unit": "岁"
})
salary: float = field(metadata={
"description": "月薪",
"min": 3000,
"max": 100000,
"currency": "CNY"
})
# 使用
my_emp = Employee("这是一个非常非常长的名字超过了50个字符", 15, 200000)
# 输出:
# name 超过最大长度 50
# age 小于最小值 18
# salary 超过最大值 100000
# 方法1:获取实例所有的字段名和值.此处仅能获取实例的字段,无法获取元数据
print(fields(my_emp))
for field in fields(my_emp):
field_name = field.name
field_value = getattr(my_emp,field_name)
print(field_value)
# ----------------------------------------------
# 方法2;使用实例的 __dict__(简单直接)
for field_name, field_value in my_emp.__dict__.items():
print(f"{field_name}: {field_value}")
# ------------------------------------------------
# 方法3:使用 asdict()(推荐用于序列化)
data = asdict(emp)
for field_name, field_value in data.items():
print(f"{field_name}: {field_value}")
# -------------------------------------------------------
# 方法4:同时获取元数据和值
# 同时获取字段元数据和实例值
for field in fields(my_emp):
print(type(field))
field_name = field.name
field_type = field.type
field_value = getattr(my_emp, field_name)
# 元数据
# 元数据类型 <class 'mappingproxy'> mappingproxy 是一个只读的字典视图,用于保护对象的内部字典不被修改
print(type(field.metadata))
# 通过field.metada获取元数据字典
description_value = field.metadata.get('description', 'No point value assigned.')
print(f"{field_name}: {field_value} (field类型: {field_type}, description: {description_value})")
# ---------------------------------------------------------
# 读取元数据
def validate_employee(my_emp):
# dt = dataclasses.fields(emp)
# print(dt)
# print(dir(dt))
# print(type(dt))
# for item in dt:
# print(item)
# print(type(item))
# print(item.metadata)
# print(item.name)
# print(item.init)
# print(dir(item))
print(emp.__dataclass_fields__.items())
for field_name, field_obj in my_emp.__dataclass_fields__.items():
metadata = field_obj.metadata
value = getattr(my_emp, field_name)
if "max_length" in metadata and len(str(value)) > metadata["max_length"]:
print(f"{field_name} 超过最大长度 {metadata['max_length']}")
if "min" in metadata and value < metadata["min"]:
print(f"{field_name} 小于最小值 {metadata['min']}")
if "max" in metadata and value > metadata["max"]:
print(f"{field_name} 超过最大值 {metadata['max']}")
validate_employee(my_emp)
# 字典、视图、列表的区别
# -----------------------------------------------------------------
# 创建数据
original_dict = {'name': '张三', 'age': 25}
items_view = original_dict.items()
# 1. 显示区别
print(f"字典: {original_dict}") # {'name': '张三', 'age': 25}
print(f"视图: {items_view}") # dict_items([('name', '张三'), ('age', 25)])
print(f"列表: {list(items_view)}") # [('name', '张三'), ('age', 25)]
# 2. 访问区别
print(original_dict['name']) # 张三 - ✅ 直接访问
# print(items_view['name']) # ❌ TypeError: 'dict_items' is not subscriptable
# print(items_view[0]) # ❌ TypeError
# 3. 修改区别
original_dict['age'] = 26 # ✅ 修改原字典
print(items_view) # 视图自动更新显示新值
# dict_items([('name', '张三'), ('age', 26)])
# items_view['age'] = 26 # ❌ 不能直接修改视图
# 4. 转换区别
as_list = list(items_view) # [('name', '张三'), ('age', 26)]
as_dict = dict(items_view) # {'name': '张三', 'age': 26}
print(type(as_list)) # <class 'list'>
print(type(as_dict)) # <class 'dict'>
# ---------------------------------------------------------------------------
from dataclasses import dataclass
@dataclass
class Employee:
name: str
age: int = 18
# __dataclass_fields__ 是字典
fields_dict = Employee.__dataclass_fields__
print(type(fields_dict)) # <class 'dict'>
print(fields_dict) # {'name': Field(...), 'age': Field(...)}
# .items() 返回 dict_items 视图
items_view = fields_dict.items()
for keys,values in items_view:
print(keys)
print(values)
print(type(items_view)) # <class 'dict_items'>
print(items_view) # dict_items([('name', Field(...)), ('age', Field(...))])
# 如果需要可修改的副本
editable_list = list(items_view) # 转为列表
editable_dict = dict(items_view) # 转为字典
"""
default vs default_factory:
default:用于不可变类型(int, str, bool, None)
default_factory:用于可变类型(list, dict, set)或需要动态生成的值
字段顺序:有默认值的字段必须在无默认值字段之后
init=False 的字段:必须在 __post_init__ 中或通过其他方式赋值
# 基础操作
obj.__dict__ # 获取属性字典
obj.__dict__['attr'] # 读取属性
obj.__dict__['attr'] = x # 设置属性
del obj.__dict__['attr'] # 删除属性
'attr' in obj.__dict__ # 检查是否存在
# 常用操作
list(obj.__dict__.keys()) # 获取所有属性名
list(obj.__dict__.values()) # 获取所有属性值
obj.__dict__.copy() # 浅拷贝
dict(obj.__dict__) # 转换为字典
# Python 内置函数
vars(obj) # 等同于 obj.__dict__
"""
"""
@dataclass
class Demo:
# 不可变类型用 default
a: int = 0
b: str = "hello"
c: bool = True
d: float = 3.14
# 可变类型用 default_factory
e: list = field(default_factory=list)
f: dict = field(default_factory=dict)
g: set = field(default_factory=set)
# 需要动态生成的值用 default_factory
h: list = field(default_factory=lambda: [1, 2, 3])
i: int = field(default_factory=lambda: len("something")) # 每次创建时计算
"""
# ---------------------------------------------------------------
"""
带参数的@dataclass装饰器
@dataclass(
init=True, # 是否生成 __init__
repr=True, # 是否生成 __repr__
eq=True, # 是否生成 __eq__(相等比较)
order=False, # 是否生成 __lt__, __le__, __gt__, __ge__(排序比较)
unsafe_hash=False, # 是否生成 __hash__
frozen=False, # 实例是否不可变
slots=False # 是否使用 __slots__
)
# 默认情况:只生成 __init__, __repr__, __eq__
@dataclass
"""
@dataclass
class Rectangle:
width: float
height: float
# 派生字段,不参与初始化
area: float = field(init=False)
perimeter: float = field(init=False)
def __post_init__(self):
self.area = self.width * self.height
self.perimeter = 2 * (self.width + self.height)
r = Rectangle(5, 3)
print(r.area) # 15.0
print(r.perimeter) # 16.0