3 Python面向对象编程

本章将会针对面向对象编程进行讲述,思路适用于所有的面向对象编程语言。

首先要理解什么样的编程方式才被成为面向对象,以及面向对象和面向过程的区别:
举个例子,现在我想把大象关进冰箱,你来用代码实现整个过程,写代码的时候,你会写:博主把大象关进冰箱共分为三步,第一步博主打开冰箱门,第二步博主把大象塞进冰箱,第三步博主关闭了冰箱门。这就是面向过程的编程思路。
那么怎样用面向对象的思维实现把大象关进冰箱?
首先分析整个过程的所有实体:有博主、大象和冰箱三个实体,这三个实体分别属于不同的类:博主是人类,大象是动物类,冰箱是家电类或者干脆就叫冰箱类(因为冰箱有很多不同的类型)。这里说的类是只每个实际存在的个体所属的类别,每个实际的个体就是这个类的实例。在面向对象编程中,我们首先需要把类定义出来,然后用类去构造对象(对象指的是实际存在的具体的个体,例如博主是人类的一个个体,我家的冰箱也是冰箱类的一个对象,被我关进冰箱的那头大象是大象类的一个具体的对象)。

这个教程不会对面向对象编程进行非常深入的讲解,读者只需要掌握如何定义类、如何使用类构造对象、理解python类的定义中成员函数和成员变量的定义和引用就足够了。
此外,作为基础知识的补充,读者要理解面向对象的基本要素:

  • 封装:通过设置属性和函数为私有(private)增加访问权限实现封装
  • 继承:把类分为父类和子类,子类继承父类的全部属性和方法(函数)。注意python是支持多重继承的,即一个子类可以继承多个父类;Java不支持多重继承。
  • 多态:指同一个函数的调用,由于对象不同而可能产生不同的行为。多态仅仅局限在方法(函数)的多态,方法可以理解为动作或行为;属性是没有多态的。多态存在的前提是继承和子类重写父类的方法。在面向对象语言中,父类可以有多个子类,这些父类和子类通常拥有类似的行为,例如所有国家的人类都能说话和吃东西,但是说的语言不同,饮食习惯也不同,中国人说中文吃火锅,德国人说德语吃土豆,这就构成了多态——即:属于同一类的不同对象拥有多种形态。
    *注意:由于python是动态语言,对于数据类型的要求不高,因此我并没有铺开来讲。而在java这种强类型静态语言中,这三大特性极为重要,数据类型一定要匹配
    如果有读者需要深入学习面向对象,可以参考如下教程:刘江的博客教程

现在开始我的正文:

1 定义类和类的属性(成员变量)、成员函数

现在,根据你的分析,向美国的扩张已经完成了,团队面临着更好地管理单个播客集的任务。我们现在进行系统化管理。

  1. 请创建一个podcast类,其属性为episodetitlelengthmoderatoradverts
  2. 前四个属性在创建新对象时被初始化。默认情况下,广告被初始化为0。
  3. 除了属性之外,最初应该只有一个方法display,通过display方法,要输出单个播客对象的所有信息的摘要(即输出Podcast类的对象的属性)。
class Podcast:
    def __init__(self, episode, title, length, moderator):
        # 属性的初始化,注意有默认值的属性不需要写在构造函数的参数表中,构造对象时默认值会自动生成
        self.Episode = episode
        self.Title = title
        self.Length = length
        self.Modetator = moderator
        self.Adverts = 0

    # 定义成员函数,参数表只有self,self表示Podcast对象,该对象拥有全部属性
    def display(self):
        print(f'Podcast episode Nr.: {self.Episode} with moderator {self.Modetator}\n')
        print(f'Theme of this podcast episode: {self.Title}\n')
        print(f'Length of this podcast episode: {self.Length}\n')
        print(f'Advertising blocks currently sold: {self.Adverts}')

*注意:定义类的时候,第一个参数按照惯例是self,用于表示这个类的对象,类似Java的this。在定义成员函数时,只要涉及调用类的属性,就必须通过self来调用。此外,无论定义类还是成员函数,都要考虑清楚参数表的内容

