Python面向对象(概念、类和实例、数据封装、继承和多态)

面向对象概念

  • 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的实例一起工作,而不需要知道它们的具体类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值