1 面向对象概述

面向对象(Object Oriented)的英文缩写是OO,它是一种设计思想。从20世纪60年代提出面向对象的概念到现在,它已经发展成为一种比较成熟的编程思想,并且逐步成为目前软件开发领域的主流技术。如我们经常听说的面向对象编程(Object Oriented Programming,即OOP)就是主要针对大型软件设计而提出的,它可以使软件设计更加灵活,并且能更好地进行代码复用。

面向对象中的对象(Object),通常是指客观世界中存在的对象,具有唯一性,对象之间各不相同,各有各的特点,每一个对象都有自己的运动规律和内部状态;对象与对象之间又是可以相互联系、相互作用的。另外,对象也可以是一个抽象的事物,例如,可以从圆形、正方形、三角形等图形抽象出一个简单图形,简单图形就是一个对象,它有自己的属性和行为,图形中边的个数是它的属性,图形的面积也是它的属性,输出图形的面积就是它的行为。概括地讲,面向对象技术是一种从组织结构上模拟客观世界的方法。

1.1  对象

对象是一个抽象概念,英文称作“Object”,表示任意存在的事物。世间万物皆对象!现实世界中,随处可见的一种事物就是对象,对象是事物存在的实体,如一个人,如图所示。

Pasted image 20250809082544

通常将对象划分为两个部分,即静态部分动态部分静态部分被称为“属性”,任何对象都具备自身属性,这些属性不仅是客观存在的,而且是不能被忽视的,如人的性别;动态部分指的是对象的行为,即对象执行的动作,如人可以跑步,如下图所示。

Pasted image 20250809082832

说明:在Python中,一切都是对象。不仅是具体的事物称为对象,字符串、函数等也都是对象。这说明Python天生就是面向对象的。

1.2  类

类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。例如,把雁群比作大雁类,那么大雁类就具备了喙、翅膀和爪等属性,觅食、飞行和睡觉等行为,而一只要从北方飞往南方的大雁则被视为大雁类的一个对象。大雁类和大雁对象的关系如图所示。

Pasted image 20250809083115

在Python语言中,类是一种抽象概念,如定义一个大雁类(Geese),在该类中,可以定义每个对象共有的属性和方法;而一只要从北方飞往南方的大雁则是大雁类的一个对象(wildGeese),对象是类的实例。

1.3  面向对象程序设计的特点

面向对象程序设计具有三大基本特征:封装、继承和多态。

1.3.1 封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类,类通常会对客户隐藏其实现细节,这就是封装的思想。例如,用户使用计算机,只需要使用手指敲击键盘就可以实现一些功能,而不需要知道计算机内部是如何工作的。

采用封装思想保证了类内部数据结构的完整性,使用该类的用户不能直接看到类中的数据结构,而只能执行类允许公开的数据,这样就避免了外部对内部数据的影响,提高了程序的可维护性。使用类实现封装特性如图所示。

Pasted image 20250809084838

1.3.2 继承

矩形、菱形、平行四边形和梯形等都是四边形。因为四边形与它们具有共同的特征:拥有4条边。只要将四边形适当地延伸,就会得到矩形、菱形、平行四边形和梯形4种图形。以平行四边形为例,如果把平行四边形看作四边形的延伸,那么平行四边形就复用了四边形的属性和行为,同时添加了平行四边形特有的属性和行为,如平行四边形的对边平行且相等。在Python中,可以把平行四边形类看作是继承四边形类后产生的类,其中,将类似于平行四边形的类称为子类,将类似于四边形的类称为父类或超类。值得注意的是,在阐述平行四边形和四边形的关系时,可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。同理,Python中可以说子类的实例都是父类的实例,但不能说父类的实例是子类的实例,四边形类层次结构示意图如图所示。

Pasted image 20250809085417

综上所述,继承是实现重复利用的重要手段,子类通过继承复用了父类的属性和行为的同时又添加了子类特有的属性和行为

1.3.3 多态

将父类对象应用于子类的特征就是多态。比如创建一个螺丝类,螺丝类有两个属性:粗细和螺纹密度;然后再创建了两个类,一个是长螺丝类,一个短螺丝类,并且它们都继承了螺丝类。这样长螺丝类和短螺丝类不仅具有相同的特征(粗细相同,且螺纹密度也相同),还具有不同的特征(一个长,一个短,长的可以用来固定大型支架,短的可以固定生活中的家具)。综上所述,一个螺丝类衍生出不同的子类,子类继承父类特征的同时,也具备了自己的特征,并且能够实现不同的效果,这就是多态化的结构。螺丝类层次结构示意图如图所示。

Pasted image 20250809085755

2 定义并创建类的实例

在Python中,类表示具有相同属性和方法的对象的集合。在使用类时,需要先定义类,然后再创建类的实例,通过类的实例就可以访问类中的属性和方法了。