现在尝试构造一个podcast类,并调用其成员函数。

newPodcast = Podcast(23, 'Traveling Salesman Problems', 70, 'Square Pants Bob')
newPodcast.display()
海绵宝宝讲TSP

*注意:在我们构造类的时候,构造函数__init__()会自动调用,而当我们成功构造出这个类的对象后,我们才可以用这个对象调用该对象所属类的成员函数

2 丰富类的定义,增加新的成员函数

面向对象编程的一个很大的优势在于,当我们希望给所有对象增加新的行为或功能时,只需要修改类的代码。也就是说类相当于对象的模板。
上一小节只是把播客类的框架搭建好了,现在给这个类增加新的功能:

  1. 初始化后,对象的属性不应该再能从外部控制。——访问限制,封装,私有属性
  2. 除了现有的方法外,还要整合如下的方法
  • 方法setAdverts将3分钟的广告块添加到播客的长度中。增加多少广告取决于传递的参数值(adverts)。广告的数量被存储在相应的属性(Adverts)中。在一集播客中加入的广告时间不得超过三次。

  • 方法 "getAdverts "输出当前的播客中广告的数量。

  • cut方法可以缩短一个播客。削减多少分钟取决于所传递的参数。然而,缩短后的播客时长不能低于30分钟。如果缩短后播客时长小于30分钟的限度,应该出现相应的提示信息。

  • 请按照规范扩展Podcast类,然后执行相应的代码。

class Podcast:
    def __init__(self, episode, title, length, moderator):
        # 属性名前面加两条下划线表示私有属性,后期调用这些私有属性的时候也要相应地加上下划线
        self.__Episode = episode
        self.__Title = title
        self.__Length = length
        self.__Modetator = moderator
        self.__Adverts = 0

    # 定义成员函数,参数表只有self,self表示Podcast对象,该对象拥有全部属性
    def display(self):
        print(f'Podcast episode Nr.: {self.__Episode} with moderator {self.__Modetator}\n')
        print(f'Theme of this podcast episode: {self.__Title}\n')
        print(f'Length of this podcast episode: {self.__Length}\n')
        print(f'Advertising blocks currently sold: {self.__Adverts}')


    def setAdverts(self, adverts):
        if self.__Adverts + adverts > 3:
            print(f'Attention: Only a maximum of {3 - self.__Adverts} adverts may be added.\n')
        else:
            self.__Adverts += adverts
            self.__Length += 3 * adverts

    def getAdverts(self):
        return self.__Adverts

    def cut(self, minutes):
        if self.__Length - minutes >= 30:
            self.__Length -= minutes
        else:
            print("Attention: This episode cannot be shortened any further. It is currently " +str(self.__Length) + " minutes long.\n")

*注意:这里需要注意的点是,成员函数需要先进行判断,然后如果满足条件则进行赋值操作,如果不满足则进行print,输出提醒

现在再进行一次实例化,依然创建海绵宝宝讲TSP的播客对象,看看这次的功能有没有增加:

newPodcast = Podcast(23, 'Traveling Salesman Problems', 70, 'Square Pants Bob')
newPodcast.display()

newPodcast.setAdverts(2) # 这条语句执行之前,还没有加入广告,因此这个setAdverts无论如何都会执行
newPodcast.setAdverts(2) # 这一行执行前已经有2条广告了,无法再插入两条,因此会输出一个提醒

newPodcast.cut(45)
newPodcast.cut(10)

newPodcast.getAdverts()

*注意:只要涉及到使用函数,则必须注意函数名的写法和该函数要求的参数表。看参数表的时候尤其需要注意数据类型(现在我们见到的数据类型都是int,str这样的基本类型,事实上我们自己定义的类也是数据类型,也就是说函数的参数可以是我们自定义的类的对象)
*注意:成员函数的参数表总是包含self这个参数,在调用成员函数的时候不需要任何self变量,因此我们调用成员函数时只需要填写除了self之外的参数,例如getAdverts(self),在调用的时候括号内不用写任何参数

