小节7:Python之面向对象

1、基本概念

面向过程是编年体,面向对象是纪传体。

面向过程编程:

面向过程编程是负责完成某个具体任务的代码,基本可以理解为函数。面向过程编程的核心,就是把要实现的事情拆分成一个个步骤,依次完成。比如我们写一个ATM的程序,要往里面依次存入50元和存入100元,那就按照顺序写出这些步骤,并配合定义出存钱和取钱的函数,加入我们还要记录比如存钱和取钱是在哪个ATM机上进行的,以及这个ATM属于哪个银行,纸币的编号,那就可以把这些作为参数传进函数。这种写法虽然没什么毛病,但是如果要记录更多的性质,这些散落的数据一方面会增加函数参数的数量,另一方面,ATM有编号,纸币也有编号,这些数据在传参过程中混在一起,不利于我们理解其具体含义,随着程序长度和和逻辑复杂度的增加,代码的清晰度可能会由此降低

面向对象编程:
正如名字所言,相比过程,它以对象为核心。面向对象编程首先不会聚焦于第一步是去存钱还是取钱,而是模拟真实世界,先考虑各个对象有什么性质、能做什么事情。比如,每个ATM都有自己的性质,包括编号、银行、支行等。那么我们可以提取出这些性质,定义ATM类,然后用类创建对象。类和对象之间的关系是:类是创建对象的模板,对象是类的实例。更通俗点说,可以把类想象成制造具体对象的图纸。图纸上说:ATM可以有编号、银行、支行这三个属性,那么通过ATM类创建出来的ATM实例,不论是哪个银行哪个支行什么编号的ATM,都是ATM对象,而这些编号、银行、支行则是不同ATM对象各自的属性。我们可以用相同的方式,把纸币类和纸币对象也定义出来,这样,这些对象就可以作为参数传入存钱和取钱函数中。

除了属性之外,另一个可以和对象绑定的是方法。正如真实世界里,对象拥有不同的属性,并且能做不同的事情,属性就对应对象拥有的性质,而方法就对应对象能做些什么。所谓方法,就是放在类里面的函数,所谓属性,就是放在类里面的变量。

2、面向对象的优点:

面向对象除了能让参数更少之外,用对象把相关属性绑定在一起,有利于让程序逻辑更加清晰。

3、面向对象的三大特性——封装、继承、多态

1)封装

封装表示写类的人,将内部实现细节隐藏起来,使用类的人只通过外部接口访问和使用。(接口可以被大致理解为提供使用的方法。

比如:已经有人写好了洗衣机这个类,你其实只需要知道它有什么方法,方法有什么作用,具体怎么用就足够了,不需要知道方法里面具体是怎么写的,就像我们使用洗衣机,都是通过操作按钮来使用,不需要知道里面的原理,更不需要区研究电路板)封装可以减少我们对不必要细节的精力投入。

2)继承

继承是在说,面向对象编程允许创建有层次的类,就像现实中的儿子继承爸爸,爸爸继承爷爷,类也可以有子类和父类,来表示从属关系。

比如:小学生、大学生都是学生,都有学号、年纪的属性,都要去学校,这两个类的共同之处,导致有很多重复代码产生。那么我们可以创建一个叫学生的父类,然后让小学生和大学生区继承这个类,这样做的好处是:父类的属性、方法都可以被继承,不需要反复定义,减少代码的冗余。

3)多态

堕胎指的是,同样的接口,因为对象具体类的不同而有不同表现。

比如:虽然小学生和大学生都要写作业,但是内容的难度肯定不一样,所以这个写作业的方法就不能直接定义在父类里面,而是要分别定义在子类里。

小节7:Python之面向对象_第1张图片

PS:面向对象并不一定全面优于面向过程,如果全方位碾压,那面向过程也就不会成为另一个主流了。选择哪个,还是取决于具体的场景、需求。

4、创建类

1)类的命名规则:

class 类名:

PS:类的命名遵循Pascal命名法,即:用首字母大写来分隔单词。如下图所示:

小节7:Python之面向对象_第2张图片

2)构造函数:

类有一个特殊的方法,叫构造函数,主要作用是定义实例对象的属性,它必须要被命名为__init__(self, 参数1, 参数2, ...),其中self表示对象自身,它能够把属性绑定在实例对象上,比如:self.name = "Lambton",说明是对象的name属性的值,如果直接写name = "Lambton",Python会觉得只是在给普通的name变量赋值,不会把这个值看作是对象的属性。代码如下:

class CuteCat:
    def __init__(self, cat_name, cat_age, cat_color):
        self.name = cat_name
        self.age = cat_age
        self.color = cat_color

cat1 = CuteCat("Lambton", 2, "黑")
cat2 = CuteCat("Jojo", 3, "白")
print(f"小猫{cat1.name}今年{cat1.age}岁了,颜色是{cat1.color}色")
print(f"小猫{cat2.name}今年{cat2.age}岁了,颜色是{cat2.color}色")

3)定义类的方法:

方法表示对象能做什么事情,比如上面的CutCat类的实例对象cat,应该可以思考,可以喵喵叫。定义方法的语法和函数的语法差不多,只有两个区别:

一:需要写在class里面,前面要有缩进,来表示这是属于该类的方法,

二:第一个参数同样也是被self占用,在方法中,self的一个作用是可以让我们在方法里面去获取或修改与对象绑定的属性。比如:如果小猫和叫唤的次数和年龄成正比,就可以写成如下代码:

class CuteCat:
    def __init__(self, cat_name, cat_age, cat_color):
        self.name = cat_name
        self.age = cat_age
        self.color = cat_color
    def speak(self):
        print("喵" * self.age)

调用方法的语法:

对象.方法名(参数1,参数2,...)

代码示例:

class CuteCat:
    def __init__(self, cat_name, cat_age, cat_color):
        self.name = cat_name
        self.age = cat_age
        self.color = cat_color
    def speak(self):
        print("喵" * self.age)

    def think(self, think_content):
        print(f"小猫{self.name}在思考:{think_content}")


cat1 = CuteCat("Lambton", 2, "黑")
print(cat1.think("今天晚上吃啥呢???"))

练习题:定义一个学生类,要求:

1. 属性包括学生姓名、学号、以及语数英三科的成绩

2. 能够设置学生某科目的成绩

3. 能够打印出该学生所有科目的成绩

class Student:
    def __init__(self, name, id):
        self.name = name
        self.id = id
        self.grades = {"语文": None, "数学": None, "英语": None}

    def set_grades(self, course, grade):
        if course in self.grades:
            self.grades[course] = grade

    def print_all_grades(self):
        print(f"学生{self.name}(学号{self.id})各科成绩如下:")
        for course, grade in self.grades.items():
            print(f"{course}: {grade}")

student1 = Student("小明", "20230101", )
student1.set_grades("数学", 100)
student1.print_all_grades()

注意:上面代码中,Student类的构造函数__init__()中,name和id是通过传参进获得的,而grades是在函数内定义的!!!!

5、类的继承

面向对象有个重要特征叫继承,意思是可以创建有层次的类,就像现实中的儿子继承爸爸,爸爸继承爷爷,类也可以有子类和父类,来表示从属关系,然后也和遗传学上的父子一样,子类也会继承父类的属性和方法。方法是在子类的类名后面加上括号,里面协商父类的类名。即 class 子类(父类):

代码如下:

class Mammal:
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        self.num_eyes = 2

    def breath(self):
        print(f"{self.name}在呼吸")

    def poop(self):
        print(f"{self.name}在拉屎")


class Human(Mammal):
    def Read(self):
        print(f"{self.name}在阅读")

class Cat(Mammal):
    def scratch_sofa(self):
        print(f"{self.name}在抓沙发")

上面代码中,由于子类没有自己的构造函数__init__(self),所以就会调用父类的构造函数,让实例具备名字、性别、眼睛数这三个属性,当实例调用poop拉屎方法时,用的也是父类的方法。但是如果子类有自己的poop方法,就会调用自己的。这背后的逻辑是:实例对象会优先看自己所属的类有没有该方法,如果没有,就往上找自己的父类的同名方法使用。那么问题来了,如果我们想给子类增加一些属性,那么我们在子类中写了构造函数__init__(self),那么对象就会优先使用子类的构造函数,那么父类构造函数中定义的属性子类就无法继承了,导致实例中只有新增的那几个属性。一个比较蠢的解决办法是:在子类的构造函数__init__(self)里面把父类的那些属性也都写上,但这又造成了代码的重复。更优雅的做法是:在子类的构造函数__init__(self)下面用super()方法,super()方法会返回当前类的父类,所以在子类的__init__(self)里写super().__init__(self, 参数1, 参数2, ...)就会调用父类的构造函数,那么子类也就会有姓名、性别、眼睛数量的属性了。练习题如下,帮助理解。

练习题:人力系统

需求:

1. 员工分为两类:全职员工FullTimeEmployee、兼职员工PartTimeEmployee

2. 全职员工都有“姓名 name”、“工号 id”属性,都具备打印信息“print _info”(打印姓名、工号)方法

3. 全职员工有月薪“monthly_salary”属性

4. 兼职员工有日新“daily_salary”属性、每月工作天数“work_days”属性

5. 全职和兼职都有“计算月薪calculate_monthly_pay”方法,但是具体计算过程不一样

class Employee:
    def __init__(self, name, staff_id):
        self.name = name
        self.staff_id = staff_id

    def print_info(self):
        print(f"姓名:{self.name}(工号:{self.staff_id})")


class FullTimeEmployee(Employee):
    def __init__(self, name, staff_id, monthly_salary):
        super().__init__(name, staff_id)
        self.monthly_salary = monthly_salary

    def calculate_monthly_pay(self):
        return self.monthly_salary


class PartTimeEmployee(Employee):
    def __init__(self, name, staff_id, daily_salary, work_days):
        super().__init__(name, staff_id)
        self.daily_salary = daily_salary
        self.work_days = work_days

    def calculate_monthly_pay(self):
        return self.daily_salary * self.work_days

zhangsan = FullTimeEmployee("张三", "1001", 6000)
zhangsan.print_info()
print(zhangsan.calculate_monthly_pay())
lisi = PartTimeEmployee("李四", "1002", 210, 15)
lisi.print_info()
print(lisi.calculate_monthly_pay())

思考:什么时候应该用继承呢?

如果A和B两个东西,你可以说成“A是B”,那么就可以把A写成B的子类。比如:人类是动物,那么人类就可以写成是动物的子类,新能源车是车,那么新能源车就可以写成是车的子类。

你可能感兴趣的:(Python,python)