16、面向对象编程入门

面向对象编程入门

1. 编程范式概述

在编程领域,主要存在两种编程范式:过程式编程和面向对象编程。

1.1 过程式编程

过程式编程就像一份食谱,它以函数和代码块的形式提供一系列步骤,这些步骤按顺序执行以完成任务。在这种编程方式中,程序围绕函数来构建。

1.2 面向对象编程

面向对象编程则提供了一种组织程序的方式,它将属性和行为捆绑到单个对象中。例如,一个对象可以代表一个人,具有姓名、年龄、地址等属性,以及行走、说话、呼吸和跑步等行为;或者代表一封电子邮件,具有收件人列表、主题、正文等属性,以及添加附件和发送等行为。简而言之,面向对象编程是一种对具体的现实事物(如汽车)以及事物之间的关系(如公司和员工、学生和教师等)进行建模的方法。

需要注意的是,Python 是一种多范式编程语言,你可以选择最适合当前问题的范式,在一个程序中混合使用不同的范式,并且随着程序的发展从一种范式切换到另一种范式。

2. 类和实例

2.1 类

类用于创建新的用户自定义数据结构,它包含关于某个事物的任意信息。以动物为例,我们可以创建一个 Animal() 类来跟踪动物的属性,如姓名和年龄。

类只是提供结构,它是一个蓝图,规定了某个事物应该如何定义,但本身并不提供任何实际内容。例如, Animal() 类可能规定定义一个动物需要姓名和年龄,但它不会具体说明某个特定动物的姓名或年龄是什么。可以将类看作是关于如何定义某个事物的一种概念。

2.2 实例

类是蓝图,而实例是具有实际值的类的副本,它是属于某个特定类的实际对象。例如,一只名叫 Roger 的八岁狗就是 Animal() 类的一个实例。

也可以把类比作一张表格或问卷,它定义了所需的信息。当你填写完表格后,你填写的具体副本就是该类的一个实例,它包含了与你相关的实际信息。你可以填写多份表格来创建多个不同的实例,但如果没有表格作为指导,你就会不知道需要哪些信息。因此,在创建对象的单个实例之前,我们必须先通过定义类来明确所需的信息。

3. 定义类

3.1 基本语法

定义一个类很简单,示例代码如下:

class Dog(object):
    pass

这里,我们使用 class 关键字来表明正在创建一个类,然后添加类的名称(使用驼峰命名法,首字母大写),最后在括号中添加要继承的类(关于继承将在后面详细介绍)。 pass 关键字通常用作占位符,允许我们运行这段代码而不抛出错误。

3.2 实例属性

所有类都会创建对象,而所有对象都包含称为属性的特征(在开头段落中称为属性)。我们使用 __init__() 方法来初始化对象的初始属性,为它们赋予默认值(或状态)。这个方法至少需要一个参数以及 self 变量, self 变量指的是对象本身(例如 Dog )。示例代码如下:

class Dog(object):
    # Initializer / Instance Attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

Dog() 类中,每只狗都有特定的姓名和年龄,这在实际创建不同的狗时显然是很重要的信息。需要注意的是,类只是用于定义狗,而不是实际创建具有特定姓名和年龄的单个狗的实例。

self 变量也是类的一个实例。由于类的实例具有不同的值,我们本可以写成 Dog.name = name 而不是 self.name = name 。但并非所有的狗都有相同的姓名,我们需要能够为不同的实例分配不同的值。因此,需要使用特殊的 self 变量来跟踪每个类的单个实例。

另外,你永远不需要手动调用 __init__() 方法,当你创建一个新的 Dog 实例时,它会自动被调用。

3.3 类属性

实例属性是每个对象特有的,而类属性对于所有实例都是相同的。在 Dog() 类中,我们可以添加一个类属性 species ,示例代码如下:

class Dog(object):
    # Class Attribute
    species = 'mammal'
    # Initializer / Instance Attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

这样,虽然每只狗都有独特的姓名和年龄,但每只狗都是哺乳动物。

4. 实例化

实例化是创建类的新的、唯一实例的专业术语。以下是一些示例:

>>> class Dog(object):
...     pass
...
>>> Dog()
<__main__.Dog object at 0x1004ccc50>
>>> Dog()
<__main__.Dog object at 0x1004ccc90>
>>> a = Dog()
>>> b = Dog()
>>> a == b
False

我们首先定义了一个新的 Dog() 类,然后创建了两只新的狗,分别分配给不同的对象。要创建类的实例,只需使用类名,后面跟上括号。为了证明每个实例实际上是不同的,我们又实例化了两只狗,将它们分别分配给变量,然后测试这些变量是否相等。

下面是一个稍微复杂一些的示例:

class Dog(object):
    # Class Attribute
    species = 'mammal'
    # Initializer / Instance Attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

# Instantiate the Dog object
philo = Dog("Philo", 5)
mikey = Dog("Mikey", 6)

# Access the instance attributes
print("{} is {} and {} is {}.".format(
    philo.name, philo.age, mikey.name, mikey.age))

# Is Philo a mammal?
if philo.species == "mammal":
    print("{0} is a {1}!".format(philo.name, philo.species))

将上述代码保存为 dog_class.py 并运行,你会看到如下输出:

Philo is 5 and Mikey is 6.
Philo is a mammal!

在这个例子中,我们创建了 Dog() 类的一个新实例,并将其分配给变量 philo 。然后我们传递了两个参数 "Philo" 5 ,分别代表这只狗的姓名和年龄。这些属性被传递给 __init__ 方法,每当你创建一个新实例时,该方法就会被调用,将姓名和年龄附加到对象上。你可能会疑惑为什么我们不需要传递 self 参数,这是 Python 的特性;当你创建类的新实例时,Python 会自动确定 self 是什么(在这个例子中是 Dog ),并将其传递给 __init__ 方法。

4.1 实例化练习

使用相同的 Dog() 类,实例化三只年龄不同的新狗。然后编写一个名为 get_biggest_number() 的函数,该函数接受任意数量的年龄( *args )并返回最大的年龄。最后输出最老的狗的年龄,格式如下: The oldest dog is 7 years old. 将此文件保存为 oldest_dog.py

5. 实例方法

实例方法定义在类内部,用于获取实例的内容,也可以用于对对象的属性执行操作。和 __init__ 方法一样,实例方法的第一个参数总是 self 。示例代码如下:

class Dog(object):
    # Class Attribute
    species = 'mammal'
    # Initializer / Instance Attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # instance method
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # instance method
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)

# Instantiate the Dog object
mikey = Dog("Mikey", 6)

# call our instance methods
print(mikey.description())
print(mikey.speak("Gruff Gruff"))

将上述代码保存为 dog_instance_methods.py 并运行,输出如下:

Mikey is 6 years old
Mikey says Gruff Gruff

speak() 方法中,我们定义了狗的行为。你还可以为狗分配其他行为,回顾前面的段落可以看到其他对象的一些示例行为。

5.1 修改属性

你可以根据某些行为更改属性的值,示例如下:

>>> class Email(object):
...     is_sent = False
...     def send_email(self):
...         self.is_sent = True
...
>>> my_email = Email()
>>> my_email.is_sent
False
>>> my_email.send_email()
>>> my_email.is_sent
True

在这个例子中,我们添加了一个发送电子邮件的方法,该方法将 is_sent 变量更新为 True

6. 继承

6.1 继承的概念

继承是一个类继承另一个类的属性和方法的过程。新形成的类称为子类,子类所继承的类称为父类。需要注意的是,子类可以覆盖或扩展父类的功能(如属性和行为)。也就是说,子类继承了父类的所有属性和行为,但也可以指定不同的行为。最基本的类是 object ,通常所有其他类都将其作为父类进行继承。

6.2 狗公园示例

假设我们在一个狗公园,有多个 Dog 对象在进行狗的行为,每个对象都有不同的属性。通俗地说,就是有些狗在跑步,有些在伸展,有些只是在看着其他狗。此外,每只狗都有主人给它起的名字,而且每只狗都会随着时间变老。

