五、类
5.1 定义类
- 使用
class
关键字定义一个类,类名通常采用首字母大写的驼峰命名法class Person: pass
5.2 构造函数
-
基本语法
class Person: def __init__(self, name, age): # 定义构造函数 self.name = name # 初始化 name 属性 self.age = age # 初始化 age 属性
-
python中,有且仅有一个构造函数。
-
构造函数不能有返回值
-
构造函数第一个参数必须是self
-
可以通过
super().__init__()
调用父类的构造函数-
如果在子类中没有定义
__init__
方法,会自动调用父类的__init__
方法。 -
但是如果子类定义类
__init__
方法,不主动调用父类的构造函数,父类的构造函数不会被调用。class Animal: def __init__(self, name): print(f"Animal initialized with name: {name}") self.name = name class Dog(Animal): def __init__(self, breed): print(f"Dog initialized with breed: {breed}") self.breed = breed # 创建 Dog 对象,不会调用 Animal 的构造函数 dog = Dog("Labrador") dog = Dog( "Labrador") print(dog.name) # 这里会报错'Dog' object has no attribute 'name'
-
-
如果有多重继承,可以指定调用某个父类的构造函数,
class D(B, C): def __init__(self): B.__init__(self) # 调用 B 的构造函数 C.__init__(self) # 调用 C 的构造函数 print("D的构造函数")
- 也可以直接
super().__init__()
调用父类的构造函数,那么,先调用谁呢?- 调用顺序是由
MRO
决定的,怎么查看调用顺序?print(D.__mro__)
- 调用顺序是由
- 也可以直接
-
__new__
方法,是创建一个对象,而上述的__init__()
是初始化对象。__new__
必须返回一个实例对象,返回的实例对象自动传递给__init__
5.3 属性
-
实例属性:在
__init__
方法中定义,属于某个具体的实例 -
类属性:属于类本身,所有实例共享同一属性。
-
如何理解类属性?
-
有一个
Person
类class Person: species = "Human" # 类属性 def __init__(self, name, age): self.name = name # 实例属性 name self.age = age # 实例属性 age
-
首先,类属性有点像C++的静态成员变量,
p = Person("ouyang", 18) p2 = Person("ouyang2", 19) print(p.species) # Human print(p2.species) # Human Person.species = "People" print(p.species) # People print(p2.species) # People
-
但是,python中,修改了其中一个实例对象的类属性后,并不会影响其他实例对象的类属性,这怎么理解?
p = Person("ouyang", 18) p2 = Person("ouyang2", 19) print(p.species) # Human print(p2.species) # Human p2.species = "People" print(p.species) # Human print(p2.species) # People
-
第5行,实际上是p2创建了一个实例属性,同样是
species
,以后再访问species
,会是p2的实例属性,而不再访问类属性。所以,p2修改的是自己的实例属性,自然不会影响到其他的实例对象,同样也不会影响到类属性。
-
5.4 方法
-
实例方法:第一个参数必须是
self
,并且访问属性时,必须使用self.属性
的方式访问。-
实例方法只能实例对象访问吗?类能访问吗?答案是可以的,因为python中,「类」是一个特殊的「实例对象」,是python自动帮我们创建好的,如下代码
class A : def __init__(self) -> None: self.name = "A" def hi(self): print("hello") A.hi(A) # hello
-
但是,不建议这样使用,因为实例方法可以使用实例属性,但是「类」,没有实例属性,如下
class A : def __init__(self) -> None: self.name = "A" def hi(self): print("hello", self.name) A.hi(A) # 报错 AttributeError: type object 'A' has no attribute 'name'
-
-
类方法:第一个参数必须是
cls
,并且需要通过@classmethod
装饰器来定义,只能操作类属性class Person: species = "Human" # 类属性 def __init__(self, name, age): self.name = name # 实例属性 name self.age = age # 实例属性 age @classmethod def Person_greet(cls): print("hello person") cls.species = "People" # 改变了类属性 会影响到其他实例对象的类属性
-
静态方法:绑定到类上,通过
@staticmethod
装饰器来定义,但是不访问属性,可以认为就是类中的普通函数。@staticmethod def Person_greet(cls): print("hello person") cls.species = "People"
-
类中的普通函数:属于这个类的函数,但是不能访问实例属性,也不能访问类属性,和静态方法的主要区别就是,静态方法能够通过实例对象访问,但是类中的普通函数只能通过类名来访问。
class Person: def Person_greet(): print("hello person") Person.Person_greet() # hello person
-
注意,方法名不要以
__
双下划线开头,这有别的含义。
5.5 继承
-
基本语法
class Son(Father):
Son类继承了Father类 -
重写
- 子类的方法签名(方法名、参数列表)必须与父类方法一致。
- 直接调用,会调用子类的,如果想要调用父类的可以使用
super().方法名
-
super()
函数-
如果有多个父类,
super()
到底是哪个父?有一个叫做方法解析顺序(MRO),决定了父类方法的调用顺序。可以通过print(类名.mro())
来查看 -
使用
super()
调用父类的构造函数时,会根据MRO,调用所有父类的构造函数。但是,调用一般方法(假设所有父类都有),只会调用MRO顺序的第一个父类。 -
假如我不希望使用MRO顺序呢?我希望调用某个父类,看下面例子
class A: def say(self): print("A") class B: def say(self): print("B") class C(B): def say(self): print("C") class D(A, C) : def say(self): B.say(self) # 虽然D只继承了A、C,但是C继承了B,所以可以调用B print("D"); d = D(); d.say() # 打印B D、
-
5.6 封装
- 在属性前,加前缀
_
表示「保护权限」,加前缀__
表示「私有属性」- 保护权限,不是强制性的规则,起到「提醒作用」,也就是说,在类外部仍然可以访问。
- 私有权限,禁止外部使用,但是可以通过
对象名._类名__私有属性名
来访问。
5.7 特殊方法
-
__str__(self)
-
当使用print,或者str函数作用于该对象时,获取该对象的字符串形式
-
例子
class Person: _species = "Human" # 类属性 def __init__(self, name, age): self.__name = name # 实例属性 name self.age = age # 实例属性 age def __str__(self) : return f"Person(name={self.__name}, age={self.age})" p = Person("ouyang", 18) print(p) # Person(name=ouyang, age=18)
-
还有个类似的
__repr__
:在调试和交互模式下显示对象的更精确或有用的描述。实现__repr__
方法可以使对象在输出时显示定制的信息,而不是直接使用对象的内存地址。
-
-
__len__(self)
- 当调用
len(实例对象)
时,会调用__len__
方法,如果没有实现,则会抛出异常
- 当调用
-
__getitem__(self, key)
- 使对象能够像访问「字典」一样,通过key,得到相应的value
-
__setitem__(self, key, value)
-
使对象能够像访问「字典一样」,通过key,设置value
-
class Person: _species = "Human" # 类属性 def __init__(self, name, age): self.__name = name # 实例属性 name self.age = age # 实例属性 age def __getitem__(self, key): if key == "name": return self.__name else: return self.age def __setitem__(self, key, val): if key == "name": self.__name = val else : self.age = val p = Person("ouyang", 18) p["name"] = "ouyang2" print(p["name"]) # ouyang2
-
-
__del__(self)
- 析构器,对象销毁前的清理操作,但是并不建议依赖
__del__
进行内存管理,手动调用del obj
并不会立刻触发__del__
,而是垃圾回收机制决定何时释放内存。 - 可以使用上下文管理器替换
__del__
,更清晰和安全
- 析构器,对象销毁前的清理操作,但是并不建议依赖
-
上下文管理器
-
__enter__
:在进入with
语句代码块时调用,负责设置或分配资源,并且可以返回资源对象 -
__exit__
:在离开with
语句代码块时调用,负责清理或释放资源。无论代码块是否正常结束或出现异常,__exit__
都会执行。 -
例子
class FileHandler: def __init__(self, filename, mode): self.file = open(filename, mode) def __enter__(self): return self.file # 返回文件对象供 with 语句内使用 def __exit__(self, exc_type, exc_value, traceback): self.file.close() # 确保在 with 代码块后关闭文件 # 使用自定义上下文管理器 with FileHandler("example.txt", "w") as file: file.write("Hello, World!")
-
-
__iter__(self)
- 当一个对象实现了
__iter__
方法,它就可以被 Python 的内置函数iter()
调用,并且可以用于for
循环、生成列表、使用list()
等构造函数。 - 返回值:
__iter__
方法必须返回一个迭代器对象,也就是一个实现了__next__
方法的对象。
- 当一个对象实现了
-
__next__(self)
-
通常
__iter__
和__next__
搭配使用。 -
__next__
方法是实际提供数据的地方。每次调用__next__
时,返回当前值,并将迭代器的状态更新为下一个值。它在没有更多数据可返回时抛出StopIteration
异常,表示迭代结束。 -
在使用
for
循环时,Python 首先会调用__iter__
方法获取一个迭代器对象,然后反复调用__next__
来逐步获取值。 -
例子
class Counter: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self # 返回自身作为迭代器 def __next__(self): if self.current >= self.end: raise StopIteration # 无更多值时停止迭代 self.current += 1 return self.current - 1 # 使用 Counter 迭代器 counter = Counter(1, 4) for num in counter: print(num) # 输出 1, 2, 3
-
-
__add__(self, other)
- 自定义类的加法
-
__sub__(self, other)
-
自定义类的减法
-
类似的还有
__mul__
乘法、__truediv__
除法 -
例子
class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __sub__(self, other): return Vector(self.x - other.x, self.y - other.y) def __mul__(self, other): return Vector(self.x * other, self.y * other) def __truediv__(self, other): return Vector(self.x / other, self.y / other) # 打印时 def __repr__(self): return f"Vector({self.x}, {self.y})" v1 = Vector(3, 4) print(v1 + v1) # Vector(6, 8) print(v1 - v1) # Vector(0, 0) print(v1 * 3) # Vector(9, 12) print(v1 / 2) # Vector(1.5, 2.0)
-
-
__eq__(self, other)
- 自定义类的
==
操作,返回一个布尔值
- 自定义类的
-
__lt__(self, other)
-
自定义类的
<
操作,返回一个布尔值 -
类似的还有
__le__
小于等于、__gt__
大于、__ge__
:大于等于、__ne__
:不等于 -
例子
class Person: def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): return self.age == other.age def __lt__(self, other): return self.age < other.age def __le__(self, other): return self.age <= other.age def __gt__(self, other): return self.age > other.age def __ge__(self, other): return self.age >= other.age def __ne__(self, other): return self.age != other.age p1 = Person("Alice", 30) p2 = Person("Bob", 25) print(p1 > p2) # 输出: True print(p1 != p2) # 输出: True
-
-
__call__(self, ...)
-
允许类的实例像函数一样被调用(C++中的仿函数)。
-
语法
class MyClass: def __call__(self, *args, **kwargs): pass
-
例子
class Greeter: def __init__(self, greeting): self.greeting = greeting def __call__(self, name): return f"{self.greeting}, {name}!" # 创建 Greeter 类的实例 greet = Greeter("Hello") # 使用像调用函数一样调用实例 print(greet("Alice")) # 输出: Hello, Alice!
-
5.8 多态
-
先看一个例子
class Dog: def speak(self): print("Dog barks") class Cat: def speak(self): print("Cat meows") class Human: def speak(self): print("Human talks") # 使用不同类型的对象,只要它们都有相同的方法,Python就能调用 def make_sound(animal): animal.speak() make_sound(Dog()) # Dog barks make_sound(Cat()) # Cat meows make_sound(Human()) # Human talks
- 有一个函数
make_sound()
,能够接收所有,包含speak()
方法的类,然后调用speak()
方法。 - 总感觉不伦不类的,因为对比C++,父类指针指向子类对象,但是因为python一个变量,类型是能够变的,所以这里的animal,本来就可以接收任何数据类型,所以,python的多态,没有C++那么多限制,比如,子类必须要「实现父类的所有抽象方法」。
- 所以,python多态总觉得「不是真正的多态。。。」
- 有一个函数
5.9 抽象类
-
抽象类是一种特殊的类,用来定义一组方法,子类必须实现这些方法。抽象类本身不能被实例化。它通过定义抽象方法来强制子类去实现某些方法。抽象类有两个主要特性:
- 抽象方法:这是没有实现的函数,它只有声明没有方法体。子类必须实现全部抽象方法,否则子类也会被视为抽象类,不能实例化。
- 不能实例化:你不能直接创建一个抽象类的对象,必须通过继承它的子类来创建实例。
-
使用
@abstractmethod
装饰器来标记一个方法为抽象方法 -
例子
from abc import ABC, abstractmethod class Animal(ABC): # Animal 是一个抽象类 @abstractmethod def make_sound(self): pass # 这个方法在抽象类中没有实现 class Dog(Animal): # Dog 是 Animal 的子类 def make_sound(self): return "Woof" class Cat(Animal): # Cat 是 Animal 的子类 def make_sound(self): return "Meow" # 实例化 dog = Dog() print(dog.make_sound()) # 输出: Woof cat = Cat() print(cat.make_sound()) # 输出: Meow