面向对象基础
面向对象的概念
面向对象程序设计OOP是一种以对象为核心来组织代码,把数据与操作这些数据的行为封装在一起,并通过对象之间的交互来解决问题的编程思想.
其组成主要是类与对象.交互主要是通过对象之间调用方法实现
与面向过程的区别
那么,面向对象与面向过程这两种程序设计,有什么区别呢?
面向过程重点关注的是步骤,以函数,步骤为中心,优势是适合于简单逻辑的场景,代码简洁.劣势是不适合复杂逻辑场景.
面向对象重点关注的是对象,以对象为中心,将对象的职责,关系划分清楚,再将对象的属性(数据)与方法(操作数据的行为)
封装到一起,最后通过对象间的交互实现程序功能,优势是适合于复杂逻辑的场景,易于扩展,更符合人类逻辑.劣势是代码较为复杂.
适用场景
面向过程: 简单的线性任务,一次性的脚本任务
面向对象: 复杂系统,多人协作项目.
核心要素
实际上是有四个,但这里只讲三个
封装
作用: 将属性与方法封装在一起,对外只暴露必要的接口,隐藏内部实现细节,并可根据业务逻辑,实现保护数据的需求.
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支持多继承,也就是一个类可以继承自多个父类.
# 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()# 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越改越多.
# 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".
抽象基类定义了一组方法,要求继承自该抽象基类的子类,必须实现该类的抽象方法.
# 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,类似于房子还没封顶就开始装修.- 实例方法: 就是普通方法,但只有实例化后才能调用.
- 类方法: 可以直接由类名.方法名调用的方法,也可以由实例.方法名调用.
- 静态方法: 可以直接由类名.方法名调用的方法
- 特殊方法,如魔术方法
魔术方法
魔术方法就是以双下划线开头与结尾的方法,如__init__,__str__,不需要显式调用,会由python解释器在一定情况下自动调用,如实例化,打印,比较,运算等,
另外,非常不推荐你自己声明一个魔术方法,这是由python官方声明的.
原因是,自己声明的魔术方法,有可能会在未来的某一个版本中,与python官方的定义冲突,导致程序出现异常.
但并不是说,你不能声明魔术方法,你仍然可以声明一个以双下划线开关与结尾的方法,并以普通方法的形式调用.
常见的魔术方法
| 魔术方法 | 触发场景 | 典型用途 |
|---|---|---|
__init__ | obj = Class() | 初始化对象属性,也即构造函数 |
__str__ | print(obj) | 定义对象的显示文本(给用户看) |
__len__ | len(obj) | 定义对象的长度 |
__del__ | 程序执行完毕/对象被删除/对象被重新初始化 | 释放资源,关闭网络连接,写入并关闭文件等. |
关于今天讲的__del__的补充
实际上,__del__ 不仅会在程序执行完毕和对象被del删除时执行,还会在对象被重新定义时执行,而且是立即执行.
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的地址是相同的,同理,创建多个对象也是一样的
# 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在类中调用本类方法
# 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()- 本文链接: https://go.kulve.tech/posts/hm%E7%AC%94%E8%AE%B0/day01/day01-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%9F%BA%E7%A1%80/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。
