Python面向对象编程入门


Python面向对象编程入门

Python 面向对象编程(Object-Oriented Programming,简称 OOP)是一种以“对象”为核心的编程思想,通过封装、继承、多态等特性,将数据(属性)和操作数据的行为(方法)组织在一起,使代码更模块化、可复用、易维护。相比面向过程(按步骤执行),OOP 更适合描述复杂事物和大型项目。

一、核心概念:类与对象

OOP 的核心是类(Class)对象(Object)

  • :是对一类事物的抽象模板,定义了这类事物的共同属性(特征)和方法(行为)。例如,“狗”是一个类,属性包括“名字、年龄”,方法包括“叫、跑”。
  • 对象:是类的具体实例,是根据类创建的具体个体。例如,“我家的旺财(名字)、3岁(年龄),会汪汪叫(方法)”就是“狗”类的一个对象。

二、类的定义与对象的创建

1. 定义类(class 关键字)

class 关键字定义类,语法如下:

class 类名:
    """类的文档字符串(说明类的功能)"""
    # 类的属性和方法
  • 类名通常采用“大驼峰命名法”(每个单词首字母大写,如 DogPerson)。

2. 类的属性与方法

  • 属性:类或对象的特征(如名字、年龄),分为实例属性(每个对象独有)和类属性(所有对象共享)。
  • 方法:类中定义的函数,用于描述对象的行为(如“叫”“跑”),第一个参数必须是 self(代表实例本身)。
(1)实例属性与 __init__ 方法

__init__ 是类的构造方法(初始化方法),当创建对象时自动调用,用于初始化对象的属性。

  • self 是特殊参数,代表当前创建的对象,必须作为方法的第一个参数(名称可自定义,但约定用 self)。

示例:定义“狗”类

class Dog:
    """狗类,包含名字、年龄属性和叫、跑方法"""

    # 构造方法:初始化实例属性
    def __init__(self, name, age):
        # self.属性名:定义实例属性(每个对象独有)
        self.name = name  # 名字
        self.age = age    # 年龄

    # 实例方法:描述对象的行为
    def bark(self):
        """狗叫的方法"""
        print(f"{self.name} 汪汪叫!")

    def run(self):
        """狗跑的方法"""
        print(f"{self.name} 跑起来了!")
(2)创建对象(实例化)

通过“类名(参数)”创建对象(实例化),参数需与 __init__ 方法中的参数(除 self 外)匹配。

示例:创建“狗”对象

# 创建第一个对象(实例):旺财,2岁
dog1 = Dog("旺财", 2)
# 创建第二个对象:小白,3岁
dog2 = Dog("小白", 3)
(3)访问属性与调用方法

通过“对象.属性”访问属性,通过“对象.方法()”调用方法。

# 访问属性
print(dog1.name)  # 输出:旺财
print(dog2.age)   # 输出:3

# 调用方法
dog1.bark()  # 输出:旺财 汪汪叫!
dog2.run()   # 输出:小白 跑起来了!
(4)类属性(所有对象共享)

类属性定义在类中、__init__ 方法外,属于类本身,所有对象共享同一个值。

示例:给“狗”类添加“腿的数量”类属性

class Dog:
    # 类属性:所有狗都有4条腿
    leg_count = 4  # 类属性

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # ... 其他方法同上
  • 访问类属性:通过“类名.属性”或“对象.属性”(推荐用类名,更清晰)。
print(Dog.leg_count)  # 输出:4(通过类访问)
print(dog1.leg_count) # 输出:4(通过对象访问,共享类属性)

三、面向对象的三大特性

1. 封装(Encapsulation)

将数据(属性)和操作数据的方法捆绑在类中,隐藏内部实现细节,仅通过公开的方法与外部交互,提高安全性。

示例:限制年龄只能是正数

class Dog:
    def __init__(self, name, age):
        self.name = name
        # 通过方法设置年龄,而非直接赋值
        self.set_age(age)

    # 公开方法:设置年龄(验证合法性)
    def set_age(self, age):
        if age > 0:
            self._age = age  # 用 _ 表示“私有”属性(约定,非强制)
        else:
            print("年龄必须是正数!")
            self._age = 1  # 默认值

    # 公开方法:获取年龄
    def get_age(self):
        return self._age

# 使用
dog = Dog("旺财", -2)  # 输出:年龄必须是正数!
print(dog.get_age())   # 输出:1(默认值)
  • Python 中没有真正的“私有属性”,但习惯用前缀 ___ 表示不希望外部直接访问(__ 会触发名称修饰,更难直接访问)。

2. 继承(Inheritance)

子类(派生类)可以继承父类(基类)的属性和方法,无需重复编写,并可添加新属性/方法或重写父类方法,实现代码复用。

语法class 子类名(父类名):

示例:继承“狗”类创建“导盲犬”子类

class GuideDog(Dog):  # 继承 Dog 类
    """导盲犬是狗的子类,新增引导方法"""

    # 新增子类方法
    def guide(self, person):
        print(f"{self.name} 正在引导 {person} 过马路!")

    # 重写父类方法(覆盖父类的 bark 方法)
    def bark(self):
        print(f"{self.name} 小声叫:旺(提醒主人注意)")

