面向对象概念
- Python从设计之初就已经是一门面向对象编程的语言,正因为如此,在Python中创建一个类和对象是很容易的
- 面向对象编程OOP——Object Oriented Programming,是一种程序设计思想
- OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
- 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向- 过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
- 而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
- 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念
假设要处理Student学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:
stu1 = {"name": "zs", "score": 90}
stu2 = {"name": "ls", "score": 89}
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
def print_score(stu: dict):
print(f"{stu['name']}的成绩是{stu['score']}")
print_score(stu1)
print_score(stu2)
'''
zs的成绩是90
ls的成绩是89
'''
如果采用OOP的程序设计思想,首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property),如果要打印学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。
类的定义格式如下图所示:
基于上述格式,定义一个Student类用于打印学生的成绩:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print(f"{self.name}的成绩是{self.score}")
给对象发消息实际上就是调用对象对应的关联函数,称之为对象的方法(Method)。面向对象的程序写出来就像这样:
zs = Student('zs', 90)
ls = Student('ls', 89)
zs.print_score()
ls.print_score()
'''
zs的成绩是90
ls的成绩是89
'''
- 面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的
- Class是一种抽象概念,而实例(Instance)则是一个个具体的Student
- 比如定义的Class——Student,是指学生这个概念,
- Bart Simpson和Lisa Simpson是两个具体的Student
- 所以,面向对象的设计思想是抽象出Class,根据Class创建Instance
- 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法
- 面向对象主要有三大特点:封装、继承和多态
专业术语介绍:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写
- 局部变量:定义在方法中的变量,只作用于当前实例的类
- 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待
- 实例化:创建一个类的实例,类的具体对象
- 对象:通过类定义的数据结构实例,包括两个数据成员(类变量和实例变量)和方法
类和实例
在面向对象编程(OOP)中,类和实例是两个核心概念,它们共同构成了面向对象编程的基础。
类(Class)
类是一种模板或蓝图,它定义了对象的属性(数据)和方法(函数)。简而言之,类描述了对象应该拥有什么(属性)以及它可以做什么(方法)。类本身不占用内存空间,它仅仅是一个设计图。
一个类可以包含:
- 属性:定义在类中的变量,用于描述对象的特征。
- 方法:定义在类中的函数,用于描述对象的行为。
例如,假设我们有一个Car类,它可能包含属性如make(制造商)、model(型号)和year(年份),以及方法如start()(启动汽车)和stop()(停止汽车)。
实例(Instance)
实例是类的具体实现,是根据类创建的对象。每个实例都有自己独立的内存空间,存储着它的属性值。实例是通过使用类作为模板,并为其属性指定具体值来创建的。
使用前面的Car类作为例子,我们可以创建多个Car的实例,每个实例代表一辆具体的汽车,它们可能有不同的制造商、型号和年份。
创建实例
在Python中,你可以使用类名后跟一对圆括号(可能包含传递给__init__方法的参数)来创建类的实例。__init__方法是一个特殊的方法,被称为类的构造函数,它在创建对象时自动调用,用于初始化对象的属性。
class Car(object):
def __init__(self, make, model, year):
"""
初始化Car类的实例
:param make: 制造商
:param model: 型号
:param year: 年份
"""
self.make = make
self.model = model
self.year = year
def start(self):
'''模拟汽车启动'''
print(f"{self.make} {self.model} ({self.year}) 启动了!")
def stop(self):
'''模拟停止汽车'''
print(f"{self.make} {self.model} ({self.year}) 停止了!")
# 使用Car类创建实例
my_car = Car("Toyota", "Corolla", 2024)
# 调用方法
my_car.start() # 输出:Toyota Corolla (2024) 启动了!
my_car.stop() # 输出:Toyota Corolla (2024) 停止了!
在这个例子中,Car类通过__init__方法(这是一个特殊方法,用于类的初始化)接收三个参数:make、model和year,并将它们分别赋值给实例的同名属性。start()和stop()方法则分别模拟了汽车的启动和停止过程,通过打印信息到控制台来展示。
- __init__前后分别有两个下划线
- __init__方法的第一个参数永远是self,表示创建的实例本身
- 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数
- self不需要传,Python解释器自己会把实例变量传进去
数据封装
在Python中,数据封装是面向对象编程(OOP)中的一个核心概念。封装意味着将数据(属性)和操作数据的方法(函数)组合在一起,形成一个单独的单元(即类)。通过这种方式,对象的数据被隐藏起来,只能通过类提供的特定方法来访问和修改,这有助于保护数据的完整性和安全性。
Python中的数据封装主要依赖于以下两个方面:
- 私有属性(Private Attributes):在Python中,私有属性通常是通过在属性名前加上双下划线(__)前缀来实现的。然而,需要注意的是,Python实际上并没有真正的私有属性或私有方法,这只是通过名称改写(name mangling)机制来实现的。任何以双下划线开头的属性名都会在类的定义中被自动“改写”成一个更复杂的名字,这个新的名字包括了类名。这样做可以阻止从类的外部直接访问这些属性,但仍然可以通过类的内部方法或继承自该类的子类(通过_ClassName__attrName的方式)来访问。
- 访问器(Accessors)和修改器(Mutators):也称为getter和setter方法,它们提供了访问和修改私有属性的安全方式。通过定义这些方法,类可以控制对私有属性的访问,比如执行输入验证、计算属性值的副作用等。
下面是一个简单的Python类示例,展示了数据封装的概念:
class Person:
def __init__(self, name, age):
# 私有属性
self.__name = name
self.__age = age
# Getter方法
def get_name(self):
return self.__name
# Setter方法
def set_name(self, name):
# 这里可以添加验证逻辑
if isinstance(name, str):
self.__name = name
else:
raise ValueError("Name must be a string")
# Getter方法
def get_age(self):
return self.__age
# Setter方法
def set_age(self, age):
# 这里可以添加验证逻辑
if isinstance(age, int) and age >= 0:
self.__age = age
else:
raise ValueError("Age must be a non-negative integer")
# 使用Person类
person = Person("Alice", 30)
print(person.get_name()) # 输出: Alice
person.set_age(25)
print(person.get_age()) # 输出: 25
# 尝试直接访问私有属性(不推荐,但技术上可行)
# print(person._Person__name) # 输出: Alice,但违反了封装原则
在这个例子中,Person类有两个私有属性__name和__age,以及它们的getter和setter方法。这样,类的外部就不能直接访问或修改这些私有属性,而必须通过类提供的公共接口(即getter和setter方法)来进行。这有助于保护数据的完整性和安全性,并允许类在内部添加必要的验证逻辑。
继承和多态
在Python中,继承和多态是面向对象编程(OOP)的两个核心概念,它们允许我们创建更灵活、更可重用的代码。
继承
在Python中,继承是通过定义一个类(子类)来继承另一个类(父类)的属性和方法。子类可以继承父类的所有公有(public)和保护(protected,尽管Python中没有显式的protected访问修饰符,但通常约定以下划线_开头的属性为受保护的)成员,但无法直接访问私有(private,即双下划线__开头的属性,它们会被名称改写)成员。子类可以添加自己的新成员,也可以覆盖(重写)从父类继承来的方法。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
class Dog(Animal):
# 将父类的speak方法输出内容重写
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
# 将父类的speak方法输出内容重写
def speak(self):
return f"{self.name} says Meow!"
# 使用
dog = Dog("Buddy")
print(dog.speak()) # 输出: Buddy says Woof!
cat = Cat("Whiskers")
print(cat.speak()) # 输出: Whiskers says Meow!
在这个例子中,Dog和Cat类都继承自Animal类,并重写了speak方法以提供特定的行为。
多态
多态在Python中通常是通过方法覆盖(重写)和接口(虽然Python没有像Java那样的显式接口声明,但可以通过抽象基类(abc.ABCMeta和abc.abstractmethod)来模拟)来实现的。多态允许我们以统一的方式调用不同的对象,而这些对象实际上可能是不同类的实例,只要它们都继承自同一个基类或实现了相同的接口。
在上面的Animal、Dog和Cat例子中,我们已经隐式地使用了多态。我们定义了一个统一的speak方法接口,在Animal类中作为一个抽象方法(尽管Python中没有强制要求这样做,但我们通过抛出NotImplementedError来指示子类必须实现它)。然后,Dog和Cat类分别实现了这个接口,提供了具体的speak方法。
当我们使用Animal类型的引用来指向Dog或Cat的实例,并通过这个引用来调用speak方法时,Python会在运行时确定实际应该调用哪个类的speak方法,这就是多态的体现。
多态的示例(继续使用上面的类):
def animal_speaks(animal):
print(animal.speak())
# 无论传入的是Dog还是Cat的实例,animal_speaks都能正确处理
animal_speaks(dog) # 输出: Buddy says Woof!
animal_speaks(cat) # 输出: Whiskers says Meow!
在这个例子中,animal_speaks函数接受任何Animal类型的对象作为参数,并调用其speak方法。由于多态,这个函数可以无缝地与Dog和Cat的实例一起工作,而不需要知道它们的具体类型。