1
0
wiki/Tech/programming-language/Python/入门/面对对象.md
2023-11-09 17:30:33 +08:00

472 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 面对对象
description: Python 面对对象
keywords:
- Python
- 面对对象
tags:
- Python/入门
sidebar_position: 6
author: 7Wate
date: 2023-08-03
---
面向对象编程Object-Oriented ProgrammingOOP是一种编程范式或模型以「对象」作为核心来设计和实现软件。这种方法主要的目标是将数据和处理数据的函数结合在一起封装成独立的软件模块我们称之为「对象」。面向对象编程被广泛应用于多种编程语言中包括 Python。
## 基本概念
### 理念和用途
**面向对象编程是在更高的抽象层次上思考问题的一种方式。**面向对象编程的主要理念是,将现实世界中的对象抽象化,将对象的属性(也叫状态)和行为(也叫方法)封装在一起。面向对象编程的思考方式,更贴近我们对现实世界的认知。
面向对象编程的主要优点包括:
1. **代码复用性**:类的定义一旦完成,就可以在任何地方创建其对象,复用类的属性和方法,而无需重复编写代码。通过类之间的继承关系,子类可以继承父类的代码,从而实现代码的复用。
2. **代码的可维护性和可扩展性**:面向对象编程的封装特性,保护了对象内部的数据,使得代码更安全,更易于维护。通过类的继承和多态,可以轻松实现对现有代码的扩展。
### 特点
**面向对象编程有三个主要的特点:封装、继承、和多态。**
- **封装**:封装是指将对象的状态(数据)和行为(方法)包装在一起,隐藏对象的内部实现细节,仅对外提供必要的接口。这样可以保护对象的内部状态,提高代码的安全性,也提高了代码的可维护性。
- **继承**:继承是指子类可以继承父类的属性和方法。继承让我们可以在父类的基础上进行扩展,实现代码的复用,提高代码的可扩展性。
- **多态**:多态是指不同类的对象对同一消息会做出不同的响应。多态提供了统一的接口,使得我们可以使用一致的方式处理不同类的对象,提高了代码的灵活性。
## Python 中的类和对象
在 Python 中,我们使用 `class` 关键字来定义类。类是一种数据类型,它定义了一种新的对象,这种对象具有自己的属性和方法。
### 类和对象的概念
- **类 (Class)** 类是对象的蓝图或原型。你可以想象成是创建对象的模板,它定义了特定类型的对象的属性和方法。
```python
# 一个简单的 Python 类的例子
class Dog:
pass
```
- **对象 (Object)**:对象是类的实例。具体来说,当我们根据类的定义创建了一个实例后,这个实例就是一个对象。每个对象都具有类定义的属性和方法。
```python
# 创建Dog类的一个实例
my_dog = Dog()
```
我们可以通过创建类的实例,也就是对象,来使用类的属性和方法。
### 类的属性和方法
类的属性和方法是类的主要组成部分。属性用于描述类和其实例的状态,方法用于描述类和其实例可以进行的操作。
在 Python 中,我们可以在类中定义两种类型的属性和方法:
- **实例属性和方法**:实例属性和方法属于类的实例。每个实例都有自己的实例属性,这些属性与其他实例的属性互不影响。实例方法可以通过实例来调用,它可以访问和修改实例的属性。
```python
# 在Python类中定义实例属性和方法的例子
class Dog:
def __init__(self, name):
self.name = name # 实例属性
def bark(self): # 实例方法
return f"{self.name} says woof!"
```
- **类属性和方法**:类属性和方法是属于类本身的,所有的实例都会共享同一个类属性。类方法可以通过类名直接调用,也可以通过实例调用,但是它不能访问和修改实例属性。
```python
# 在Python类中定义类属性和方法的例子
class Dog:
species = "Canis familiaris" # 类属性
@classmethod
def description(cls): # 类方法
return "This class represents a dog."
```
### `__init__`
在 Python 中使用 `__init__` 方法来初始化类的实例。这个方法会在创建实例时自动调用,我们可以在这个方法中设置实例的初始状态,也就是定义实例的属性。
`__init__` 方法的第一个参数始终是 `self`,代表了实例本身。在 `__init__` 方法中,我们使用 `self.属性名` 来定义实例属性。
```python
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
```
## 封装
### 概念和用途
封装这是面向对象编程OOP中的基本概念之一其目标是通过将数据对象的状态和对象的行为组合在一起来**隐藏或封装对象内部的详细信息**。简单地说,封装就是把客户端代码(对象的使用者)从对象的内部细节中解耦出来。
封装的主要优势包括:
1. **提高了代码的安全性**:封装可以防止对象状态的无意义或不合适的修改。只有通过定义好的方法(有时被称为 getter 和 setter )才能修改状态,这些方法可以控制对对象状态的更改,从而保证其正确和一致。
2. **提高了代码的可维护性**:由于客户端代码从对象的内部实现中解耦出来,我们可以自由地改变对象的内部实现,而不会影响到使用该对象的代码。
### Python 中实现封装
#### 数据封装
在 Python 中,我们可以通过使用私有属性(私有变量)实现数据封装。**Python 中的私有属性名字前面带有两个下划线(例如 `__name`)。**这些属性只能在类的内部被访问,不能在类的外部直接访问,从而实现了数据的封装。
```python
class Dog:
def __init__(self, name, age):
# __name和__age是私有属性我们不能在类的外部直接访问
self.__name = name
self.__age = age
```
#### 方法封装
Python 同样可以通过私有方法实现方法的封装。私有方法的名字也以两个下划线开始,它们只能在类的内部被调用,不能在类的外部直接调用。
```python
class Dog:
def __init__(self, name, age):
self.__name = name
self.__age = age
# __bark 是一个私有方法,我们不能在类的外部直接调用
def __bark(self):
return f"{self.__name} says woof!"
```
尽管私有属性和方法不能在类的外部直接访问,但是我们可以提供公共的 getter 和 setter 方法,让外部代码能够以受控的方式读写这些私有属性。这样我们就可以保证封装的安全性,同时也提供了一定的灵活性。
```python
class Dog:
def __init__(self, name, age):
self.__name = name
self.__age = age
# getter for __name
def get_name(self):
return self.__name
# setter for __name
def set_name(self, name):
self.__name = name
# getter for __age
def get_age(self):
return self.__age
# setter for __age
def set_age(self, age):
self.__age = age
# __bark 是一个私有方法,我们不能在类的外部直接调用
def __bark(self):
return f"{self.__name} says woof!"
# 公共方法可以调用私有方法
def public_bark(self):
return self.__bark()
```
## 继承
### 继承的概念和用途
继承是面向对象编程中的核心概念之一。继承允许我们定义一个新的类子类或派生类来继承现有类父类或基类的属性变量和方法函数。这样我们就可以在新类中复用父类的代码避免了代码的重复同时也可以在新类中添加新的方法或对父类的方法进行重写override
继承的主要优点包括:
1. **代码复用**:子类继承了父类的所有属性和方法,所以我们可以通过创建子类,来复用和扩展父类的代码,减少了代码的重复。
2. **易于维护和修改**:由于子类可以重写父类的方法,这就意味着我们可以在不改变父类的情况下,改变子类的行为。这使得代码更容易维护和修改。
### Python 中实现继承
#### 单继承
在 Python 中,我们可以通过在类定义时的括号中写入父类的名字来创建子类。新定义的类将会继承父类的所有属性和方法。
```python
class Animal:
def __init__(self, name):
self.name = name
# Dog 是 Animal 的子类,它继承了 Animal 的所有属性和方法。
class Dog(Animal):
pass
```
#### 多继承
Python 支持多继承,意味着一个类可以继承多个父类。但这可能会导致一些复杂性,如“菱形问题”。
```python
class Swimming:
def swim(self):
return "Swims!"
class Flying:
def fly(self):
return "Flies!"
class Duck(Swimming, Flying):
pass
duck = Duck()
print(duck.swim()) # Outputs: Swims!
print(duck.fly()) # Outputs: Flies!
```
#### `super` 函数
在 Python 中,`super` 函数是一个内置函数,它可以用来调用父类的方法。这在你需要在子类中扩展父类的方法时特别有用。
```python
class Animal:
def __init__(self, name):
self.name = name
# Dog 的 __init__ 方法中使用了 super().__init__(name) 来调用 Animal 的 __init__ 方法。
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的构造方法
self.breed = breed # 新增的属性
```
## 多态
### 多态的概念和用途
在面向对象编程OOP多态性是一种允许一个实体采取多种形态的能力。更具体地说多态是指通过同一个接口使用不同的实例可以产生不同的结果。这使得我们可以在运行时确定我们正在使用的对象类型然后执行相应的操作。
多态的优势主要有两点:
1. **提高代码的灵活性**:多态能够让我们以更一般的方式编写代码,处理更广泛的数据类型,而不仅仅是特定的单一数据类型。这使得我们的代码更加灵活和可维护。
2. **提高代码的可扩展性**:如果我们想要添加新的数据类型,我们只需要确保它们遵循现有的接口规定。这使得我们的代码更易于扩展和改进,而无需修改大量现有代码。
3. **促进了类之间的解耦**:多态允许系统在不修改现有代码的情况下,添加新的对象类型。
### Python 中实现多态
Python 是一种动态类型语言,意味着我们不需要明确地声明对象的类型。这就让我们在 Python 中能够非常容易地实现多态。以下是 Python 中实现多态的一些方法:
#### 方法重写
在 Python 中,子类可以重写父类的方法。这就意味着,当我们在子类中调用一个父类的方法时,将会执行子类中定义的版本,而非父类中的原始版本。这使得我们可以改变子类中方法的行为,以符合我们的需求。
```python
class Animal:
def sound(self):
return "Generic animal sound"
# Dog类重写了Animal类的sound方法所以当我们调用Dog实例的sound方法时会返回"Woof!",而不是"Generic animal sound"。
class Dog(Animal):
def sound(self):
return "Woof!"
```
#### Duck Typing
Python 支持一种编程概念叫做 duck typing这也是实现多态的一种方法。Duck typing 的核心思想是:如果一个对象能够像鸭子一样走路,像鸭子一样叫,那么我们就可以认为它是鸭子。对于 Python 来说,如果一个对象有我们需要的方法,我们就可以使用它,而不管它是什么类型的对象(运行时类型确定)。
```python
def animal_sound(animal):
# 这个函数只关心对象是否有sound方法而不关心对象的具体类型
return animal.sound()
print(animal_sound(Dog())) # 输出"Woof!"
print(animal_sound(Animal())) # 输出"Generic animal sound"
```
## 抽象类和接口
### 抽象类和接口的概念
抽象类是一种特殊的类,它不能被实例化,只能被继承。抽象类中可以定义抽象方法,这些方法在抽象类中没有实现,在子类中必须实现。
接口是一种特殊的抽象类,它只定义了一组方法的签名,没有提供任何实现。接口定义了一组行为,任何实现了这些行为的类都可以说是实现了这个接口。
### Python 的 Abc 模块
在 Python 中,我们使用 abc 模块来创建抽象类和接口。abc 模块提供了 `ABC` 基类和 `abstractmethod` 装饰器,我们可以使用它们来定义抽象类和抽象方法。
### Python 中使用抽象类和接口
#### 创建抽象类
我们可以使用 `ABC` 基类和 `abstractmethod` 装饰器来创建抽象类。
```Python
from abc import ABC, abstractmethod
# AbstractAnimal 是一个抽象类,它有一个抽象方法 sound。
class AbstractAnimal(ABC):
@abstractmethod
def sound(self):
pass
```
#### 创建接口
在 Python 中,接口的概念可以通过完全由抽象方法构成的抽象类来实现。与抽象类类似,我们使用 ABC 基类和 abstractmethod 装饰器来定义接口。
```python
from abc import ABC, abstractmethod
# AnimalBehaviour 是一个接口它定义了两个抽象方法eat 和 sleep。
class AnimalBehaviour(ABC):
@abstractmethod
def eat(self):
pass
@abstractmethod
def sleep(self):
pass
```
#### 实现接口
我们可以通过继承抽象类并实现其所有的抽象方法来实现接口。
```Python
class Dog(AbstractAnimal, AnimalBehaviour):
def sound(self):
return "Woof!"
def eat(self):
return "The dog is eating."
def sleep(self):
return "The dog is sleeping."
```
## 异常处理
### 异常的概念
在 Python 中异常是程序运行期间发生的错误事件它会中断常规程序的执行流程。当程序执行过程中遇到错误时Python 解释器会自动引发raise一个异常。
异常是一种特殊的对象它包含了有关错误的详细信息例如错误类型和错误发生时的程序状态。Python 内置了很多标准异常类型,如 `ValueError``TypeError``IndexError` 等,每种类型都对应了一类特定的错误。
我们可以使用异常处理机制来捕获catch异常。通过处理异常我们可以决定在出现错误时程序如何响应而不是让程序直接崩溃。这对于构建健壮和稳定的程序至关重要。
```mermaid
graph TB
BaseException --> SystemExit
BaseException --> KeyboardInterrupt
BaseException --> GeneratorExit
BaseException --> Exception
Exception --> StopIteration
Exception --> ArithmeticError
ArithmeticError --> FloatingPointError
ArithmeticError --> OverflowError
ArithmeticError --> ZeroDivisionError
Exception --> AssertionError
Exception --> AttributeError
Exception --> EOFError
Exception --> ImportError
Exception --> ModuleNotFoundError
Exception --> LookupError
LookupError --> IndexError
LookupError --> KeyError
Exception --> NameError
NameError --> UnboundLocalError
Exception --> OSError
OSError --> IOError
Exception --> RuntimeError
RuntimeError --> NotImplementedError
RuntimeError --> RecursionError
Exception --> SyntaxError
Exception --> SystemError
Exception --> TypeError
Exception --> ValueError
Exception --> UnicodeError
```
### 异常处理关键字
Python 中处理异常的关键字主要有四个:`try``except``finally``else`。
- `try`: 你可以把可能会引发异常的代码放在 `try` 块中。
- `except`: 当 `try` 块中的代码引发异常时,`except` 块中的代码将被执行。你可以在 `except` 后面指定你想捕获的异常类型。一个 `try` 块后面可以跟随多个 `except` 块,用于捕获不同类型的异常。
- `finally`: 无论 `try` 块中的代码是否引发异常,`finally` 块中的代码都将被执行。这常用于执行一些无论异常是否发生都需要执行的清理操作,如关闭文件。
- `else`: 如果 `try` 块中的代码没有引发异常,那么 `else` 块中的代码将被执行。`else` 关键字是可选的。
```Python
try:
# 这里是可能抛出异常的代码
result = 10 / 0
except ZeroDivisionError:
# 这里是处理ZeroDivisionError异常的代码
print("Cannot divide by zero!")
else:
# 这里是try代码块成功执行后的代码
print("Operation successful.")
finally:
# 这里是无论是否发生异常都会执行的代码
print("This is the finally block.")
```
### Python 内置标准异常
| 异常名 | 描述 |
| --------------------- | ----------------------------------- |
| `BaseException` | 所有异常的基类 |
| `SystemExit` | 解释器请求退出 |
| `KeyboardInterrupt` | 用户中断执行 (通常是输入^C) |
| `Exception` | 常规错误的基类 |
| `StopIteration` | 迭代器没有更多的值 |
| `GeneratorExit` | 生成器 (generator) 发生异常来通知退出 |
| `SystemError` | 解释器发现内部错误 |
| `SyntaxError` | Python 语法错误 |
| `IndentationError` | 缩进错误 |
| `TabError` | Tab 和空格混用 |
| `NameError` | 未声明/初始化对象 (没有属性) |
| `UnboundLocalError` | 访问未初始化的本地变量 |
| `AttributeError` | 对象没有这个属性 |
| `TypeError` | 对类型无效的操作 |
| `AssertionError` | 断言语句失败 |
| `ImportError` | 导入模块/对象失败 |
| `ModuleNotFoundError` | 找不到模块 |
| `LookupError` | 无效数据查询的基类 |
| `IndexError` | 序列中没有此索引 (index) |
| `KeyError` | 映射中没有这个键 |
| `ValueError` | 传入无效的参数 |
| `UnicodeError` | Unicode 相关的错误 |
| `ArithmeticError` | 数学运算基类 |
| `FloatingPointError` | 浮点计算错误 |
| `OverflowError` | 数值运算超出最大限制 |
| `ZeroDivisionError` | 除 (或取模) 零 (所有数据类型) |
| `EnvironmentError` | 操作系统错误的基类 |
| `IOError` | 输入/输出操作失败 |
| `OSError` | 操作系统错误 |
| `EOFError` | 没有内建输入,到达 EOF 标记 |
| `RuntimeError` | 一般的运行时错误 |
| `NotImplementedError` | 尚未实现的方法 |
| `RecursionError` | 超过最大递归深度 |
### 自定义异常的创建和抛出
Python 允许你创建自定义的异常类型。为了创建自定义的异常类型,你需要定义一个类,它继承自 `Exception` 类或者它的子类。在你的类中,你可以定义任何你需要的方法,但是通常,自定义的异常类型会非常简单,只提供一些基本的信息。
要抛出你自定义的异常,你可以使用 `raise` 关键字。在 `raise` 语句后面,你可以指定要抛出的异常类型,以及一个可选的错误消息。
```Python
class CustomError(Exception):
"""自定义的异常类型"""
def __init__(self, message):
self.message = message
try:
# 抛出自定义的异常
raise CustomError("This is a custom error.")
except CustomError as e:
# 捕获并处理自定义的异常
print("Caught an exception:", e.message)
```