# 创建导盲犬对象
guide_dog = GuideDog(" Lucky ", 3)
guide_dog.bark()       # 调用重写的方法 → Lucky 小声叫:旺(提醒主人注意)
guide_dog.guide("盲人") # 调用子类新增方法 → Lucky 正在引导 盲人 过马路!
print(guide_dog.leg_count)  # 继承父类的类属性 → 4
  • super() 函数:在子类中调用父类的方法(如 super().__init__(name, age) 调用父类的构造方法)。

3. 多态(Polymorphism)

不同类的对象对同一方法可以有不同的实现,调用时无需关心具体类型,只需调用方法即可,提高代码灵活性。

示例:多态体现

# 定义另一个子类:军犬
class ArmyDog(Dog):
    def bark(self):
        print(f"{self.name} 大声叫:汪汪汪(警告敌人)")

# 定义一个通用函数:让狗叫(不关心具体是哪种狗)
def make_dog_bark(dog):
    dog.bark()  # 调用 bark 方法,具体行为由对象类型决定

# 多态:同一方法调用,不同对象表现不同
guide_dog = GuideDog("Lucky", 3)
army_dog = ArmyDog("军军", 4)

make_dog_bark(guide_dog)  # → Lucky 小声叫:旺(提醒主人注意)
make_dog_bark(army_dog)   # → 军军 大声叫:汪汪汪(警告敌人)

四、类的特殊方法(魔术方法)

Python 类中以 __ 开头和结尾的方法称为特殊方法(如 __init__),用于实现特定功能(如运算符重载、打印对象等)。

常见特殊方法:

  • __str__:定义对象被 print 时的字符串显示(返回字符串)。
  • __repr__:定义对象的“官方”字符串表示(用于调试)。
  • __add__:重载 + 运算符。

示例:__str__ 方法

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 定义 print(dog) 时的显示内容
    def __str__(self):
        return f"狗的名字是 {self.name},年龄 {self.age} 岁"

dog = Dog("旺财", 2)
print(dog)  # 输出:狗的名字是 旺财,年龄 2 岁(调用 __str__ 方法)

五、面向对象 vs 面向过程

  • 面向过程:按步骤编写代码(如“获取数据→处理数据→输出结果”),适合简单任务。
  • 面向对象:将数据和行为封装成类,通过对象交互,适合复杂任务(如游戏开发、大型应用),代码更易扩展和维护。

总结

面向对象编程的核心是类(模板)对象(实例),通过封装、继承、多态三大特性实现代码复用和模块化:

  • 封装:隐藏细节,通过方法访问属性。
  • 继承:子类复用父类代码,减少重复。
  • 多态:同一方法在不同对象上有不同实现,灵活通用。

入门阶段需重点掌握类的定义、__init__ 方法、对象的创建与使用,再逐步理解三大特性的应用场景。

课程目标:了解面向对象并可以根据面向对象知识进行编写代码。

课程概要:

  • 初识面向对象
  • 三大特性(面向对象)
    • 封装
    • 继承
    • 多态

面向对象

面向对象编程是最有效的软件编写方法之一。

在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。

编写类时,你定义一大类对象都有的通用行为。

基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。

使用面向对象编程可模拟现实情景,其逼真程度达到了令人惊讶的地步。根据类来创建对象称为实例化,这让你能够使用类的实例。

为什么学习面向对象

理解面向对象编程有助于你像程序员那样看世界,还可以帮助你真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让你能够通过编写程序来解决遇到的几乎任何问题。

随着面临的挑战日益严峻,类还能让你以及与你合作的其他程序员的生活更轻松。如果你与其他程序员基于同样的逻辑来编写代码,你们就能明白对方所做的工作。你编写的程序将能被众多合作者所理解,每个人都能事半功倍。

创建和使用类

想要通过面向对象去实现某个或某些功能时需要2步:

  • 定义类,在类中定义方法,在方法中去实现具体的功能。

  • 实例化类并的个一个对象,通过对象去调用并执行方法。

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类Dog,它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,我们都知道些什么呢?它们都有名字和年龄。我们还知道,大多数小狗还会蹲下和打滚。由于大多数小狗都具备上述两项信息(名字和年龄)和两种行为(蹲下和打滚),我们的Dog类将包含它们。这个类让Python知道如何创建表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。

创建Dog类

根据Dog类创建的每个实例都将存储名字和年龄,我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力:

# dog.py

class Dog:
    """一次模拟小狗的简单尝试"""

    def __init__(self, name, age) -> None:
        """初始化属性name和age"""
        self.name = name
        self.age = age

    def sit(self):
        """模拟小狗收到命令时蹲下"""
        print(f"{self.name} is now sitting.")

    def roll_over(self):
        """模拟小狗收到命令打滚。"""
        print(f"{self.name} rolled over!")

dog = Dog('小黑', 1)
dog.roll_over()
dog.sit()
---------------------------------------------------------
小黑 rolled over!
小黑 is now sitting.  
---------------------------------------------------------