3 继承:子类和父类

定义子类时,要用到下面的语法:

class 子类名(父类名):

子类包含了父类所有的属性和方法,但是子类又未必拥有父类的所有属性和方法,这就需要用到重写或覆盖的概念,以播客类为例,
作为最后一步,将引入一个SpecialPodcast类,除了现有的属性外,它还应该包含一个specialguest。然而,由于嘉宾的确认总是在相对较短的时间内进行的,因此可以使用changeGuest方法再次改变specialguest

请使用继承创建该子类,并且该类中包括附加属性和新方法。

class SpecialPodcast(Podcast):

    def __init__(self, episode, title, length, moderator, specialguest: str):
        super().__init__(episode, title, length, moderator)
        self.__SpecialGuest = specialguest
    
    def display(self):
        # 调用父类display函数
        super().display() 

        # 父类并不能完全满足需求,则增加一条属于子类的输出语句
        print(f"Specialguest der Podcastfolge ist: {self.SpecialGuest}") 
    
    @property  # 装饰器,decorator,用于将方法变成属性来调用
    def SpecialGuest(self):
        return self.__SpecialGuest

    def changeGuest(self, newGuest):
        if type(newGuest) != str:
            pass
        else:
            self.__SpecialGuest = newGuest
        return self.__SpecialGuest

sp = SpecialPodcast(1, "Vehicle Routing Problem", 65, "M. Ipler", "Dr. Best")
sp.changeGuest("Dr. Secondbest")
sp.display()
sp.changeGuest(2)
继承

4 super().init()

本小节引用自红鲤鱼与彩虹

init()一般用来创建对象的实例变量,或一次性操作,super()用于调用父类的方法,可用来解决多重继承问题,直白的说super().init(),就是继承父类的init方法,同样可以使用super()点 其他方法名,去继承其他方法。

测试一、我们尝试下面代码,没有super(A, self).init()时调用A的父类Root的属性和方法(方法里不对Root数据进行二次操作)

class Root(object):
    def __init__(self):
        self.x= '这是属性'

    def fun(self):
        #print(self.x)
        print('这是方法')
        
class A(Root):
    def __init__(self):
        print('实例化时执行')

test = A()      #实例化类
test.fun()  #调用方法
test.x      #调用属性

可以看到此时父类的方法继承成功,可以使用,但是父类的属性却未继承,并不能用

*这里父类属性不能被继承,是因为父类的构造函数是私有的,父类私有的东西只能在父类中调用(x属性是公有的)

测试二、我们尝试下面代码,没有super(A,self).init()时调用A的父类Root的属性和方法(方法里对Root数据进行二次操作)

class Root(object):
    def __init__(self):
        self.x= '这是属性'

    def fun(self):
        print(self.x)
        print('这是方法')
        
class A(Root):
    def __init__(self):
        print('实例化时执行')

test = A()      #实例化类
test.fun()  #调用方法
test.x      #调用属性

可以看到此时报错和测试一相似,果然,还是不能用父类的属性

*注意:这里父类fun函数中调用了父类自己的属性,子类本应该继承了这个函数,但是实际上子类并不能使用父类的属性

测试三、我们尝试下面代码,加入super(A, self).init()时调用A的父类Root的属性和方法(方法里对Root数据进行二次操作)

class Root(object):
    def __init__(self):
        self.x = '这是属性'

    def fun(self):
        print(self.x)
        print('这是方法')


class A(Root):
    def __init__(self):
        super(A,self).__init__()
        print('实例化时执行')


test = A()  # 实例化类
test.fun()  # 调用方法
test.x  # 调用属性
执行父类构造函数

此时A已经成功继承了父类的属性,所以super().init()的作用也就显而易见了,就是执行父类的构造函数,使得我们能够调用父类的属性。

你可能感兴趣的:(3 Python面向对象编程)