Day01-面向对象基础

Day01-面向对象基础

面向对象基础

面向对象的概念

面向对象程序设计OOP是一种以对象为核心来组织代码,把数据与操作这些数据的行为封装在一起,并通过对象之间的交互来解决问题的编程思想.

其组成主要是类与对象.交互主要是通过对象之间调用方法实现

与面向过程的区别

那么,面向对象与面向过程这两种程序设计,有什么区别呢?

面向过程重点关注的是步骤,以函数,步骤为中心,优势是适合于简单逻辑的场景,代码简洁.劣势是不适合复杂逻辑场景.

面向对象重点关注的是对象,以对象为中心,将对象的职责,关系划分清楚,再将对象的属性(数据)与方法(操作数据的行为)

封装到一起,最后通过对象间的交互实现程序功能,优势是适合于复杂逻辑的场景,易于扩展,更符合人类逻辑.劣势是代码较为复杂.

适用场景

面向过程: 简单的线性任务,一次性的脚本任务

面向对象: 复杂系统,多人协作项目.

核心要素

实际上是有四个,但这里只讲三个

封装

作用: 将属性与方法封装在一起,对外只暴露必要的接口,隐藏内部实现细节,并可根据业务逻辑,实现保护数据的需求.

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

    # 在这里,对外暴露了一个方法,是say_hello,具体实现在类中.
    def say_hello(self):
        print(f"你好,我是{self.name},我{self.age}岁")


if __name__ == '__main__':
    p = People('Alice', 20)
    # 在这里,调用了People类的暴露的方法,具体实现不在这里,只管调用就可以.
    p.say_hello()

继承

为什么要有继承? 继承的目的: 代码复用与层级关系的构建,它关注的是这个东西和那个东西是什么关系,以及它多了和改了些什么?

如,麻雀继承了鸟,那么麻雀就自动拥有了翅膀和飞行的能力(这就是复用).以及,它可以添加自己的属性,如胆小.

一句话解释: 继承简单来说使子类快速获取父类属性及方法的一个特性.

实际上,继承不仅可以使子类快速复用父用的属性和方法,子类还可以在父类的基础上,扩展,改写父类的属性和方法.

所以,继承实际上等于复用+扩展+改写

​ 扩展: 子类可以在拥有父类方法的基础上,增加自己的新功能.

​ 改写: 子类可以重写父类的方法,当子类觉得父类的方法不能够满足自己需要的时候,子类就可以重写父类的方法,添加自己的实现.

注意,Python支持多继承,也就是一个类可以继承自多个父类.

python
# main.py
# 该实例简单定义了一个父类与子类,用于解释继承中,方法,属性的传递.
class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 在这里,对外暴露了一个方法,是say_hello,具体实现在类中.
    def say_hello(self):
        print(f"你好,我是{self.name},我{self.age}岁")

    def say_goodbye(self):
        print(f"再见,我是{self.name}")


class Student(People):
    def __init__(self, name, age, school):
        super().__init__(name, age)	# 在子类中,调用父类需要
        self.school = school

    def say_hello(self):
        # 在这里调用了父类的方法
        super().say_hello()
        print(f"我在{self.school}上学")

        
if __name__ == '__main__':
    student = Student("小明", 10, "清华大学")
    student.say_hello()
    # 在这里,可以发现子类中没有定义say_goodbye方法,但是可以调用父类的方法.
    # 因为子类继承了父类的方法,所以子类中也包含了父类的方法.
    student.say_goodbye()
python
# demo1.py
class Person:
    def __init__(self, name):
        self.name = name

    def introduce(self):
        return f"我是 {self.name}"


# 继承: class <子类名>[(<父类名列表>)]
class Student(Person):
    # Student 继承自 Person,新增 grade 属性
    def __init__(self, name, grade):
        super().__init__(name)  # 调用父类构造函数
        self.grade = grade

    # 无需装饰器,直接隐式重写父类方法了.
    def introduce(self):  # 重写父类方法
        return f"我是 {self.name},是一名学生,今年 {self.grade} 年级"

    # 定义一个子类专属方法
    def study(self):
        return f"{self.name} 正在学习中..."


class Teacher(Person):
    # Teacher 继承自 Person,新增 subject 属性
    def __init__(self, name, subject):
        super().__init__(name)
        self.subject = subject

    def introduce(self):
        return f"我是 {self.name},是一名老师,教授科目为 {self.subject}"

    # 定义一个专属方法
    def teach(self):
        return f"{self.name} 正在教 {self.subject}"

if __name__ == "__main__":
    print("单继承与方法重写")
    s = Student("小明", 3)
    t = Teacher("王老师", "数学")

    # 调用各自实例对象的方法
    print(s.introduce())
    print(t.introduce())
    print(s.study())
    print(t.teach())

多态

作用: 同一个函数在不同的场景下有不同的具体实现. 白话理解就是同一个动作,不同的反应。

优点:

  • 易于扩展,增加新的类型时,只需要新建一个类.
  • 修改本类时,修改自己类的方法不会影响到其它的类.

多态的初衷:开闭原则

多态的存在,是为了实现软件设计的最高准则对扩展开放,对修改关闭,也就是可以新增新功能,但旧有功能不能修改,防止bug越改越多.

python
# main.py
class Person:
    def introduce(self):
        return "我是一个普通人"


# 继承自Person
class Student(Person):
    def introduce(self):
        return "我是一名学生"


# 继承自Person
class Teacher(Person):
    def introduce(self):
        return "我是一名老师"


