构造函数
在Python里,__init__
函数是一个特殊的方法,也被叫做构造函数,它主要用在类的对象创建过程中对对象进行初始化操作。这也意味着,类在实例化时也支持个性化定制,只需要在定义类的同时定义构造函数即可。
通俗类比
想象我们有一家蛋糕店,每一款蛋糕都有自己固定的配方,比如草莓蛋糕,需要用到面粉、鸡蛋、奶油和草莓等材料。当顾客下单要一个草莓蛋糕时,店员就得按照这个固定配方去准备材料并制作蛋糕。
在Python的类和对象的世界里,这个蛋糕店就像是一个类,每一个蛋糕就是类创建出来的对象,而 __init__
函数就相当于这个蛋糕的配方,它规定了创建对象时需要哪些“材料”(也就是参数),并把这些“材料”放到对象里,让对象有自己的初始状态。
class Cake:
def __init__(self, flour, eggs, cream, strawberries):
# 这里的self代表创建出来的蛋糕对象本身
self.flour = flour
self.eggs = eggs
self.cream = cream
self.strawberries = strawberries
def describe(self):
print(f"这个蛋糕用了 {self.flour} 克面粉,{self.eggs} 个鸡蛋,{self.cream} 克奶油和 {self.strawberries} 颗草莓。")
# 创建一个草莓蛋糕对象
strawberry_cake = Cake(200, 3, 150, 5)
# 调用对象的describe方法来描述蛋糕
strawberry_cake.describe()
class Cake
定义了一个名为Cake
的类,它就像蛋糕店,规定了蛋糕的制作规则。-
def __init__(self, flour, eggs, cream, strawberries)
是Cake
类的__init__
函数。这里的self
代表将来创建出来的蛋糕对象本身,而flour
、eggs
、cream
和strawberries
就是创建蛋糕所需的“材料”(参数)。- 在函数内部,
self.flour = flour
等语句把传入的参数值赋给对象的属性,这样对象就有了自己的初始状态。
strawberry_cake = Cake(200, 3, 150, 5)
这行代码创建了一个Cake
类的对象strawberry_cake
,并按照__init__
函数的要求传入了相应的参数,就像按照配方准备材料制作蛋糕一样。strawberry_cake.describe()
调用了对象的describe
方法,输出蛋糕的信息。
重写
如果对父类中的属性或方法不满意的话,可以在子类中写一个同名的去覆盖掉它,这就叫子类对父类的重写。这样,既不需要改动原本父类,又可以拓展子类的新特性。
打个比方说,我们有个汽车工厂,生产过基础款汽车。现在,我们造出了新的升级款,在基础款的基础上改变了鸣笛的声音,真是一个了不起的升级。。
# 定义父类:基础款汽车
class BasicCar:
def __init__(self):
print("基础款汽车初始化完成")
# 定义父类的鸣笛方法
def honk(self):
print("普通的鸣笛声音:嘀嘀")
# 定义子类:升级版汽车
class UpgradedCar(BasicCar):
def __init__(self):
# 直接通过父类名调用初始化方法,即未绑定的父类方法
BasicCar.__init__(self)
print("升级版汽车额外初始化完成")
# 重写父类的鸣笛方法
def honk(self):
print("升级版的鸣笛声音:嘟嘟嘟")
# 创建基础款汽车对象
basic = BasicCar()
# 调用基础款汽车的鸣笛方法
basic.honk()
# 创建升级版汽车对象
upgraded = UpgradedCar()
# 调用升级版汽车的鸣笛方法
upgraded.honk()
ps:未绑定的父类方法就是,父类里定义了的方法但没有和具体的对象关联起来。可以把它想象成一个还没放到具体物品上执行的操作步骤。在 Python 里,我们可以手动把一个对象传递给这个未绑定的方法,让它去操作这个对象。
钻石继承
钻石继承也叫菱形继承,因继承关系连接形似菱形而得名。最简单的钻石继承结构就是一个父类、两个子类,两个子类又共同被新一个子类继承。
想象一下,有一个基础“生物”类,其类下由“怪兽”和“人类”两个子类继承;再然后,有一个“兽人”类,同时继承“怪兽”和“人类”这两个类。这就构建了一个最简单的钻石继承:
# 定义最顶层的基类:生物
class Creature:
def __init__(self):
print("Creature 初始化")
def action(self):
print("生物的通用行为")
# 定义从 Creature 派生的人类类
class Human(Creature):
def __init__(self):
Creature.__init__(self)
print("Human 初始化")
def use_weapon(self):
print("人类使用武器")
# 定义从 Creature 派生的怪物类
class Monster(Creature):
def __init__(self):
Creature.__init__(self)
print("Monster 初始化")
def attack_style(self):
print("怪物野蛮攻击")
# 定义兽人类,继承自 Human 和 Monster
class Orc(Human, Monster):
def __init__(self):
Human.__init__(self)
Monster.__init__(self)
print("Orc 初始化")
# 创建一个兽人对象
orc = Orc()
>>>Creature 初始化
Human 初始化
Creature 初始化
Monster 初始化
Orc 初始化
从输出我们可以看到,很明显的问题:Creature 的初始化方法被调用了两次。在 Human中1次, 在Monster 中一次。显然,调用是重复冗余了的。那么如何避免这个问题?
super函数
在多继承中,super() 会按照方法解析顺序(MRO)来调用父类的方法。MRO 是 Python 为每个类计算出的一个线性顺序,规定了在查找方法时的搜索顺序,这样就可以避免查找进程的重复或混乱。
用上面兽人的例子改写,就得到:
# 定义最顶层的基类:生物
class Creature:
def __init__(self):
print("Creature 初始化")
def action(self):
print("生物的通用行为")
# 定义从 Creature 派生的人类类
class Human(Creature):
def __init__(self):
super().__init__()
print("Human 初始化")
def use_weapon(self):
print("人类使用武器")
# 定义从 Creature 派生的怪物类
class Monster(Creature):
def __init__(self):
super().__init__()
print("Monster 初始化")
def attack_style(self):
print("怪物野蛮攻击")
# 定义兽人类,继承自 Human 和 Monster
class Orc(Human, Monster):
def __init__(self):
super().__init__()
print("Orc 初始化")
# 创建一个兽人对象
orc = Orc()
>>>Creature 初始化
Monster 初始化
Human 初始化
Orc 初始化
这样输出就没有重复了。
同时,在一些场景下,如有必要,super() 函数可以接受两个参数:super(type, object_or_type)。
- type:指定从哪个类开始查找父类方法。
- object_or_type:指定要查找方法的对象或类。
多态
多态是指同一个运算符、函数、对象等在不同场景下能展现出不同的作用。也就是说,使用相同的接口,却可以根据不同的对象执行不同的操作。
运算符多态
运算符在不同的数据类型上可以有不同的行为。例如,+
运算符:
- 当用于数字类型(如整数、浮点数)时,它执行加法运算:
num1 = 5
num2 = 3
result = num1 + num2
print(result) # 输出: 8
- 当用于字符串类型时,它执行字符串拼接操作:
str1 = "Hello"
str2 = " World"
result = str1 + str2
print(result) # 输出: Hello World
内置函数多态
一些内置函数可以处理不同类型的对象,并根据对象类型执行不同的操作。以 len()
函数为例:
- 当传入字符串时,它返回字符串中的字符个数:
string = "Python"
print(len(string)) # 输出: 6
- 当传入列表时,它返回列表中的元素个数:
my_list = [1, 2, 3, 4, 5]
print(len(my_list)) # 输出: 5
- 当传入字典时,它返回字典中键的个数:
my_dict = {'a': 1, 'b': 2, 'c': 3}
print(len(my_dict)) # 输出: 3
类继承与多态
在类的继承体系中,多态通常通过方法重写来实现。子类可以重写父类的方法,从而在调用相同方法时,根据对象的实际类型执行不同的操作。
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
print("汪汪汪")
class Cat(Animal):
def make_sound(self):
print("喵喵喵")
# 创建不同的动物对象
dog = Dog()
cat = Cat()
# 调用相同的方法,根据对象类型执行不同操作
dog.make_sound() # 输出: 汪汪汪
cat.make_sound() # 输出: 喵喵喵
自定义函数实现多态接口
要让自定义函数实现多态接口,可以通过以下几种方式:
基于继承和方法重写
定义一个基类,其中包含一个方法,然后让不同的子类重写这个方法。自定义函数接收基类对象作为参数,在调用该方法时,会根据对象的实际类型执行相应的操作。
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def calculate_area(shape):
return shape.area()
# 创建不同的形状对象
rect = Rectangle(5, 4)
circle = Circle(3)
# 调用自定义函数,根据对象类型计算不同形状的面积
print(calculate_area(rect)) # 输出: 20
print(calculate_area(circle)) # 输出: 约 28.27
基于鸭子类型(Duck Typing)
鸭子类型是一种编程风格,它不关注对象的具体类型,而是关注对象是否具有特定的方法或属性。只要对象具有所需的方法,就可以被传递给函数进行处理。
class Person:
def speak(self):
print("我会说话")
class Robot:
def speak(self):
print("我会发出电子声音")
def let_it_speak(obj):
obj.speak()
# 创建不同的对象
person = Person()
robot = Robot()
# 调用自定义函数,根据对象的方法执行不同操作
let_it_speak(person) # 输出: 我会说话
let_it_speak(robot) # 输出: 我会发出电子声音
通过上述方式,自定义函数可以实现多态接口,能够处理不同类型的对象,提高代码的灵活性和可扩展性。