1、继承
继承是面向对象编程中的一个重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,并且有机会添加新的属性和方法,或者重写父类的方法。
继承的基本语法:
在 Python 中,使用以下语法实现继承:
class ParentClass:# 父类的属性和方法class ChildClass(ParentClass):# 子类继承父类# 可以添加新的属性和方法,也可以重写父类的方法
示例:
class Animal:def __init__(self, name):self.name = namedef speak(self):print(f"{self.name} makes a sound")# Dog 类继承 Animal 类
class Dog(Animal):def speak(self):print(f"{self.name} barks")# 创建父类实例
animal_obj = Animal("Generic Animal")
animal_obj.speak() # 输出: Generic Animal makes a sound# 创建子类实例
dog_obj = Dog("Buddy")
dog_obj.speak() # 输出: Buddy barks
在这个例子中,Dog
类继承了 Animal
类。Dog
类具有 Animal
类的属性和方法,但它还重写了 speak
方法,使其输出狗的特定声音。通过继承,我们可以实现代码的重用和扩展。
调用父类的方法:
在子类中,可以使用 super()
函数调用父类的方法。这样可以在子类中扩展父类的方法而不完全覆盖它。
class Bird(Animal):def speak(self):# 调用父类的 speak 方法super().speak()print(f"{self.name} sings")# 创建子类实例
bird_obj = Bird("Tweetie")
bird_obj.speak()
在这个例子中,Bird
类继承了 Animal
类,并在 speak
方法中通过 super().speak()
调用了父类的 speak
方法,然后添加了新的逻辑。
在面向对象编程中,Python 支持多继承,这意味着一个类可以继承自多个父类。同时,一个父类也可以有多个子类。这种多对多的关系可以带来更灵活的代码组织和结构。
一个子类继承多个父类:
class ParentClass1:# 父类1的属性和方法class ParentClass2:# 父类2的属性和方法class ChildClass(ParentClass1, ParentClass2):# 子类继承父类1和父类2# 可以添加新的属性和方法,也可以重写父类的方法
在这个例子中,ChildClass
继承了 ParentClass1
和 ParentClass2
两个父类。
一个父类拥有多个子类:
class ParentClass:# 父类的属性和方法class ChildClass1(ParentClass):# 子类1继承父类class ChildClass2(ParentClass):# 子类2继承父类class ChildClass3(ParentClass):# 子类3继承父类
在这个例子中,ParentClass
有三个子类:ChildClass1
、ChildClass2
和 ChildClass3
。
多继承的注意事项:
尽管多继承提供了更灵活的代码组织方式,但也需要注意一些潜在的问题:
-
命名冲突: 如果多个父类中有相同名称的属性或方法,可能会导致命名冲突。可以使用
super()
函数来明确调用特定父类的方法,以解决这个问题。 -
复杂性增加: 多继承可能导致代码结构复杂化,理解和维护变得更加困难。在设计时要权衡利弊,确保选择多继承是合理的。
-
Diamond Inheritance Problem: 当一个类同时继承自两个类,而这两个类又继承自同一个类,就会形成“菱形继承”结构,可能引发一些问题。Python 使用 C3 线性化算法来解决这个问题,但需要程序员遵循良好的设计原则。
class Animal:def speak(self):print("Animal speaks")class Bird:def fly(self):print("Bird flies")class Parrot(Animal, Bird):def speak(self):super().speak()print("Parrot talks")# 创建子类实例
parrot = Parrot()# 调用子类方法
parrot.speak() # 输出: Animal speaks \n Parrot talks
parrot.fly() # 输出: Bird flies
2、方法重写
方法重写(Method Overriding)是面向对象编程中的一个概念,它允许子类提供对其父类中已经定义的方法的新实现。重写的方法在子类中具有与父类中方法相同的名称和签名,但是提供了不同的实现。
以下是一个简单的例子,演示了方法重写的概念:
class Animal:def make_sound(self):print("Some generic sound")class Dog(Animal):def make_sound(self):print("Woof! Woof!")class Cat(Animal):def make_sound(self):print("Meow!")# 创建 Animal、Dog 和 Cat 类的实例
animal = Animal()
dog = Dog()
cat = Cat()# 调用各个类的 make_sound 方法
animal.make_sound() # 输出: Some generic sound
dog.make_sound() # 输出: Woof! Woof!
cat.make_sound() # 输出: Meow!
在这个例子中:
Animal
类有一个make_sound
方法,它提供了一个通用的动物声音。Dog
类和Cat
类分别继承自Animal
,并且它们都重写了make_sound
方法,提供了狗和猫特有的声音。
当你调用对象的 make_sound
方法时,实际上调用的是该对象所属类中的方法。因此,dog.make_sound()
调用的是 Dog
类中的 make_sound
方法,而不是从 Animal
类继承的版本。
方法重写是一种实现多态(Polymorphism)的方式,它允许不同的子类对象对相同的方法做出不同的响应。这提高了代码的灵活性和可维护性,允许你根据具体的子类实现定制化的行为。
3、多态
多态(Polymorphism)是面向对象编程中的一个重要概念,它允许使用不同的数据类型执行相同的操作,而这些操作可以根据对象的类型具有不同的行为。简而言之,多态允许不同的对象对同一消息做出不同的响应。
多态有两种实现方式:编译时多态和运行时多态。
-
编译时多态:也称为静态多态或早期绑定。在编译时,根据对象的声明类型来决定调用哪个方法。这种多态是通过函数重载和运算符重载来实现的。
-
运行时多态:也称为动态多态或晚期绑定。在运行时,根据对象的实际类型来决定调用哪个方法。这种多态是通过方法重写(覆盖)和接口实现来实现的。
通过一个示例来说明多态的概念:
class Animal:def speak(self):passclass Dog(Animal):def speak(self):return "Woof!"class Cat(Animal):def speak(self):return "Meow!"class Duck(Animal):def speak(self):return "Quack!"# 函数接受 Animal 对象,并调用其 speak 方法
def make_sound(animal):print(animal.speak())# 创建不同的动物对象
dog = Dog()
cat = Cat()
duck = Duck()# 调用 make_sound 函数,传入不同的动物对象
make_sound(dog) # 输出: Woof!
make_sound(cat) # 输出: Meow!
make_sound(duck) # 输出: Quack!
在这个示例中:
Animal
类定义了一个 方法speak
,但没有提供具体的实现。Dog
、Cat
和Duck
类分别继承自Animal
,并且它们都重写了 方法speak
,提供了狗、猫和鸭的叫声。make_sound
函数接受一个 对象Animal
,并调用其 方法speak
,而不需要知道具体对象的类型。
这就是多态的威力:通过相同的接口处理不同类型的对象,使得代码更加灵活、可扩展和易于维护。
4、object类
在Python中, object
是所有类的基类。所有其他类都直接或间接地继承自 object
类。在面向对象编程中, 提供了一些基本的方法和属性,所有object
类都可以使用这些方法和属性。
一些常见的 object
类的方法包括:
-
__init__
方法: 用于对象的初始化。当一个对象被创建时,__init__
方法会被调用。 -
__str__
方法: 用于返回对象的字符串表示。当使用str(object)
或print(object)
时,实际上是调用了对象的__str__
方法。 -
__repr__
方法: 类似于__str__
方法,用于返回对象的字符串表示。当使用repr(object)
或print(repr(object))在交互式环境中键入对象名并按回车时,会调用__repr__
方法。 -
__eq__
方法: 用于比较对象的相等性。当使用==
运算符进行比较时,实际上是调用了对象的__eq__
方法。
这是一个简单的例子,演示了如何创建一个自定义类并继承自object
:
class CustomClass(object):def __init__(self, name):self.name = namedef __str__(self):return f"CustomClass: {self.name}"def __repr__(self):return f"CustomClass('{self.name}')"def __eq__(self, other):if isinstance(other, CustomClass):return self.name == other.namereturn False# 创建两个 CustomClass 对象
obj1 = CustomClass("Object 1")
obj2 = CustomClass("Object 2")# 调用 __str__ 方法
print(obj1) # 输出: CustomClass: Object 1# 调用 __repr__ 方法
print(repr(obj1)) # 输出: CustomClass('Object 1')# 使用 == 运算符比较对象
print(obj1 == obj2) # 输出: False
5、对象的特殊方法
对象的特殊方法(也称为魔术方法或双下划线方法)是在类中以双下划线(__
)开头和结尾的方法,用于实现对象的特定行为。这些方法可以被Python解释器调用,而不是由开发者直接调用。以下是一些常见的对象特殊方法:
-
__init__(self, ...)
: 该方法在对象创建时被调用,用于进行初始化操作。它在对象被创建后立即执行。 -
__str__(self)
: 当使用str(object)
或print(object)
时调用,返回对象的字符串表示。 -
__repr__(self)
: 类似于__str__
方法,但通常用于生成对象的“开发者友好”字符串表示,通过repr(object)
或在交互式环境中输入对象名而不调用print
时调用。 -
__eq__(self, other)
: 用于比较对象的相等性。当使用==
运算符进行比较时,实际上是调用了对象的__eq__
方法。 -
__lt__(self, other)
,__le__(self, other)
,__gt__(self, other)
,__ge__(self, other)
: 分别用于实现对象的小于、小于等于、大于和大于等于运算符。 -
__add__(self, other)
: 用于实现对象的加法操作。当使用+
运算符时,实际上是调用了对象的__add__
方法。 -
__sub__(self, other)
: 用于实现对象的减法操作。当使用-
运算符时,实际上是调用了对象的__sub__
方法。 -
__len__(self)
: 返回对象的长度。当调用内建函数len(object)
时调用。 -
__getitem__(self, key)
: 用于获取对象的元素,支持索引操作。当使用object[key]
时调用。 -
__setitem__(self, key, value)
: 用于设置对象的元素,支持索引操作。当使用object[key] = value
时调用。 -
__delitem__(self, key)
: 用于删除对象的元素,支持索引操作。当使用del object[key]
时调用。
这些特殊方法允许类自定义其实例的行为,使其可以与Python的内建功能(如len()
、str()
、==
等)交互。通过实现这些方法,开发者可以更好地控制类的实例在不同上下文中的行为。
6、特殊属性
在Python中,除了特殊方法(双下划线开头和结尾的方法)之外,还有一些特殊属性(也称为魔术属性或内置属性),它们以双下划线开头和结尾,用于提供对象的一些元信息或行为。以下是一些常见的特殊属性:
-
__dict__
: 包含对象的命名空间(namespace)的字典,其中存储了对象的属性。 -
__class__
: 表示对象所属的类。 -
__doc__
: 包含对象的文档字符串(docstring)。 -
__name__
: 对于模块,表示模块的名称;对于类,表示类的名称。 -
__module__
: 表示定义对象的模块名称。对于在交互式环境中定义的对象,它可能为"__main__"
。 -
__bases__
: 对于类,表示其基类的元组。 -
__annotations__
: 包含变量注解的字典。 -
__slots__
: 一个类属性,用于限制类实例可以具有的属性,通常是一个字符串列表。 -
__weakref__
: 用于支持弱引用(weak reference)。
这里是一个简单的例子,演示了一些特殊属性的使用:
class MyClass:class_variable = "I am a class variable"def __init__(self, name):self.name = name# 创建一个对象
obj = MyClass("Object")# 访问特殊属性
print(obj.__dict__) # 对象的命名空间
print(obj.__class__) # 对象所属的类
print(obj.__doc__) # 对象的文档字符串
print(obj.__module__) # 定义对象的模块名称
print(obj.__name__) # 对象的名称
print(obj.__bases__) # 对象的基类元组
print(obj.__annotations__) # 变量注解的字典
需要注意的是,虽然这些特殊属性可以访问,但在通常的编程中,直接使用它们的情况相对较少。特殊属性主要用于一些高级用途,例如元编程(metaprogramming)或在特定情况下获取有关对象的信息。在一般情况下,通过调用对象的方法和访问其属性来与对象交互更为常见。
7、类的深拷贝与浅拷贝
深拷贝(deep copy)和浅拷贝(shallow copy)是关于复制对象时涉及到的两个概念,主要涉及到嵌套对象的复制问题。
浅拷贝:
- 浅拷贝创建一个新对象,然后将原对象中的元素(如子对象)的引用复制到新对象中。这意味着新对象中的元素是原对象中元素的引用,而不是新的独立的对象。
- Python中,可以使用
copy
模块的copy()
函数进行浅拷贝。
import copyoriginal_list = [1, [2, 3, 4], 5]
shallow_copy = copy.copy(original_list)print(shallow_copy)
在上面的例子中,original_list
中的第二个元素是一个嵌套的列表。通过浅拷贝后,shallow_copy
中的嵌套列表仍然是原始列表中相同的列表引用。
深拷贝:
- 深拷贝创建一个新对象,然后递归地复制原对象中的所有元素,包括子对象。这意味着新对象是原对象及其所有子对象的独立副本,而不是简单的引用关系。 在
- Python中,可以使用
copy
模块的deepcopy()
函数进行深拷贝。import copyoriginal_list = [1, [2, 3, 4], 5] deep_copy = copy.deepcopy(original_list)print(deep_copy)
在上面的例子中,通过深拷贝后,
deep_copy
中的嵌套列表是原始列表中相同元素的独立副本,而不是引用。
选择深拷贝还是浅拷贝取决于你的需求。如果对象中没有嵌套对象或者你希望嵌套对象也是独立的,那么使用深拷贝是合适的。如果你只是想复制对象的结构而不需要复制嵌套对象的元素,那么浅拷贝可能更有效。
如果还是不明白浅拷贝和深拷贝的区别,下面详细介绍下
赋值,浅拷贝,深拷贝在内存中的行为:
看最后画的内存图后一定能理解它们的区别
class CPU():passclass GPU():passclass Computer():# 计算机有CPU和GPUdef __init__(self,cpu,gpu):self.cpu = cpuself.gpu = gpucpu = CPU()#创建一个CPU对象
gpu = GPU()#创建一个GPU对象#创建一个计算机对象
computer = Computer(cpu,gpu)
#变量(对象)的赋值
computer1 = computer
print(computer,'computer子对象的内存地址:',computer.cpu,computer.gpu)
print(computer1,'computer1子对象的内存地址:',computer1.cpu,computer1.gpu)import copy
computer2 = copy.copy(computer)#computer2是新产生的对象,computer2的子对象cpu,gpu不变
print(computer2,'computer2子对象的内存地址:',computer2.cpu,computer2.gpu)computer3 = copy.deepcopy(computer)#computer3是新产生的对象,computer3的子对象cpu,gpu也会重新创建
print(computer3,'computer3子对象的内存地址:',computer3.cpu,computer3.gpu)
- 赋值: 共享相同的内存空间,修改一个会影响另一个。
- 浅拷贝: 顶层元素有不同的内存地址,但嵌套对象是共享的。
- 深拷贝: 完全独立,包括所有嵌套对象。