除了这些,我们还可以通过狗的品种来区分不同的狗,示例代码如下:

>>> class Dog(object):
...     def __init__(self, breed):
...         self.breed = breed
...
>>> spencer = Dog("German Shepard")
>>> spencer.breed
'German Shepard'
>>> sara = Dog("Boston Terrier")
>>> sara.breed
'Boston Terrier'

不同品种的狗有稍微不同的行为。为了考虑这些差异,我们可以为每个品种创建单独的类,这些类是父类 Dog 的子类。

6.3 子类扩展父类功能

创建一个名为 dog_inheritance.py 的新文件,代码如下:

# Parent class
class Dog(object):
    # Class attribute
    species = 'mammal'
    # Initializer / Instance attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # instance method
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # instance method
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)

# child class (inherits from Dog class)
class RussellTerrier(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)

# child class (inherits from Dog class)
class Bulldog(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)

# child classes inherit attributes and
# behaviors from the parent class
jim = Bulldog("Jim", 12)
print(jim.description())

# child classes have specific attributes
# and behaviors as well
print(jim.run("slowly"))

在运行这个程序之前,先大声朗读注释以帮助理解发生了什么,然后尝试预测预期的输出。运行程序后,你应该会看到如下输出:

Jim is 12 years old
Jim runs slowly

虽然我们没有添加任何特殊的属性或方法来区分 RussellTerrier Bulldog ,但由于它们现在是两个不同的类,我们可以为它们定义不同的类属性,以定义它们各自的速度。

6.4 父类和子类的关系判断

可以使用 isinstance() 函数来确定一个实例是否也是某个父类的实例。保存以下代码为 dog_isinstance.py

# Parent class
class Dog(object):
    # Class attribute
    species = 'mammal'
    # Initializer / Instance attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # instance method
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # instance method
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)

# child class (inherits from Dog() class)
class RussellTerrier(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)

# child class (inherits from Dog() class)
class Bulldog(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)

# child classes inherit attributes and
# behaviors from the parent class
jim = Bulldog("Jim", 12)
print(jim.description())

# child classes have specific attributes
# and behaviors as well
print(jim.run("slowly"))

# is jim an instance of Dog()?
print(isinstance(jim, Dog))

# is julie an instance of Dog()?
julie = Dog("Julie", 100)
print(isinstance(julie, Dog))

# is johnny walker an instance of Bulldog()
johnnywalker = RussellTerrier("Johnny Walker", 4)
print(isinstance(johnnywalker, Bulldog))

# is julie and instance of jim?
print(isinstance(julie, jim))

输出如下:

('Jim', 12)
Jim runs slowly
True
True
False
Traceback (most recent call last):
  File "dog_isinstance.py", line 50, in <module>
    print isinstance(julie, jim)
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

这表明 jim julie 都是 Dog() 类的实例,而 johnnywalker 不是 Bulldog() 类的实例。最后我们测试 julie 是否是 jim 的实例,这是不可能的,因为 jim 是一个类的实例而不是类本身,所以会出现 TypeError

6.5 子类覆盖父类功能

子类还可以覆盖父类的属性和行为,示例如下:

>>> class Dog(object):
...     species = 'mammal'
...
>>> class SomeBreed(Dog):
...     pass
...
>>> class SomeOtherBreed(Dog):
...     species = 'reptile'
...
>>> frank = SomeBreed()
>>> frank.species
'mammal'
>>> beans = SomeOtherBreed()
>>> beans.species
'reptile'

SomeBreed() 类从父类继承了 species 属性,而 SomeOtherBreed() 类覆盖了 species 属性,将其设置为 reptile

6.6 继承练习

  1. 创建一个 Pet() 类,用于保存狗的实例;这个类与 Dog() 类完全独立,即 Dog() 类不继承自 Pet() 类。然后将三个狗的实例分配给 Pet() 类。从以下代码开始,将文件保存为 pet_class.py ,输出应该如下所示: I have 3 dogs. Tom is 6. Mike is 7. Larry is 9. And they're all mammals, of course.