在Python中,首字母大写的名称指的是类。这个类定义中没有圆括号,因为要从空白创建这个类。编写了一个文档字符串,对这个类的功能做了描述。

方法__init__()

类中的函数称为方法。

__init__()是一个特殊方法,每当你根据Dog类创建新实例时,Python都会自动运行它。

在每个类中都可以定义个特殊的:__init__ 初始化方法,在实例化类创建对象时自动执行,即:对象=类()

在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。

在这个方法的定义中,形参self必不可少,而且必须位于其他形参的前面。

为何必须在方法定义中包含形参self呢?因为Python调用这个方法来创建Dog实例时,将自动传入实参self。每个与实例相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。

创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄,self会自动传递,因此不需要传递它。

self.name= name获取与形参name相关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例。self.age = age的作用与此类似。像这样可通过实例访问的变量称为属性。

Dog类还定义了另外两个方法:sit()roll_over()。这些方法执行时不需要额外的信息,因此它们只有一个形参self。我们随后将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。

对象和self

  • 对象,让我们可以在它的内部先封装一部分数据,以后想要使用时,再去里面获取。
  • self,类中的方法需要由这个类的对象来触发并执行( 对象.方法名 ),且在执行时会自动将对象当做参数传递给self,以供方法中获取对象中已封装的值。

三大特性

面向对象编程在很多语言中都存在,这种编程方式有三大特性:封装、继承、多态。

封装

封装主要体现在两个方面:

  • 将同一类方法封装到了一个类中,例如上述示例中:匪徒的相关方法都写在Terrorist类中;警察的相关方法都写在Police类中。
  • 将数据封装到了对象中,在实例化一个对象时,可以通过__init__初始化方法在对象中封装一些数据,便于以后使用。

继承

传统的理念中有:儿子可以继承父亲的财产。

在面向对象中也有这样的理念,即:子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)。

父类
子类

基类
派生类
class Base:

    def func(self):
        print("Base.func")

class Son(Base):

    def show(self):
        print("Son.show")

s1 = Son()
s1.show()
s1.func() # 优先在自己的类中找,自己没有才去父类。

s2 = Base()
s2.func()
class Base:
    def f1(self):
        pass

class Foo(Base):

    def f2(self):
        pass

class Bar(Base):

    def f3(self):
        pass

o1 = Foo()
o1.f2()
o1.f1()

多态

多态,按字面翻译其实就是多种形态。

在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型,例如:一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。

在python中,不同的对象调用同一个接口,表现出不同的状态,称为多态。

class Duck():
    def who(self):
        print("I am a duck")

class Dog():
    def who(self):
        print("I am a dog")

class Cat():
    def who(self):
        print("I am a cat")
if __name__ == "__main__":
    duck=Duck()
    dog=Dog()
    cat=Cat()
    duck.who()
    dog.who()
    cat.who()

输出结果:

I am a duck
I am a dog
I am a cat

以上代码是多态吗?虽然不同的对象调用同一个接口表现出不同状态,但是!!这不是多态!要实现多态有两个前提:

1.继承:多态必须发生在父类与子类之间

2.重写:子类重写父类方法

把以上代码进行修改,使之继承父类,才实现多态,如下:

class Animal():
    def who(self):
        print("I am an Animal")

class Duck(Animal):
    def who(self):
        print("I am a duck")

class Dog(Animal):
    def who(self):
        print("I am a dog")

class Cat(Animal):
    def who(self):
        print("I am a cat")

if __name__ == "__main__":
    duck=Duck()
    dog=Dog()
    cat=Cat()
    duck.who()
    dog.who()
    cat.who()

多态有什么用?

其实以上代码实际貌似没啥用,为什么一定要去定义通过同一个函数名?我定义who1,who2,who3不可以吗?代码量也一样啊!

其实真正体验出多态好处的代码一般是这样写的

class Animal():
    def who(self):
        print("I am an Animal")

class Duck(Animal):
    def who(self):
        print("I am a duck")

class Dog(Animal):
    def who(self):
        print("I am a dog")

class Cat(Animal):
    def who(self):
        print("I am a cat")

def func(obj):
    obj.who()

if __name__ == "__main__":
    duck=Duck()
    dog=Dog()
    cat=Cat()
    func(duck)
    func(dog)
    func(cat)

仅仅需要一个函数,就可以把不同对象的who函数表现出来了。这就增加了程序的灵活性,以不变应万变,不管你类中的who()写得如何得天花乱坠,我都可以用一个统一的形式来调用。另外,它增加了程序的可扩展性,不管是我们或者调用者,如果想增加新的功能,都不需要修改原来的代码。

例如,我想新增一个bird类,仅需增加以下代码,无需修改原来的。

class Bird(Animal):
    def who(self):
        print("I am a bird")

对于调用者,也仅需增加以下代码,无需修改原来的。

bird=Bird()
func(bird)

所以说多态有什么用?一是增加程序的灵活性,二是增加程序的可扩展性