2.1  定义类

在Python中,类的定义使用class关键字来实现,语法如下:

class ClassName:
    '''类的帮助信息'''          # 类文档字符串
    statement                 # 类体

参数说明:

  • ClassName:用于指定类名,一般使用大写字母开头,如果类名中包括两个单词,第二个单词的首字母也大写,这种命名方法也称为“驼峰式命名法”,这是惯例。当然,也可根据自己的习惯命名,但是一般推荐按照惯例来命名。
  • '''类的帮助信息''':用于指定类的文档字符串,定义该字符串后,在创建类的对象时,输入类名和左侧的括号(后,将显示该信息。
  • statement类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没想好类的具体功能,也可以在类体中直接使用pass语句代替。

例如,下面以大雁为例声明一个类,代码如下:

class Geese:
    '''大雁类'''
    pass

2.2  创建类的实例

定义完类后,并不会真正创建一个实例。这有点像一个汽车的设计图。设计图可以告诉你汽车看上去怎么样,但设计图本身不是一个汽车。你不能开走它,它只能用来建造真正的汽车,而且可以使用它制造很多汽车。那么如何创建实例呢?

class语句本身并不创建该类的任何实例。所以在类定义完成以后,可以创建类的实例,即实例化该类的对象。创建类的实例的语法如下:

ClassName(parameterlist)

其中,ClassName必选参数,用于指定具体的类parameterlist可选参数,当创建一个类时,没有创建__init__()方法,或者__init__()方法只有一个self参数时,parameterlist可以省略。

例如,创建Geese类的实例,可以使用下面的代码:

wildGoose = Geese()   # 创建大雁类的实例
print(wildGoose)

执行上面代码后,将显示类似下面的内容:

<__main__.Geese object at 0x00000000002F47AC8>

从上面的执行结果中可以看出,wildGoose是Geese类的实例。

3 创建__init__()方法

在创建类后,可以手动创建一个__init__()方法。该方法是一个特殊的方法,类似Java语言中的构造方法。每当创建一个类的新实例时,Python都会自动执行它。__init__()方法必须包含一个self参数,并且必须是第一个参数。self参数是一个指向实例本身的引用,用于访问类中的属性和方法。在方法调用时会自动传递实际参数self,因此当__init__()方法只有一个参数时,在创建类的实例时,就不需要指定实际参数了。

说明:在__init__()方法的名称中,开头和结尾处是两个下划线(中间没有空格),这是一种约定,旨在区分Python默认方法和普通方法

例如,下面仍然以大雁为例声明一个类,并且创建__init__()方法,代码如下:

class Geese:
    '''大雁类'''
    def __init__(self):        # 构造方法
        print("我是大雁类!")

wildGoose = Geese()            # 创建大雁类的实例

运行上面的代码,将输出以下内容:

我是大雁类!

从上面的运行结果可以看出,在创建大雁类的实例时,虽然没有为__init__()方法指定参数,但是该方法会自动执行。

常见错误:在为类创建__init__()方法时,在开发环境中运行下面代码:

class Geese:  
    '''大雁类'''  
    def __init__():            # 构造方法  
       print("我是大雁类!")  
  
wildGoose = Geese()            # 创建大雁类的实例

将显示如图所示的异常信息。该错误的解决方法是在第3行代码的括号中添加self

Pasted image 20250809092822

__init__()方法中,除了self参数外,还可以自定义一些参数,参数间使用逗号,进行分隔。例如,下面的代码将在创建__init__()方法时,再指定3个参数,分别是beakwingclaw

class Geese:  
    '''大雁类'''  
    def __init__(self,beak,wing,claw):               # 构造方法  
        print("我是大雁类!我有以下特征:")  
        print(beak)                                  # 输出喙的特征  
        print(wing)                                  # 输出翅膀的特征  
        print(claw)                                  # 输出爪子的特征  
  
beak_1 = "喙的基部较高,长度和头部的长度几乎相等"        # 喙的特征  
wing_1 = "翅膀长而尖"                                 # 翅膀的特征  
claw_1 = "爪子是蹼状的"                               # 爪子的特征  
wildGoose = Geese(beak_1,wing_1,claw_1)             # 创建大雁类的实例

Pasted image 20250809093346

在 Python 类中,__init____enter____exit__ 是三个核心的 “魔术方法”(Magic Methods),分别负责初始化实例上下文管理的进入上下文管理的退出,三者功能和使用场景完全不同,以下是详细拆解:

1. __init__:实例的 “构造初始化方法”

  • 核心作用:当通过 类名() 创建实例时,__init__ 会自动被调用,用于初始化实例的属性、分配资源(如变量赋值、打开文件等),是实例创建后的 “第一站”。
  • 本质:它不是 “构造函数”(真正创建实例的是 __new__),而是 “初始化函数”,接收刚创建的实例(self)并对其初始化。
  • 关键特征
    • 第一个参数必须是 self,代表当前实例本身。
    • 无返回值(若强行返回非 None 会报错)。
    • 若未自定义,Python 会提供默认的空 __init__

示例:

class Student:
    # 自定义__init__,初始化实例的name和age属性
    def __init__(self, name, age):
        self.name = name  # 给实例绑定name属性
        self.age = age    # 给实例绑定age属性

# 创建实例时,自动调用__init__,传入name和age参数
stu = Student("张三", 20)
print(stu.name)  # 输出:张三(__init__初始化的属性可直接使用)

2. __enter__:上下文管理器的 “进入方法”

  • 核心作用:当实例被用于 with 语句(上下文管理器)时,进入 with 代码块前,__enter__ 会自动被调用,主要用于 “准备资源”(如创建数据库连接、打开文件、获取锁等)。
  • 使用前提:类必须同时实现 __enter__ 和 __exit__,才是合格的 “上下文管理器”,才能用在 with 中。
  • 关键特征
    • 第一个参数是 self,代表当前实例。
    • 必须有返回值:通常返回 self(让 with 语句的变量直接指向实例,方便后续操作)。

示例(结合数据库场景):

class DBConn:
    def __init__(self, db_config):
        self.db_config = db_config
        self.conn = None  # 初始化时暂不创建连接

    # 进入with块时,创建数据库连接(准备资源)
    def __enter__(self):
        self.conn = pymysql.connect(**self.db_config)  # 准备连接资源
        return self  # 返回实例,让with变量指向它

# 使用with语句:进入时自动调用__enter__
with DBConn(config) as db:
    # db就是__enter__返回的实例,可直接使用其conn属性
    print(db.conn)  # 输出:数据库连接对象

3. __exit__:上下文管理器的 “退出方法”

  • 核心作用:当 with 代码块执行结束(无论正常结束还是抛出异常),__exit__ 会自动被调用,主要用于 “清理资源”(如关闭数据库连接、关闭文件、释放锁等),是上下文管理的 “最后一站”。
  • 关键特征
    • 有 4 个参数:self(实例)、exc_type(异常类型,无异常则为 None)、exc_val(异常实例,无异常则为 None)、exc_tb(异常追踪栈,无异常则为 None)。
    • 返回值为布尔值(True/False):若返回 True,表示 “已处理异常”,外部不会再捕获该异常;若返回 False 或无返回值,异常会向外传播。

示例(完善数据库连接的关闭逻辑):

class DBConn:
    def __init__(self, db_config):
        self.db_config = db_config
        self.conn = None
        self.cursor = None

    def __enter__(self):
        self.conn = pymysql.connect(**self.db_config)
        self.cursor = self.conn.cursor()
        return self

    # 退出with块时,自动清理资源
    def __exit__(self, exc_type, exc_val, exc_tb):
        # 1. 关闭cursor(先关轻量资源)
        if self.cursor:
            self.cursor.close()
        # 2. 关闭conn(再关重量级资源)
        if self.conn:
            self.conn.close()
        # 3. 处理异常(可选)
        if exc_type:
            print(f"发生异常:{exc_val}")
            # return True  # 若加这行,外部不会再捕获该异常

# 使用with:结束后自动调用__exit__,关闭连接
with DBConn(config) as db:
    db.cursor.execute("SELECT 1")
# 此时conn和cursor已被__exit__自动关闭

三者核心区别与关联

方法触发时机核心功能依赖关系
__init__类名() 创建实例时初始化实例属性无(独立存在)
__enter__进入 with 代码块时准备上下文资源(如建连接)必须与 __exit__ 成对出现
__exit__退出 with 代码块时清理上下文资源(如关连接)必须与 __enter__ 成对出现

关联场景:在数据库、文件等需要 “用完即关” 的场景中,通常会同时实现三者 ——__init__ 存配置,__enter__ 建连接,__exit__ 关连接,通过 with 语句实现 “自动资源管理”。

4 创建类的成员并访问

类的成员主要由实例方法和数据成员组成。在类中创建了类的成员后,可以通过类的实例进行访问。

4.1 创建实例方法并访问

所谓实例方法是指在类中定义的函数。该函数是一种在类的实例上操作的函数。同__init__()方法一样,实例方法的第一个参数必须是self,并且必须包含一个self参数。创建实例方法的语法格式如下:

def functionName(self,parameterlist):  
    block

参数说明:

  • functionName:用于指定方法名,一般使用小写字母开头。
  • self:必要参数,表示类的实例,其名称可以是self以外的单词,使用self只是一个惯例而已。
  • parameterlist:用于指定除self参数以外的参数,各参数间使用逗号“,”进行分隔。
  • block:方法体,实现的具体功能。

说明:实例方法和Python中的函数的主要区别就是,函数实现的是某个独立的功能,而实例方法是实现类中的一个行为,是类的一部分。

实例方法创建完成后,可以通过类的实例名称和点(.)操作符进行访问,语法格式如下:

instanceName.functionName(parametervalue)

参数说明:

  • instanceName:为类的实例名称。
  • functionName:为要调用的方法名称。
  • parametervalue:表示为方法指定对应的实际参数,其值的个数与创建实例方法中parameterlist的个数相同。

下面通过一个具体的实例演示创建实例方法并访问。

实例  创建大雁类并定义飞行方法

在IDLE中创建一个名称为geese.py的文件,然后在该文件中定义一个大雁类Geese,并定义一个构造方法,然后再定义一个实例方法fly(),该方法有两个参数,一个是self,另一个用于指定飞行状态,最后再创建大雁类的实例,并调用实例方法fly(),代码如下:

class Geese:                                                    # 创建大雁类  
    '''大雁类'''  
    def __init__(self, beak, wing, claw):                       # 构造方法  
        print("我是大雁类!我有以下特征:")  
        print(beak)                                             # 输出喙的特征  
        print(wing)                                             # 输出翅膀的特征  
        print(claw)                                             # 输出爪子的特征  
  
    def fly(self, state):                                       # 定义飞行方法  
        print(state)  
  
'''**************调用方法*********************'''  
  
beak_1 = "喙的基部较高,长度和头部的长度几乎相等"                   # 喙的特征  
wing_1 = "翅膀长而尖"                                            # 翅膀的特征  
claw_1 = "爪子是蹼状的"                                          # 爪子的特征  
wildGoose = Geese(beak_1, wing_1, claw_1)                       # 创建大雁类的实例  
wildGoose.fly("我飞行的时候,一会儿排成个人字,一会排成个一字")      # 调用实例方法

运行结果如图所示:

Pasted image 20250809095521

4.2 创建数据成员并访问

数据成员是指在类中定义的变量,即属性,根据定义位置,又可以分为类属性和实例属性。

4.2.1 类属性

类属性是指定义在类中,并且在函数体外的属性。类属性可以在类的所有实例之间共享值,也就是在所有实例化的对象中公用。

说明:类属性可以通过类名称或者实例名访问。

例如,定义一个雁类Geese,在该类中定义3个类属性,用于记录雁类的特征,代码如下:

class Geese:  
    '''雁类'''  
    neck = "脖子较长"                           # 定义类属性(脖子)  
    wing = "振翅频率高"                         # 定义类属性(翅膀)  
    leg = "腿位于身体的中心支点,行走自如"        # 定义类属性(腿)  
  
    def __init__(self):                       # 实例方法(相当于构造方法)  
        print("我属于雁类!我有以下特征:")  
        print(Geese.neck)                     # 输出脖子的特征  
        print(Geese.wing)                     # 输出翅膀的特征  
        print(Geese.leg)                      # 输出腿的特征

创建上面的类Geese,然后创建该类的实例,代码如下:

geese = Geese()     # 实例化一个大雁类的对象

应用上面的代码创建Geese类的实例后,将显示以下内容:

我是雁类!我有以下特征:
脖子较长
振翅频率高
腿位于身体的中心支点,行走自如

下面通过一个具体的实例演示类属性在类的所有实例之间共享值的应用。

场景模拟:春天来了,有一群大雁从南方返回北方。现在想要输出每只大雁的特征以及大雁的数量。

实例 通过类属性统计类的实例个数

在IDLE中创建一个名称为geese_a.py的文件,然后在该文件中定义一个雁类Geese,并在该类中定义4个类属性,前3个用于记录雁类的特征,第4个用于记录实例编号,然后定义一个构造方法,在该构造方法中将记录实例编号的类属性进行加1操作,并输出4个类属性的值,最后通过for循环创建4个雁类的实例,代码如下:

class Geese:  
    '''雁类'''  
    neck = "脖子较长"                           # 类属性(脖子)  
    wing = "振翅频率高"                         # 类属性(翅膀)  
    leg = "腿位于身体的中心支点,行走自如"         # 类属性(腿)  
    number = 0                                 # 编号  
  
    def __init__(self):                        # 构造方法  
        Geese.number += 1                      # 将编号加1  
        print("\n我是第"+str(Geese.number)+"只大雁,我属于雁类!我有以下特征:")  
        print(Geese.neck)                      # 输出脖子的特征  
        print(Geese.wing)                      # 输出翅膀的特征  
        print(Geese.leg)                       # 输出腿的特征  
  
# 创建4个雁类的对象(相当于有4只大雁)  
list1 = []  
for i in range(4):                             # 循环4次  
     list1.append(Geese())                     # 创建一个雁类的实例  
print("一共有"+str(Geese.number)+"只大雁")

运行结果如下图所示:

Pasted image 20250809101300

Python中除了可以通过类名称访问类属性,还可以动态地为类和对象添加属性。例如,在实例02的基础上为雁类添加一个beak属性,并通过类的实例访问该属性,可以在上面代码的后面再添加以下代码:

Geese.beak = "喙的基部较高,长度和头部的长度几乎相等"   # 添加类属性  
print("第2只大雁的喙:",list1[1].beak)               # 访问类属性

说明:上面的代码只是以第2只大雁为例进行演示,读者也可以换成其他的大雁试试。

运行后,将在原来的结果后面再显示以下内容:

第2只大雁的喙:喙的基部较高,长度和头部的长度几乎相等

说明:除了可以动态地为类和对象添加属性,也可以修改类属性。修改结果将作用于该类的所有实例。

4.2.2 实例属性

实例属性是指定义在类的方法中的属性,只作用于当前实例中。

例如,定义一个雁类Geese,在该类的__init__()方法中定义3个实例属性,用于记录雁类的特征,代码如下:

class Geese:  
    '''雁类'''  
    def __init__(self):                              # 实例方法(相当于构造方法)  
        self.neck = "脖子较长"                        # 定义实例属性(脖子)  
        self.wing = "振翅频率高"                      # 定义实例属性(翅膀)  
        self.leg = "腿位于身体的中心支点,行走自如"     # 定义实例属性(腿)  
        print("我属于雁类!我有以下特征:")  
        print(self.neck)                           # 输出脖子的特征  
        print(self.wing)                           # 输出翅膀的特征  
        print(self.leg)                            # 输出腿的特征

创建上面的类Geese,然后创建该类的实例,代码如下:

geese = Geese()                                  # 实例化一个雁类的对象

应用上面的代码创建Geese类的实例后,将显示以下内容:

我是雁类!我有以下特征:
脖子较长
振翅频率高
腿位于身体的中心支点,行走自如

说明:实例属性只能通过实例名访问。如果通过类名访问实例属性,如执行print(Geese.neck)将抛出如图所示的异常。

Pasted image 20250809102417

对于实例属性也可以通过实例名称修改,与类属性不同,通过实例名称修改实例属性后,并不影响该类的另一个实例中相应的实例属性的值。例如,定义一个雁类,并在__init__()方法中定义一个实例属性,然后创建两个Geese类的实例,并且修改第一个实例的实例属性,最后分别输出实例1和实例2的实例属性,代码如下:

class Geese:  
    '''雁类'''  
    def __init__(self):                           # 实例方法(相当于构造方法)  
        self.neck = "脖子较长"                     # 定义实例属性(脖子)  
        print(self.neck)                          # 输出脖子的特征  
  
goose1 = Geese()                                 # 创建Geese类的实例1  
goose2 = Geese()                                 # 创建Geese类的实例2  
goose1.neck = "脖子没有天鹅的长"                   # 修改实例属性  
print("goose1的neck属性:",goose1.neck)  
print("goose2的neck属性:",goose2.neck)

运行上面的代码,将显示以下内容:

脖子较长
脖子较长
goose1的neck属性: 脖子没有天鹅的长
goose2的neck属性: 脖子较长

5 访问限制

在类的内部可以定义属性和方法,而在类的外部则可以直接调用属性或方法来操作数据,从而隐藏了类内部的复杂逻辑。但是Python并没有对属性和方法的访问权限进行限制。为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加单下划线(_foo)、双下划线(__foo)或首尾加双下划线(__foo__),从而限制访问权限。其中,单下划线、双下划线、首尾双下划线的作用如下:

(1)首尾双下划线表示定义特殊方法,一般是系统定义名字,如__init__()。 (2)以单下划线开头的表示protected保护类型的成员,只允许类本身和子类进行访问,但不能使用from module import *语句导入。

例如,创建一个Swan类,定义保护属性_neck_swan,并使用__init__()方法访问该属性,然后创建Swan类的实例,并通过实例名输出保护属性_neck_swan,代码如下:

class Swan:  
    '''天鹅类'''  
    _neck_swan = '天鹅的脖子很长'                   # 定义保护属性  
    def __init__(self):  
        print("__init__():", Swan._neck_swan)     # 在实例方法中访问保护属性  
  
swan = Swan()                                     # 创建Swan类的实例  
print("直接访问:" , swan._neck_swan)               # 保护属性可以通过实例名访问

执行下面的代码,将显示以下内容:

__init__(): 天鹅的脖子很长
直接访问: 天鹅的脖子很长

从上面的运行结果中可以看出,保护属性可以通过实例名访问。

(3)双下划线表示private私有类型的成员,只允许定义该方法的类本身进行访问,而且也不能通过类的实例进行访问,但是可以通过类的实例名._类名__xxx方式访问

例如,创建一个Swan类,定义私有属性__neck_swan,并使用__init__()方法访问该属性,然后创建Swan类的实例,并通过实例名输出私有属性__neck_swan,代码如下:

class Swan:  
    '''天鹅类'''  
    __neck_swan = '天鹅的脖子很长'               # 定义私有属性  
    def __init__(self):  
        print("__init__():", Swan.__neck_swan)  # 在实例方法中访问私有属性  
  
swan = Swan()                                   # 创建Swan类的实例  
print("加入类名:" , swan._Swan__neck_swan)       # 私有属性,可以通过“实例名._类名__xxx”方式访问  
try:  
    print("直接访问:" , swan.__neck_swan)            # 私有属性不能通过实例名访问,出错 
except AttributeError:  
    print("访问出错")

执行上面的代码后,将输出如图13所示的结果。

Pasted image 20250809204614

从上面的运行结果可以看出:私有属性不能直接通过实例名+属性名访问,可以在类的实例方法中访问,也可以通过实例名._类名__xxx方式访问

6 属性(Property)

此部分介绍的属性与上述部分介绍的类属性和实例属性不同。上述部分的属性将返回所存储的值,而此部分要介绍的属性则是一种特殊的属性,访问它时将计算它的值。另外,该属性还可以为属性添加安全保护机制

6.1  创建用于计算的属性

在Python中,可以通过@property(装饰器)将一个方法转换为属性,从而实现用于计算的属性将方法转换为属性后,可以直接通过方法名来访问方法,而不需要再添加一对小括号(),这样可以让代码更加简洁。

通过@property创建用于计算的属性的语法格式如下:

@property
def methodname(self):  
    block

参数说明:

  • methodname:用于指定方法名,一般使用小写字母开头。该名称最后将作为创建的属性名。
  • self:必要参数,表示类的实例
  • block方法体,实现的具体功能。在方法体中,通常以return语句结束,用于返回计算结果。

例如,定义一个矩形类,在__init__()方法中定义两个实例属性,然后再定义一个计算矩形面积的方法,并应用@property将其转换为属性,最后创建类的实例,并访问转换后的属性,代码如下:

class Rect:  
  
    def __init__(self,width,height):  
        self.width = width                  # 矩形的宽  
        self.height = height                # 矩形的高  
  
    @property                               # 将方法转换为属性  
    def area(self):                         # 计算矩形的面积的方法  
        return self.width*self.height       # 返回矩形的面积  
  
rect = Rect(800,600)          # 创建类的实例  
print("面积为:",rect.area)                 # 输出属性的值

运行上面的代码,将显示以下运行结果:

面积为: 480000

注意:通过@property转换后的属性不能重新赋值,如果对其重新赋值,将抛出如图所示的异常信息。

Pasted image 20250809211454

6.2  为属性添加安全保护机制

在Python中,默认情况下,创建的类属性或者实例是可以在类体外进行修改的,如果想要限制其不能在类体外修改,可以将其设置为私有的,但设置为私有后,在类体外也不能直接通过实例名+属性名获取它的值。如果想要创建一个可以读取但不能修改的属性,那么可以使用@property实现只读属性

例如,创建一个电视节目类TVshow,再创建一个show属性,用于显示当前播放的电视节目,代码如下:

class TVshow:   # 定义电视节目类  
  
    def __init__(self,show):  
        self.__show = show  
  
    @property                            # 将方法转换为属性  
    def show(self):                      # 定义show()方法  
        return self.__show               # 返回私有属性的值  
  
tvshow = TVshow("正在播放《战狼2》")       # 创建类的实例  
print("默认:",tvshow.show)               # 获取属性值

执行上面的代码,将显示以下内容:

默认: 正在播放《战狼2》

通过上面的方法创建的show属性是只读的,尝试修改该属性的值,再重新获取。在上面代码中添加以下代码:

tvshow.show = "正在播放《红海行动》"         # 修改属性值  
print("修改后:",tvshow.show)              # 获取属性值

运行后,将显示如图所示的运行结果,其中红字的异常信息就是修改属性show时抛出的异常。

Pasted image 20250809212121

通过属性不仅可以将属性设置为只读属性,而且可以为属性设置拦截器,即允许对属性进行修改,但修改时需要遵守一定的约束

场景模拟:某电视台开设了电影点播功能,但要求只能从指定的几个电影(如《战狼2》《红海行动》《西游记女儿国》《熊出没・变形记》)中选择一个。

实例  在模拟电影点播功能时应用属性

在IDLE中创建一个名称为film.py的文件,然后在该文件中定义一个电视节目类TVshow,并在该类中定义一个类属性,用于保存电影列表,然后在__init__()方法中定义一个私有的实例属性,再将该属性转换为可读取、可修改(有条件进行)的属性,最后创建类的实例,并获取和修改属性值,代码如下:

class TVshow:                                     # 定义电视节目类  
  
    list_film = ["战狼2","红海行动","西游记女儿国","熊出没・变形记"]  
  
    def __init__(self,show):  
        self.__show = show  
  
    @property                                     # 将方法转换为属性  
    def show(self):                               # 定义show()方法  
        return self.__show                        # 返回私有属性的值  
  
    @show.setter                                  # 设置setter方法,让属性可修改  
    def show(self,value):  
        if value in TVshow.list_film:             # 判断值是否在列表中  
            self.__show = "您选择了《" + value + "》,稍后将播放"  # 返回修改的值  
        else:  
            self.__show = "您点播的电影不存在"  
  
tvshow = TVshow("战狼2")                          # 创建类的实例  
print("正在播放:《",tvshow.show,"》")             # 获取属性值  
print("您可以从",tvshow.list_film,"中选择要点播放的电影")  
tvshow.show = "红海行动"                           # 修改属性值  
print(tvshow.show)                                # 获取属性值

运行结果如下所示:

正在播放:《 战狼2 》
您可以从 ['战狼2', '红海行动', '西游记女儿国', '熊出没・变形记'] 中选择要点播放的电影
您选择了《红海行动》,稍后将播放

如果将第17行代码中的“红海行动”修改为“流浪地球”,将显示如下所示的效果。

正在播放:《 战狼2 》
您可以从 ['战狼2', '红海行动', '西游记女儿国', '熊出没・变形记'] 中选择要点播放的电影
您点播的电影不存在

7 继承

7.1 继承的基本语法

在编写类时,并不是每次都要从空白开始。当要编写的类和另一个已经存在的类之间存在一定的继承关系时,就可以通过继承来达到代码重用的目的,提高开发效率。

继承是面向对象编程最重要的特性之一,它源于人们认识客观世界的过程,是自然界普遍存在的一种现象。例如,我们每一个人都从祖辈和父母那里继承了一些体貌特征,但是每个人却又不同于父母,因为每个人都存在自己的一些特性,这些特性是独有的,在父母身上并没有体现。在程序设计中实现继承,表示这个类拥有它继承的类的所有公有成员或者受保护成员。在面向对象编程中,被继承的类称为父类或基类,新的类称为子类或派生类

通过继承不仅可以实现代码的重用,还可以通过继承来理顺类与类之间的关系。在Python中,可以在类定义语句中,类名右侧使用一对小括号将要继承的基类名称括起来,从而实现类的继承。具体的语法格式如下:

class ClassName(baseclasslist):  
    '''类的帮助信息'''                     # 类文档字符串  
    statement                            # 类体

参数说明:

  • ClassName:用于指定类名
  • baseclasslist:用于指定要继承的基类,可以有多个,类名之间用逗号,分隔。如果不指定,将使用所有Python对象的根类object。
  • '''类的帮助信息''':用于指定类的文档字符串,定义该字符串后,在创建类的对象时,输入类名和左侧的括号(后,将显示该信息。
  • statement类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没想好类的具体功能,也可以在类体中直接使用pass语句代替。

实例  创建水果基类及其派生类

在IDLE中创建一个名称为fruit.py的文件,然后在该文件中定义一个水果类Fruit(作为基类),并在该类中定义一个类属性(用于保存水果默认的颜色)和一个harvest()方法,然后创建Apple类和Orange类,都继承自Fruit类,最后创建Apple类和Orange类的实例,并调用harvest()方法(在基类中编写),代码如下:

class Fruit:                                           # 定义水果类(基类)  
    color = "绿色"                                     # 定义类属性  
    def harvest(self, color):  
        print("水果是:" + color + "的!")              # 输出的是形式参数color  
        print("水果已经收获……")  
        print("水果原来是:" + Fruit.color + "的!")     # 输出的是类属性color  
  
class Apple(Fruit):                                    # 定义苹果类(派生类)  
    color = "红色"  
    def __init__(self):  
        print("我是苹果")  
  
class Orange(Fruit):                                   # 定义橘子类(派生类)  
    color = "橙色"  
    def __init__(self):  
        print("\n我是橘子")  
  
apple = Apple()                                         # 创建类的实例(苹果)  
apple.harvest(apple.color)                              # 调用基类的harvest()方法  
orange = Orange()                                       # 创建类的实例(橘子)  
orange.harvest(orange.color)                            # 调用基类的harvest()方法

执行上面的代码,将显示如图所示的运行结果。从该运行结果中可以看出,虽然在Apple类和Orange类中没有harvest()方法,但是Python允许派生类访问基类的方法

Pasted image 20250809213928

7.2 方法重写

基类的成员都会被派生类继承,当基类中的某个方法不完全适用于派生类时,就需要在派生类中重写父类的这个方法,这和Java语言中的方法重写是一样的。

在实例中,基类中定义的harvest()方法,无论派生类是什么水果都显示“水果……”,如果想要针对不同水果给出不同的提示,可以在派生类中重写harvest()方法。例如,在创建派生类Orange时,重写harvest()方法的代码如下:

class Orange(Fruit):                                    # 定义橘子类(派生类)  
    color = "橙色"  
    def __init__(self):  
        print("\n我是橘子")  
  
    def harvest(self, color):  
        print("橘子是:" + color + "的!")               # 输出的是形式参数color  
        print("橘子已经收获……")  
        print("橘子原来是:" + Fruit.color + "的!")      # 输出的是类属性color

添加harvest()方法后(即在实例中添加上面代码中的05~08行代码),再次运行实例,将显示如图所示的运行结果。

Pasted image 20250809215755

7.3 派生类中调用基类的__init__()方法

在派生类中定义__init__()方法时,不会自动调用基类的__init__()方法。例如,定义一个Fruit类,在__init__()方法中创建类属性color,然后在Fruit类中定义一个harvest()方法,在该方法中输出类属性color的值,再创建继承自Fruit类的Apple类,最后创建Apple类的实例,并调用harvest()方法,代码如下:

class Fruit:                             # 定义水果类(基类)  
  
    def __init__(self,color = "绿色"):  
        Fruit.color = color              # 定义类属性  
  
    def harvest(self):  
        print("水果原来是:" + Fruit.color + "的!")    # 输出的是类属性color  
  
class Apple(Fruit):                      # 定义苹果类(派生类)  
  
    def __init__(self):  
        print("我是苹果")  
  
apple = Apple()                          # 创建类的实例(苹果)  
apple.harvest()                          # 调用基类的harvest()方法

执行上面的代码后,将显示如图所示的异常信息:

Pasted image 20250809220147

因此,要让派生类调用基类的__init__()方法进行必要的初始化,需要在派生类使用super()函数调用基类的__init__()方法。例如,在上面代码的第8行代码的下方添加以下代码:

super().__init__()      # 调用基类的__init__()方法

注意:在添加上面的代码时,一定要注意缩进的正确性。

运行后将显示以下正常的运行结果:

我是苹果
水果原来是:绿色的!

下面通过一个具体实例演示派生类中调用基类的__init__()方法的具体的应用。

实例  在派生类中调用基类的__init__()方法定义类属性

在IDLE中创建一个名称为fruit.py的文件,然后在该文件中定义一个水果类Fruit(作为基类),并在该类中定义__init__()方法,在该方法中定义一个类属性(用于保存水果默认的颜色),然后在Fruit类中定义一个harvest()方法,再创建Apple类和Sapodilla类,都继承自Fruit类,最后创建Apple类和Sapodilla类的实例,并调用harvest()方法(在基类中编写),代码如下:

class Fruit:                                            # 定义水果类(基类)  
  
    def __init__(self, color="绿色"):  
        Fruit.color = color                             # 定义类属性  
  
    def harvest(self, color):  
        print("水果是:" + self.color + "的!")           # 输出的是形式参数color  
        print("水果已经收获……")  
        print("水果原来是:" + Fruit.color + "的!")      # 输出的是类属性color  
  
class Apple(Fruit):                                     # 定义苹果类(派生类)  
    color = "红色"  
    def __init__(self):  
        print("我是苹果")  
        super().__init__()                              # 调用基类的__init__()方法  
  
class Sapodilla(Fruit):                                 # 定义人参果类(派生类)  
  
    def __init__(self, color):  
        print("\n我是人参果")  
        super().__init__(color)                         # 调用基类的__init__()方法  
  
    # 重写harvest()方法的代码  
    def harvest(self, color):  
        print("人参果是:" + color + "的!")              # 输出的是形式参数color  
        print("人参果已经收获……")  
        print("人参果原来是:" + Fruit.color + "的!")    # 输出的是类属性color  
  
apple = Apple()                                         # 创建类的实例(苹果)  
apple.harvest(apple.color)                              # 调用harvest()方法  
sapodilla = Sapodilla("白色")                            # 创建类的实例(人参果)  
sapodilla.harvest("金黄色带紫色条纹")                     # 调用harvest()方法

执行上面的代码,将显示如下图所示的运行结果:

Pasted image 20250809220748