def make_introduction(people_object):
    # 同一个接口,传入不同对象,表现出不同行为
    # 在这里,通过传入不同对象的同名方法,可以发现对外暴露的方法定义是不变的,但是具体行为是不同的.
    print(people_object.introduce())


if __name__ == "__main__":
    print("多态演示")
    # 在这里声明了三个类的三个匿名对象,并存储进peopleList中.
    peopleList = [Student(), Teacher(), Person()]
    for i in peopleList:
		# 调用时,传入不同的对象,但执行的是同一个方法,也就是introduce()方法,只不过
        make_introduction(i)

抽象(暂未涉及)

抽象忽略了无关的细节,只关注与问题相关的关键特征. 类是对象的抽象.

为什么要的抽象? 抽象的目的: 降低复杂度,只关注这个东西是什么以及它能做什么,而不关心它怎么做.

如,脑子里有鸟这个概念,会关注鸟有翅膀,会飞,但不会去纠结这只鸟羽毛的具体颜色,怎么摆翅膀等.

下面的示例,涉及到了抽象基类,使用前需要导包"ABC".

抽象基类定义了一组方法,要求继承自该抽象基类的子类,必须实现该类的抽象方法.

python
# demo4.py
from abc import ABC, abstractmethod

# PythonABC: 定义了必须有什么
class Person(ABC):
    # 使用@abstractmethod装饰器来表示该方法没有实现,是抽象方法,需要子类实现
    # 在这里,方法没有具体实现,只有一个抽象方法,所以"如果调用这个方法,则不会有任何结果",这就要求子类必须实现该方法.
    @abstractmethod
    def get_role(self) -> str:
        # 返回人的角色
        pass

    @abstractmethod
    def introduce(self) -> str:
        # 自我介绍
        pass

class Student(Person):
    def __init__(self, name: str, grade: str):
        self.name = name
        self.grade = grade
    # 如果子类不实现父类的抽象方法,那么IDE会进行提示
    def get_role(self) -> str:
        return "学生"

    def introduce(self) -> str:
        return f"我叫{self.name},是{self.grade}的学生."

class Teacher(Person):
    def __init__(self, name: str, subject: str):
        self.name = name
        self.subject = subject

    def get_role(self) -> str:
        return "老师"

    def introduce(self) -> str:
        return f"我是{self.name}老师,教{self.subject}."

类名使用大驼峰的方式命名

对应于类名的类文件,使用全小写字母+下划线的方式命名

类的组成

一个Python类由以下部分组成

  • 类属性
  • 构造方法
  • 实例方法
  • 类方法
  • 静态方法
  • 特殊方法

简单解释

  • 类属性: 只能由类名直接调用的属性,如Student.name
  • 构造方法: __init__,从功能上来看,类似于构造方法,还有一个魔术方法__new__,这个方法中也可以写self.xxx = xxx,但非常不建议,违反了职责分离,__new__是用于创建对象时执行,在这里面写self.xxx = xxx,类似于房子还没封顶就开始装修.
  • 实例方法: 就是普通方法,但只有实例化后才能调用.
  • 类方法: 可以直接由类名.方法名调用的方法,也可以由实例.方法名调用.
  • 静态方法: 可以直接由类名.方法名调用的方法
  • 特殊方法,如魔术方法
python

魔术方法

魔术方法就是以双下划线开头与结尾的方法,如__init__,__str__,不需要显式调用,会由python解释器在一定情况下自动调用,如实例化,打印,比较,运算等,

另外,非常不推荐你自己声明一个魔术方法,这是由python官方声明的.

原因是,自己声明的魔术方法,有可能会在未来的某一个版本中,与python官方的定义冲突,导致程序出现异常.

但并不是说,你不能声明魔术方法,你仍然可以声明一个以双下划线开关与结尾的方法,并以普通方法的形式调用.

常见的魔术方法

魔术方法触发场景典型用途
__init__obj = Class()初始化对象属性,也即构造函数
__str__print(obj)定义对象的显示文本(给用户看)
__len__len(obj)定义对象的长度
__del__程序执行完毕/对象被删除/对象被重新初始化释放资源,关闭网络连接,写入并关闭文件等.

关于今天讲的__del__的补充

实际上,__del__ 不仅会在程序执行完毕和对象被del删除时执行,还会在对象被重新定义时执行,而且是立即执行.

python
class Bird:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print(f"Bird is dead@{self.name}")


if __name__ == '__main__':
    print("Start")
    bird = Bird("麻雀")
    bird = Bird("鹦鹉")  # 在这里可以发现bird="麻雀"被立即删除了.
    # 同上,鹦鹉被立即删除了.
    # 这行代码定义的对象的__del__,要在End之后输出,因为在程序未执行结束前,__del__方法并未执行
    bird = Bird("鸽子")
    print("End")

self关键字

内置关键字,用于指向对象实例本身,存储的是地址值.

在下面的示例中,可以发现self与people1的地址是相同的,同理,创建多个对象也是一样的

python
# main.py
class Person:
    def get_self(self):
        print(f"id(self): {id(self)}")

if __name__ == "__main__":
    people1 = Person()
    print(f"id(people1): {id(people1)}")
    people1.get_self()

使用self在类中调用本类方法

python
# main.py
class Person:
    def get_self(self):
        print(f"id(self): {id(self)}")
        # 在类内部,若要访问当前类中定义的方法,需要加上self关键字.
        self.say_hello()

    def say_hello(self):
        print("hello")


if __name__ == "__main__":
    people1 = Person()
    print(f"id(people1): {id(people1)}")
    people1.get_self()