# Parent class
class Dog(object):
    # Class attribute
    species = 'mammal'
    # Initializer / Instance attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # instance method
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # instance method
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)

# child class (inherits from Dog() class)
class RussellTerrier(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)

# child class (inherits from Dog() class)
class Bulldog(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)
  1. 在同一个文件中,为 Dog() 类添加一个类属性 is_hungry = True 。然后添加一个名为 eat() 的方法,该方法在被调用时将 is_hungry 的值更改为 False 。找出喂养每只狗的最佳方法,然后如果所有狗都饿了,输出 My dogs are hungry. ;如果所有狗都不饿,输出 My dogs are not hungry. 最终输出应该如下所示: I have 3 dogs. Tom is 6. Mike is 7. Larry is 9. And they're all mammals, of course. My dogs are not hungry.
  2. 接下来,为 Pet() 类和 Dog() 类都添加一个 walk() 方法,这样当你在 Pet() 类上调用该方法时,分配给 Pet() 类的每个狗实例都会 walk() 。将此保存为 dog_walking.py 。这稍微有点难度,首先按照 speak() 方法的方式实现该方法。对于 Pet() 类中的方法,你需要遍历狗的列表,然后调用该方法本身。输出应该如下所示:
Tom is walking!
Mike is walking!
Larry is walking!

7. 总结

以下是关于面向对象编程的一些关键概念总结:
| 概念 | 描述 |
| ---- | ---- |
| 类 | 用于创建新的用户自定义数据结构,是一个蓝图,规定了事物的定义方式,但不包含实际内容 |
| 实例 | 具有实际值的类的副本,是属于某个特定类的实际对象 |
| 实例属性 | 每个对象特有的属性,通过 __init__ 方法初始化 |
| 类属性 | 对于所有实例都相同的属性 |
| 实例方法 | 定义在类内部,用于获取实例内容或对属性执行操作,第一个参数总是 self |
| 继承 | 一个类继承另一个类的属性和方法,子类可以扩展或覆盖父类的功能 |

下面是一个简单的面向对象编程流程的 mermaid 流程图:

graph TD;
    A[定义类] --> B[创建实例];
    B --> C[访问属性和方法];
    C --> D[修改属性];
    A --> E[子类继承父类];
    E --> F[子类扩展或覆盖父类功能];

通过学习面向对象编程,我们可以更高效地组织和管理代码,提高代码的可维护性和可扩展性。希望这些内容能帮助你更好地理解和应用面向对象编程。

8. 理解检查作业

8.1 问题解答

以下是对一些面向对象编程相关问题的解答:
1. 什么是类?
类是用于创建新的用户自定义数据结构的蓝图,它规定了某个事物应该如何定义,但本身不包含实际内容。例如, Dog 类规定了狗应该有姓名和年龄等属性,但不会具体说明某只狗的姓名和年龄是什么。
2. 什么是实例?
实例是具有实际值的类的副本,是属于某个特定类的实际对象。比如,一只名叫 Roger 的八岁狗就是 Dog 类的一个实例。
3. 类和实例之间的关系是什么?
类是创建实例的模板,实例是类的具体实现。类定义了对象的属性和方法,而实例则是这些属性和方法的具体体现,每个实例都有自己的属性值。
4. Python 中定义新类的语法是什么?
使用 class 关键字,后面跟上类名(采用驼峰命名法,首字母大写),再在括号中指定要继承的类(通常为 object ),示例如下:

class Dog(object):
    pass
  1. 类名的拼写约定是什么?
    类名通常使用驼峰命名法,即每个单词的首字母大写,其余字母小写,例如 Dog Animal 等。
  2. 如何实例化一个类,即创建类的实例?
    使用类名后面跟上括号的方式来实例化类,例如:
dog = Dog()

如果类的 __init__ 方法需要参数,则在括号中传入相应的参数,如:

dog = Dog("Philo", 5)
  1. 如何访问类实例的属性和行为?
    使用点号( . )来访问类实例的属性和方法,例如:
