美文网首页Python 学习
详解 Python 中的面向对象编程(2)

详解 Python 中的面向对象编程(2)

作者: 数据科学工厂 | 来源:发表于2024-08-15 22:40 被阅读0次

    引言

    面向对象编程(OOP)是一种编程范式,它通过将属性和行为整合到对象中来构建程序。本教程将带你了解Python语言中面向对象编程的基本概念。

    想象一下,对象就像是系统中的各个部件。可以把程序比作一条工厂流水线。在流水线的每一个环节,部件都会对材料进行处理,最终将原材料变成成品。

    对象内部存储着数据,类似于流水线上各个环节所需的原材料或经过初步处理的材料。同时,对象还具有行为,即流水线上每个部件执行的具体操作。

    通过本教程,你将学会:

    • 如何定义一个类,这可以看作是创建对象的模板。
    • 如何利用类来生成新的对象。
    • 如何利用类继承来构建和模拟复杂的系统。

    如何继承另一个类?

    继承是一种机制,允许一个类获得另一个类的属性和方法。通过这种方式形成的新类称为子类,而作为继承基础的类则称为父类。

    继承父类是通过定义一个新类,并在新类的声明中将父类的名称放在括号内来实现的。

    # inheritance.py
    
    class Parent:
        hair_color = "brown"
    
    class Child(Parent):
        pass
    

    在这个简化的示例中,子类 Child 继承了父类 Parent 的特性。由于子类自动继承了父类的属性和方法,所以 Child 的 hair_color 属性也会是 "brown",无需你明确指定。

    子类不仅可以继承父类的所有属性和方法,还可以重写或扩展它们,以形成自己独特的特性和行为。

    虽然这个比喻不是完全准确,但你可以将对象的继承想象成遗传学中的遗传。比如,你的发色可能是从父母那里遗传来的,这是你出生时就确定的属性。但如果某天你决定将头发染成紫色,那么在这个属性上,你就相当于覆盖了从父母那里遗传来的特征。

    # inheritance.py
    
    class Parent:
        hair_color = "brown"
    
    class Child(Parent):
        hair_color = "purple"
    

    如果你对代码示例做出这样的修改,那么 Child 类的 hair_color 属性值将变为 "purple"。

    在某种程度上,你也继承了父母的语言。如果你的父母说英语,你自然也会说英语。设想你决定学习第二语言,例如德语。这样,你就扩展了自己的属性集,因为你添加了一个你的父母所不具备的新属性:

    # inheritance.py
    
    class Parent:
        speaks = ["English"]
    
    class Child(Parent):
        def __init__(self):
            super().__init__()
            self.speaks.append("German")
    

    你将在后续章节中更深入地了解上述代码的运作机制。但在深入探讨Python的继承概念之前,我们先去一个狗公园散步,这有助于你更好地理解在自己的代码中使用继承的原因。

    • 示例:狗公园

    想象一下,你现在身处一个狗公园。这里聚集了各种不同品种的狗,它们各自展示着不同的行为。

    假设你想用Python类来构建一个狗公园的模型。在上一节中你编写的Dog类能够根据名字和年龄来区分不同的狗,但还无法根据品种进行区分。

    你可以通过在编辑器窗口中为Dog类添加一个.breed属性来对其进行修改:

    # dog.py
    
    class Dog:
        species = "Canis familiaris"
    
        def __init__(self, name, age, breed):
            self.name = name
            self.age = age
            self.breed = breed
    
        def __str__(self):
            return f"{self.name} is {self.age} years old"
    
        def speak(self, sound):
            return f"{self.name} says {sound}"
    

    按下F5键来保存你的文件。接下来,你可以在交互式窗口中创建多种不同品种的狗,以此来构建一个狗公园的模型:

    >>> miles = Dog("Miles", 4, "Jack Russell Terrier")
    >>> buddy = Dog("Buddy", 9, "Dachshund")
    >>> jack = Dog("Jack", 3, "Bulldog")
    >>> jim = Dog("Jim", 5, "Bulldog")
    

    不同品种的狗有着各自独特的行为特征。比如,斗牛犬发出的低沉吠声听起来像是“汪汪”,而腊肠犬则发出更尖锐的“啾啾”声。

    如果只使用Dog类,每次调用Dog实例的.speak()方法时,你都需要为sound参数指定一个具体的叫声字符串:

    >>> buddy.speak("Yap")
    'Buddy says Yap'
    
    >>> jim.speak("Woof")
    'Jim says Woof'
    
    >>> jack.speak("Woof")
    'Jack says Woof'
    

    反复为.speak()方法传递字符串不仅繁琐,也缺乏便捷性。更合理的设计是让.breed属性自动决定每个Dog实例的叫声,但在当前情况下,你每次都需要手动为.speak()方法指定正确的字符串。

    为了改善使用Dog类的体验,你可以通过为每种狗的品种创建一个子类来实现。这样,你不仅可以扩展每个子类继承的功能,还可以为.speak()方法设置一个默认的叫声参数。

    • 父类与子类

    在接下来的部分,你将为前文提到的三种狗的品种——杰克罗素梗、腊肠犬和斗牛犬——各创建一个子类。

    以下是你目前所使用Dog类的完整定义,供参考:

    # dog.py
    
    class Dog:
        species = "Canis familiaris"
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __str__(self):
            return f"{self.name} is {self.age} years old"
    
        def speak(self, sound):
            return f"{self.name} says {sound}"
    

    在上一节完成了狗公园的示例之后,你已经去除了.breed属性。接下来,你将使用子类的方式来记录狗的品种信息。

    创建子类的过程是,你首先定义一个具有独立名称的新类,然后在其后添加父类的名称,并用括号括起来。在dog.py文件中添加以下代码,以便创建Dog类的三个新的子类:

    # dog.py
    
    # ...
    
    class JackRussellTerrier(Dog):
        pass
    
    class Dachshund(Dog):
        pass
    
    class Bulldog(Dog):
        pass
    

    按下F5键保存并执行文件。定义好子类之后,你就可以在交互式窗口里创建几种不同品种的狗了:

    >>> miles = JackRussellTerrier("Miles", 4)
    >>> buddy = Dachshund("Buddy", 9)
    >>> jack = Bulldog("Jack", 3)
    >>> jim = Bulldog("Jim", 5)
    

    子类的对象会继承其父类的所有特性和方法。

    >>> miles.species
    'Canis familiaris'
    
    >>> buddy.name
    'Buddy'
    
    >>> print(jack)
    Jack is 3 years old
    
    >>> jim.speak("Woof")
    'Jim says Woof'
    

    要识别一个特定对象属于哪个类,你可以利用Python内置的type()函数来查询:

    >>> type(miles)
    <class '__main__.JackRussellTerrier'>
    

    如果你想判断miles是否属于Dog类,可以使用内置的isinstance()函数进行判断:

    >>> isinstance(miles, Dog)
    True
    

    isinstance()函数需要两个参数:一个对象和一个类。如上例所示,isinstance()用来判断miles是否是Dog类的实例,结果返回True。

    >>> isinstance(miles, Bulldog)
    False
    
    >>> isinstance(jack, Dachshund)
    False
    

    miles、buddy、jack和jim这些对象都是Dog类的实例。但是,miles并不是Bulldog类的实例,同样,jack也不是Dachshund类的实例。

    # dog.py
    
    # ...
    
    class JackRussellTerrier(Dog):
        def speak(self, sound="Arf"):
            return f"{self.name} says {sound}"
    
    # ...
    

    通常来说,所有通过子类创建的对象都被视为父类的实例,尽管它们可能并不属于其他子类的实例。

    既然你已经为一些不同品种的狗定义了子类,现在你可以为每个品种的狗指定它们特有的叫声。

    • 扩展父类功能

    因为不同品种的狗叫声略有不同,你可能会想要为它们的.speak()方法的sound参数设定一个默认值。这需要你在每个品种的类定义中重写.speak()方法。

    重写父类中定义的方法,就是在子类中定义一个相同名称的方法。以下展示了如何在JackRussellTerrier类中进行这样的操作:

    # dog.py
    
    # ...
    
    class JackRussellTerrier(Dog):
        def speak(self, sound="Arf"):
            return f"{self.name} says {sound}"
    
    # ...
    

    .speak()方法已经在JackRussellTerrier类中被重新定义,其sound参数的默认值被设定为“Arf”。

    更新dog.py文件,加入新定义的JackRussellTerrier类,并按下F5键来保存并执行文件。此后,你可以直接在JackRussellTerrier的实例上调用.speak()方法,无需再为sound参数提供任何值。

    >>> miles = JackRussellTerrier("Miles", 4)
    >>> miles.speak()
    'Miles says Arf'
    

    狗狗有时会发出各种各样的声音。比如,如果Miles生气了,开始咆哮,你依然可以通过.speak()方法传入“Grrr”这样的不同声音来表达:

    >>> miles.speak("Grrr")
    'Miles says Grrr'
    

    关于类继承的一个重要概念是,对父类所做的更改会自动影响到子类,前提是子类没有重写被更改的属性或方法。

    举个例子,如果你在编辑器中修改了Dog类中.speak()方法的返回字符串:

    # dog.py
    
    class Dog:
        # ...
    
        def speak(self, sound):
            return f"{self.name} barks: {sound}"
    
    # ...
    

    保存并运行文件(按F5)。此时,如果你创建了一个新的Bulldog实例,比如命名为jim,调用jim.speak()将返回新的字符串格式:

    >>> jim = Bulldog("Jim", 5)
    >>> jim.speak("Woof")
    'Jim barks: Woof'
    

    但是,如果你在JackRussellTerrier实例上调用.speak(),输出的格式将不会按照Dog类的更新而改变:

    >>> miles = JackRussellTerrier("Miles", 4)
    >>> miles.speak()
    'Miles says Arf'
    

    有时候,我们可能需要完全重写父类中的某个方法。但在这种情况下,我们希望JackRussellTerrier类能够保留对Dog类.speak()方法输出格式可能做出的任何更改。

    为此,你需要在JackRussellTerrier子类中定义一个.speak()方法。与其明确指定输出字符串,不如在子类的.speak()方法内部,使用传递给JackRussellTerrier.speak()的相同参数,调用父类Dog的.speak()方法。

    你可以通过super()函数来访问子类方法中的父类:

    # dog.py
    
    # ...
    
    class JackRussellTerrier(Dog):
        def speak(self, sound="Arf"):
            return super().speak(sound)
    
    # ...
    

    当你在JackRussellTerrier类中调用super().speak(sound)时,Python会在Dog类中查找.speak()方法,并使用你提供的声音参数调用它。

    更新dog.py文件,加入修改后的JackRussellTerrier类。保存并运行(按F5),然后在交互式窗口中测试新的实现:

    >>> miles = JackRussellTerrier("Miles", 4)
    >>> miles.speak()
    'Miles barks: Arf'
    

    现在,当你调用miles.speak()时,输出的格式将与Dog类中更新后的格式保持一致。

    总结

    本教程向你介绍了Python中的面向对象编程(OOP)概念。像Java、C#和C++这样的现代编程语言都采用了OOP原则,所以你在这里学到的知识将对你未来的编程道路大有裨益。

    通过本教程,你学会了:

    • 如何定义一个类,它作为创建对象的模板
    • 如何通过类的实例化来生成具体的对象
    • 利用属性和方法来确定对象的特性和行为
    • 利用继承机制,从一个父类派生出多个子类
    • 使用super()来调用父类中的方法
    • 通过isinstance()函数来判断一个对象是否基于另一个类进行扩展

    动动您发财的小手点个赞吧!欢迎转发!

    相关文章

      网友评论

        本文标题:详解 Python 中的面向对象编程(2)

        本文链接:https://www.haomeiwen.com/subject/eewekjtx.html