面向对象编程
面向对象(Object-Oriented)和面向过程(Procedural)是两种不同的编程范式,它们在软件设计和实现上有一些根本的区别。
面向过程编程是基于函数和过程的编程范式。在这种范式中,程序被组织为一系列的函数或过程,这些函数或过程按照一定的顺序执行,完成特定的任务。面向过程编程的特点包括:
- 函数和过程:程序由函数和过程组成,它们是执行特定任务的基本单元。
- 数据隐藏:数据和函数通常是分离的,数据隐藏在函数内部。
- 模块化:程序被分解为多个模块,每个模块负责完成一个特定的功能。
- 自顶向下设计:从整体到局部,先设计高层次的结构,再逐步细化到低层次的实现。
面向对象编程是基于对象和类的编程范式。在这种范式中,程序被组织为对象,对象是具有数据和行为的实体。面向对象编程的特点包括:
- 封装:将数据和操作数据的函数封装在一起,隐藏内部实现细节,对外只提供公共接口。
- 继承:允许一个类继承另一个类的属性和方法,实现代码的复用。
- 多态:允许不同类的对象对同一消息作出响应,通过方法重载和动态绑定实现。
- 自底向上设计:从具体的对象和类开始,逐步构建复杂的系统。
- 设计哲学:面向过程强调的是功能的分解和过程的执行,而面向对象强调的是对象的创建和交互。
- 数据与行为:面向过程通常将数据和函数分离,而面向对象将数据和操作数据的函数封装在一起。
- 代码复用:面向对象通过继承和多态实现代码复用,而面向过程则更多地依赖于函数和过程的重用。
- 系统构建:面向过程倾向于自顶向下设计,而面向对象倾向于自底向上设计。在实际编程中,这两种范式并不是互相排斥的,很多现代编程语言都支持这两种范式,并且可以根据具体需求灵活运用。例如,在 Python 中,你可以使用面向对象的方式编写类和对象,也可以使用面向过程的方式编写函数和过程。
面向过程的思维方式是分析综合,面向对象的思维方式是构造。 简单来说:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
-
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
-
**方法:**类中定义的函数。
-
**类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
-
**数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。
-
**方法重写:**如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
-
**局部变量:**定义在方法中的变量,只作用于当前实例的类。
-
**实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
-
**继承:**即一个派生类(
derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例如,Dog是一个Animal)。 -
**实例化:**创建一个类的实例,类的具体对象。
-
**对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。
1.9.1 类的定义和使用
在Python中,类(Class)是面向对象编程(Object-Oriented Programming, OOP)的基础概念。类是创建对象(Object)的蓝图,它定义了对象的属性和方法。每个对象都是类的一个实例,具有类定义的属性和行为。
类是一种用户自定义的数据结构,它包含了一组属性(成员变量)和行为(成员方法)。在Python中,可以使用class关键字来定义一个类,并使用def关键字来定义类的成员方法。
实现原理:
- 使用
class关键字定义一个类,类名作为该类的标识符。 - 在类中定义属性(成员变量)和行为(成员方法)。属性可以使用
类属性 = 值进行定义,方法可以使用def关键字加方法名、self参数和方法体进行定义。 - 使用对象名
=类名()创建一个类的实例对象。 - 使用
.属性名或[]操作符为对象的属性赋值,或使用.方法名()调用对象的方法。
用途: 类是Python中一种高级的数据结构,它可以用来创建和封装一组相关的数据和行为。在实际应用中,可以使用类来定义对象的数据结构和行为,然后创建对象实例来使用这些属性和方法。
注意事项:
- 在定义类的方法时,需要使用
self作为第一个参数,以便让成员方法能访问到当前类的成员变量的值。 - 在访问对象的属性时,可以使用
.属性名或[]操作符,但不能使用*或**操作符,因为它们是特殊的关键字参数。 - 在调用对象的方法时,需要在方法名后加上
(),以便让Python知道是要调用这个方法。 - 类的属性和方法不能被其他类访问,它们是私有的。如果需要让其他类访问,可以使用
@classmethod或@staticmethod装饰器进行封装。
语法结构如下:
# 定义类
class 类名:
类属性 = 值
def 方法名(self, 形参1, 形参2, ...):
方法体...
# 实例化对象
对象名 = 类名()
# 为对象属性赋值
对象.属性名 = "——"
示例:定义了一个名为User的类,并在其中定义了一些方法。这个类包含两个属性:name和age,以及三个方法:say_hello、username和__init__。
实现原理:
name和age是属性,它们没有访问控制,可以直接通过对象名访问。__init__方法是一个特殊的方法,它在对象创建时自动被调用。在这段代码中,__init__方法没有实现任何功能,只是一个空的方法定义。say_hello方法接收一个参数self,它是一个指向当前对象的引用。这个方法会在控制台上输出一条问候消息,其中包含对象的name属性。username方法接收两个参数:self和name。这个方法会在控制台上输出一条消息,其中包含对象的name和传入的name参数。- 最后,代码创建了一个
User类的实例user_1,并给它的name和age属性赋了值。然后调用了say_hello和username方法,并打印了对象的属性值。
注意事项:
- 在 Python 中,类名、方法名和变量名通常不需要全部大写,但如果你想在代码中使用关键字或避免与Python内置函数冲突,可以使用大写。
- 在 Python 中,通常使用
self来表示当前对象,但在某些情况下,你可能需要使用其他变量来接收函数调用的参数。例如,在定义一个带有参数的__init__方法时,你可以使用args(一个元组)或kwargs(一个字典)来接收参数。 - 在 Python 中,通常不需要为方法定义访问控制,因为Python是一种动态类型的语言。但是,如果你希望方法是私有的,可以在方法名前加上
_。
# 定义类
class User:
name: None
age: None
def say_hello(self):
print(f"hello {self.name}")
def username(self, name):
print(f"{self.name}的网名是{name}")
user_1 = User()
user_1.name = "李逸飞"
user_1.age = 18
# 调用类方法
user_1.say_hello() # 输出结果 hello 李逸飞
user_1.username("偶尔也有风") # 输出结果 李逸飞的网名是偶尔也有风
print(f"姓名:{user_1.name} 年龄:{user_1.age}") # 输出结果 姓名:李逸飞 年龄:18
语法:__init__(self, 形参1, 形参2, ...)
作用:
- 将传入参数自动传递给
__init__方法使用 - 创建类对象的时候,自动执行
示例:创建一个名为User的类。这个类包含两个属性name和age,以及一个构造函数__init__。当创建一个新的User对象时,它会打印出姓名和年龄。
实现原理:
- 定义一个名为
User的类。 - 在类中定义一个构造函数
__init__,用于初始化对象的属性。 - 当创建一个新的
User对象时,调用__init__函数,并将name和age作为参数传递。 - 在
__init__函数中,使用print函数打印出对象的姓名和年龄。
用途: 这个类可以用于表示用户信息,当需要创建一个用户对象时,只需创建一个新的User实例并传入相应的姓名和年龄即可。
注意事项:
- 在 Python 中,通常使用小写字母和下划线作为变量和函数名的分隔符,以提高代码的可读性。例如,
User类中的name和age属性,以及__init__方法。 - 在 Python 中,通常使用
self关键字来表示类的实例。在构造函数__init__中,需要将name和age作为参数传递给self,以便在类的其他方法中使用这些属性。
class User:
# 可省略
# name: None
# age: None
def __init__(self, name, age):
self.name = name
self.age = age
print(f"姓名:{self.name} 年龄:{self.age}")
user = User("李逸飞", 20) # 输出结果 姓名:李逸飞 年龄:20
| 方法 | 功能 |
|---|---|
__init__ | 构造方法: 创建类对象时设置初始化行为 |
__str__ | 实现类对象转字符串的行为 |
__lt__ | 用于2个类对象进行小于或大于比较 |
__le__ | 用于2个类对象进行小于等于或大于等于比较 |
__eq__ | 用于2个类对象进行相等比较 |
示例:实现一个用户类(User)。该类包含三个属性(name、age),以及三个方法(构造方法、对象输出字符串、大小比较)。
实现原理:
- 构造方法
__init__:这个方法在创建对象时自动调用,用于初始化对象的属性。在这个例子中,我们通过__init__方法接收一个name和一个age参数,并将它们分别赋值给对象的name和age属性。 - 对象输出字符串
__str__:这个方法返回一个对象的字符串表示形式。在这个例子中,我们通过__str__方法返回一个包含对象属性值的字符串,格式为"姓名:{self.name} 年龄:{self.age}"。 - 大小比较
__lt__、__le__:这些方法用于实现对象之间的比较操作。在这个例子中,我们通过__lt__方法返回一个布尔值,表示当前对象是否小于传入的对象。同样,我们通过__le__方法返回一个布尔值,表示当前对象是否小于或等于传入的对象。 - 相等比较
__eq__:这个方法用于实现对象之间的相等比较操作。在这个例子中,我们通过__eq__方法返回一个布尔值,表示当前对象是否与传入的对象相等。 - 注意事项:在Python中,这些特殊方法通常不需要手动调用,因为Python解释器会自动调用它们。但是,当你需要自定义这些方法时,请确保它们的名字遵循一定的命名约定,例如
__str__、__lt__等。
class User:
# 构造方法
def __init__(self, name, age):
self.name = name
self.age = age
# 对象输出字符串
def __str__(self):
return f"姓名:{self.name} 年龄:{self.age}"
# 大小比较
def __lt__(self, other):
return self.age < other.age
def __le__(self, other):
return self.age <= other.age
def __eq__(self, other):
return self.age == other.age
user_1 = User("寇文", 19)
user_2 = User("李逸飞", 20)
print(user_1) # 输出结果 姓名:寇文 年龄:19
print(user_1 < user_2) # 输出结果 True
print(user_1 <= user_2) # 输出结果 True
print(user_1 == user_2) # 输出结果 False
1.9.2 封装
- 将动物的身高、体重、外貌等封装为属性(类的成员变量)
- 将动物的吃饭、睡觉、跑步等封装为行为(类的成员方法)
私有成员(变量和方法):不对外开放-即类对象无法使用,但在类中可以使用-即提供仅内部可使用的属性和方法。 命名定义以__开头
示例:定义一个名为User的类,类中包含私有成员变量和私有成员方法,以及一个公开的is_adult方法。私有成员变量__basic_age和私有成员方法__un_adult都是被私有成员变量和私有成员方法装饰的。
实现原理:
- 使用双下划线(
__)装饰符定义私有成员变量和私有成员方法,使它们成为私有成员,只能在类内部访问。 - 使用单下划线(
_)装饰器定义公开方法,使它们成为公开的成员,可以在类外部访问。 - 在
is_adult方法中,通过内部调用私有成员变量和私有成员方法来实现类的外部逻辑。
用途:
- 可以用来控制类的成员变量的访问权限,确保类的封装性。
- 可以使代码更加清晰,避免直接访问私有成员变量和私有成员方法。
- 可以在类的外部调用公开方法,实现特定的功能。
注意事项:
- 私有成员变量和私有成员方法的命名不能以双下划线开头,但可以以单下划线开头。
- 私有成员变量和私有成员方法的访问权限受制于类的构造方法,只有通过构造方法初始化后才能访问。
- 公开方法的访问权限受制于单下划线,即在类外部无法直接访问,只能在类内部通过构造方法初始化后调用。
class User:
# 私有成员变量
__basic_age = 18
# 构造方法
def __init__(self, name, age):
self.name = name
self.age = age
# 私有成员方法
def __un_adult(self):
print("未成年...")
# 公开方法
def is_adult(self):
# 内部调用私有成员变量
if self.age >= self.__basic_age:
print("成年了...")
else:
# 内部调用私有成员方法
self.__un_adult()
user = User("李逸飞", 20)
user.is_adult() # 输出结果 成年了...
1.9.3 继承
继承:一个类从另外一个类继承它的成员变量和成员方法(不含私有)
类名是一个字符串,用于标识这个类;父类名可以是多个,用逗号分隔,表示这个类可以继承多个父类;类内容体是一个字符串,用于定义这个类的属性和方法。在Python中,Class还支持多继承,即一个类可以继承多个父类。但是,需要注意继承的顺序。如果有多个父类,那么先继承的优先级更高,后继承的会覆盖先继承的。
语法结构:
# 单继承
class 类名(父类名):
类内容体...
# 多继承
class 类名(父类名1, 父类名2, ...):
类内容体...
示例:动物和人类的类,以及狗和猫的类。
实现原理:使用多继承(Multiple Inheritance),即一个类可以有多个父类。在这个例子中,动物类(Animal)和人类类(Person)都有跑(run)的方法,而狗类(Dog)和猫类(Cat)分别继承动物类和人类类。在实例化Cat类时,由于猫类同时继承了动物类和人类类,所以会先继承Animal类的方法run,然后覆盖为Person类的方法run。所以输出结果为:[动物] 西西 跑…。
注意事项:
- 构造方法(
init方法)是类的特殊方法,当创建类实例时,会自动调用该方法。在这段代码中,每个类的构造方法都没有重写(即没有自定义init方法),所以默认调用父类的init方法。 - 在
Python中,子类继承父类时,会先继承前面的父类,所以Cat类中的run方法会覆盖Animal类中的run方法。 Python是动态语言,interpreter-driven language, 即解释器驱动语言。这意味着在运行时,解释器会根据代码中的执行顺序来调用方法。在这段代码中,由于Python是解释器驱动语言,所以输出结果是按照代码中的顺序执行的。
class Animal:
name: None
def run(self):
print(f"[动物] {self.name} 跑...")
class Person:
name: None
def run(self):
print(f"[人类] {self.name} 跑...")
class Dog(Animal): # Dog 继承 Animal 类
# 占位语句,用于补充语法完整性
pass
class Cat(Animal, Person): # Cat 继承 Animal 和 Person 类
pass
cat = Cat()
cat.name = "西西"
cat.run() # 输出结果 [动物] 西西 跑...
重写:对父类的成员属性或方法重新定义。
语法:子类中定义同名的成员属性或方法即可。
方式1:
-
访问成员变量:使用类名访问成员变量。
-
调用成员方法:使用类名调用成员方法,后面跟一个括号,括号内传入所需的参数。
方式2:
-
访问成员变量:使用
super()函数访问父类中的成员变量。super().成员变量。 -
调用成员方法:使用
super()函数调用父类中的成员方法。super().成员方法()。
super() 函数可以自动追踪调用方法的类的父类,直到找到指定的父类为止。
示例:实现的继承和多态示例。
实现原理:将子类Pig继承自父类Animal,然后重写了父类Animal的run方法。在运行时,通过调用不同的方式来调用父类Animal的run方法,展示了多态的效果。
注意事项:
- 代码中使用了
super()函数来调用父类方法。在Python 3中,所有类的方法都默认为继承自真子类(即广义上的“超类”)。因此,当一个类继承自多个类时,super()函数可以确保正确地调用父类的方法。 - 在Python中,实例化类时,会自动调用构造方法(如果存在)。因此,当实例化Pig类时,会自动调用构造方法,从而初始化对象的属性。
class Animal:
name = "大狗熊"
def run(self):
print(f"[动物] {self.name}说: '熊就该有个熊样'")
class Pig(Animal):
name = "熊大"
# 重写父类方法
def run(self):
print(f"{Animal.name}") # 大狗熊
# 调用父类方式 1
Animal.run(self) # [动物] 熊大说: '熊就该有个熊样'
print(f"{super().name}") # 大狗熊
# 调用父类方式 2
super().run() # [动物] 熊大说: '熊就该有个熊样'
pig = Pig()
pig.run()
1.9.4 多态
多态(polymorphism)是指同一个操作在不同的类中可以有不同的实现。在面向对象编程中,多态性的体现主要是通过继承和重写(覆盖)父类的方法来实现。当子类覆盖或重写父类的方法时,我们就获得了多态。这样可以使得同一个对象根据不同的情况执行不同的操作,提高了代码的可扩展性和灵活性。比如定义一个函数(方法),通过类型注解声明需要父类对象,但实际使用时传入子类对象工作。
抽象方法:没有具体实现的方法体(pass)
抽象类:包含抽象方法的类
作用:用于顶层设计(设计标准),以便子类做具体实现。 – 对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法
示例:首先,我们定义了一个抽象类Animal,其中包含一个抽象方法say。这个抽象方法在子类中被实现。然后,我们定义了两个具体的子类Dog和Cat,分别实现Animal类中的say方法。
在主程序中,我们定义了一个名为say的函数,它接受一个Animal类型的参数。在这个函数中,我们直接调用了传入的animal对象的say方法。这样,我们就可以根据传入的animal对象的不同,执行不同的say方法(取决于子类的实现)。
在测试代码中,我们创建了两个具体的子类实例dog和cat,分别传入到say函数中,分别执行不同的say方法(打印不同的音频)。这充分表现了多态的特性:根据传入的对象不同,执行不同的操作。
# 定义抽象类(也可以叫接口) -- 含有抽象方法的类叫抽象类
class Animal:
# 方法体为空实现(pass)的叫抽象方法
def say(self):
pass
class Dog(Animal):
def say(self):
print("汪~ 汪~ 汪~")
class Cat(Animal):
def say(self):
print("喵~ 喵~ 喵~")
# 多态
# 抽象的父类设计(设计标准)
def say(animal: Animal):
animal.say()
# 具体的子类实现(实现标准)
dog = Dog()
say(dog) # 汪~ 汪~ 汪~
cat = Cat()
say(cat) # 喵~ 喵~ 喵~
647

被折叠的 条评论
为什么被折叠?