dog = Dog("Philo", 5)
print(dog.name)  # 访问属性
print(dog.speak("Woof"))  # 调用方法
  1. 什么是方法?
    方法是定义在类内部的函数,用于实现对象的行为。方法可以访问和修改对象的属性,并且可以执行特定的操作。例如, Dog 类中的 speak 方法就是一个实例方法。
  2. self 的作用是什么?
    self 是 Python 中一个特殊的参数,它代表类的实例本身。在类的方法中,使用 self 可以访问和修改实例的属性。当调用实例方法时,Python 会自动将实例作为 self 参数传递给方法。
  3. __init__ 方法的作用是什么?
    __init__ 方法是一个特殊的实例方法,也称为构造方法。它在创建类的实例时自动调用,用于初始化实例的属性。通过 __init__ 方法,可以为实例的属性赋予初始值。
  4. 继承如何帮助防止代码重复?
    继承允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以复用父类的代码,避免了重复编写相同的代码。例如,如果多个类都有一些共同的属性和方法,可以将这些共同的部分放在一个父类中,然后让其他类继承这个父类,这样就减少了代码的重复。
  5. 子类可以覆盖父类的属性吗?
    可以。子类可以覆盖父类的属性和方法,以实现不同的行为。当子类中定义了与父类相同名称的属性或方法时,子类的属性或方法会覆盖父类的属性或方法。例如,在前面的例子中, SomeOtherBreed 类覆盖了 Dog 类的 species 属性。

8.2 农场建模作业

在这个作业中,我们要创建一个简化的农场模型。以下是实现步骤:
1. 设计思路
- 我们需要至少四个类:一个父类 Animal 和至少三个继承自 Animal 的子类。
- 每个类应该有一些属性和至少一个方法,以体现动物的行为。
- 利用继承来避免代码重复。
2. 代码实现

# 父类 Animal
class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def description(self):
        return f"{self.name} is {self.age} years old."

    def sleep(self):
        return f"{self.name} is sleeping."

# 子类 Cow
class Cow(Animal):
    def __init__(self, name, age):
        super().__init__(name, age)

    def moo(self):
        return f"{self.name} says Moo!"

# 子类 Chicken
class Chicken(Animal):
    def __init__(self, name, age):
        super().__init__(name, age)

    def cluck(self):
        return f"{self.name} says Cluck!"

# 子类 Horse
class Horse(Animal):
    def __init__(self, name, age):
        super().__init__(name, age)

    def neigh(self):
        return f"{self.name} says Neigh!"

# 创建动物实例
cow = Cow("Daisy", 3)
chicken = Chicken("Betty", 1)
horse = Horse("Spirit", 5)

# 输出动物信息和行为
print(cow.description())
print(cow.moo())
print(chicken.description())
print(chicken.cluck())
print(horse.description())
print(horse.neigh())
  1. 代码解释
    • Animal 类是父类,包含了所有动物共有的属性(姓名和年龄)和方法(描述和睡觉)。
    • Cow Chicken Horse 类是子类,它们继承了 Animal 类的属性和方法,并分别添加了自己独特的方法( moo cluck neigh )。
    • 通过创建这些类的实例,我们可以输出动物的信息和它们的行为。

以下是一个农场动物类继承关系的 mermaid 流程图:

graph TD;
    A[Animal] --> B[Cow];
    A --> C[Chicken];
    A --> D[Horse];

9. 总结与展望

9.1 总结

面向对象编程是一种强大的编程范式,它通过类和对象的概念,将数据和操作封装在一起,提高了代码的可维护性和可扩展性。在本文中,我们学习了类、实例、属性、方法、继承等面向对象编程的核心概念,并通过多个示例和练习加深了对这些概念的理解。

9.2 展望

掌握面向对象编程后,我们可以更好地应对复杂的编程任务。在实际开发中,我们可以根据需求设计合理的类和继承关系,避免代码重复,提高开发效率。同时,面向对象编程也为我们学习更高级的编程技术,如设计模式、框架开发等打下了坚实的基础。希望大家在今后的编程实践中,能够灵活运用面向对象编程的思想,编写出更加优秀的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值