# 创建和使用类
# 创建类
from mimetypes import inited
from pdb import post_mortem
from unittest import skipIf
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}")